diff --git a/.gitignore b/.gitignore index 4239ba7..35e731c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,9 @@ settings.json Dockerfile.local edf_files/ -**/.DS_Store \ No newline at end of file +galaxy-app/edf/physionet/edfx/* +**/.DS_Store + +# VSCode Files +.vscode/* +jsconfig.json \ No newline at end of file diff --git a/client/Annotators/EDF/index.js b/client/Annotators/EDF/index.js index 7f8f7eb..38925cc 100644 --- a/client/Annotators/EDF/index.js +++ b/client/Annotators/EDF/index.js @@ -47,21 +47,40 @@ Template.AnnotatorEDF.onCreated(function() { }); Template.AnnotatorEDF.onRendered(function() { - const annotatorContainer = $(this.find('.annotator-container')); - const template = this; - let config = $.extend({}, this.data.task.annotatorConfig); - config = $.extend(config, this.data.preferences.annotatorConfig); - config = $.extend(config, { - recordingName: this.data.data.path, - context: this.data, - setVisibilityStatusForInfoPanel: (isVisible) => { - setVisibilityStatusForFloatingPanel(isVisible, template, '.info-panel-container'); - }, - toggleInfoPanel: () => { - toggleFloatingPanel(undefined, template, '.info-panel-container'); - }, - }); - annotatorContainer.TimeSeriesAnnotator(config); + const annotatorContainer = $(this.find(".annotator-container")); + const template = this; + //console.log("Template:", this); + let config = $.extend({}, this.data.task.annotatorConfig); + config = $.extend(config, this.data.preferences.annotatorConfig); + //console.log("here"); + config = $.extend(config, { + recordingName: this.data.dataset.reduce((combined, data) => { + let dataPathSegments = data.path.split("/"); + if (!combined.length) + return dataPathSegments[dataPathSegments.length - 1]; + return ( + combined + " + " + dataPathSegments[dataPathSegments.length - 1] + ); + }, ""), + allRecordings: this.data.dataset.map((data) => { + return { _id: data._id, path: data.path, source: data.source }; + }), + context: this.data, + setVisibilityStatusForInfoPanel: (isVisible) => { + setVisibilityStatusForFloatingPanel( + isVisible, + template, + ".info-panel-container" + ); + }, + toggleInfoPanel: () => { + toggleFloatingPanel(undefined, template, ".info-panel-container"); + }, + }); + //console.log("Template2:", this); + //console.log("dataset:", this.data.dataset); + //console.log("config", config); + annotatorContainer.TimeSeriesAnnotator(config); }); Template.AnnotatorEDF.onDestroyed(function() { diff --git a/client/Annotators/EDF/style.css b/client/Annotators/EDF/style.css index 8a7a895..943ff31 100644 --- a/client/Annotators/EDF/style.css +++ b/client/Annotators/EDF/style.css @@ -174,7 +174,57 @@ } .frequency_filter_panel .select_panel { - width: 160px; + width: 110px; +} + +.user_selection_panel { + float: left; + margin: 0; +} + +.user_selection_panel .select_panel { + width: 210px; +} + +.user_selection_panel .optgroup { + text-align: center; + font-weight: normal; +} + +.annotation_type_select_panel { + float: left; + margin: 0; +} + +.annotation_type_select_panel .select_panel { + width: 290px; +} + +.annotation_type_select_panel .optgroup { + text-align: center; + font-weight: normal; +} + +.timescale_panel { + float: left; + margin: 0; +} + +.timescale_panel .select_panel { + width: 180px; +} + +.timesync_panel { + float: left; + margin: 0; +} + +.timesync_panel .select_panel { + width: 210px; +} + +.timesync_panel button { + margin: 0 0 0 20px !important; } .button_container { @@ -236,7 +286,7 @@ } .highcharts-annotation.saved:hover .toolbar { - display: block; + display: inline; } .confidence-buttons input { @@ -273,12 +323,12 @@ } .highcharts-annotation .comment input { - height: 21px !important; + /* height: 21px !important; width: 180px !important; - vertical-align: middle !important; - font-size: 13px !important; + vertical-align: middle !important; */ + font-size: 13px !important; background-color: #ffffff !important; - margin: 0 !important; + margin: 0 !important; } .annotationTime { @@ -556,3 +606,101 @@ padding-left: 1.6rem; padding-right: 1.6rem; } +/* Popup container */ +.popup { + position: relative; + display: inline-block; + cursor: pointer; + z-index: 4; +} + +/* The actual popup (appears on top) */ +.popup .popuptext { + visibility: hidden; + width: 450px; + background-color: #26a69a; + color: #000; + text-align: center; + border-radius: 10px; + padding: 8px 0; + position: fixed; + z-index: 1002; + bottom: 60%; + left: 20%; + margin-left: 10px; +} + +/* Popup arrow */ +.popup .popuptext::after { + content: ""; + position: absolute; + top: 10%; + left: -100%; + margin-left: -5px; + border-width: 5px; + z-index: 1005; + border-style: solid; + border-color:#fff transparent transparent transparent; +} +.popup .popupbutton { + visibility: hidden; +} + +/* Toggle this class when clicking on the popup container (hide and show the popup) */ +.popup .show { + visibility: visible; + -webkit-animation: fadeIn 1s; + animation: fadeIn 1s; + z-index: 7; +} + +.channel_name { + font-size: 1rem; + font-weight: thin; + color: #000000; + display: inline; + line-height: 2rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.time_sync { + font-size: 1rem; + font-weight: thin; + color: #000000; + background-color: rgba(51, 247, 60, 0.774); + display: inline; + line-height: 2rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + margin-left: 0.5rem; + margin-right: 0.5rem; +} + + +.amplitude_adjustment_container{ + float: left; + margin-left: 1rem; +} + +.amplitude_adjustment_panel{ + float: right; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.amplitude_adjustment_button{ + float: right; + margin-left: 0.2rem; + margin-right: 0.2rem; +} + + + + + + diff --git a/client/Annotators/EDF/timeSeriesAnnotator.js b/client/Annotators/EDF/timeSeriesAnnotator.js index ca38a34..b11bb85 100644 --- a/client/Annotators/EDF/timeSeriesAnnotator.js +++ b/client/Annotators/EDF/timeSeriesAnnotator.js @@ -1,487 +1,648 @@ -import { ReactiveVar } from 'meteor/reactive-var'; -import { Annotations, Preferences } from '/collections'; -import swal from 'sweetalert2'; - -var Highcharts = require('highcharts/highstock'); -require('highcharts-annotations')(Highcharts); -require('highcharts-boost')(Highcharts); - -$.widget('crowdeeg.TimeSeriesAnnotator', { - - options: { - optionsURLParameter: 'annotatorOptions', - projectUUID: undefined, - requireConsent: false, - trainingVideo: { - forcePlay: false, - blockInteraction: true, - vimeoId: '169158678', +import { ReactiveVar } from "meteor/reactive-var"; +import { Annotations, Preferences, Assignments, Data } from "/collections"; +import swal from "sweetalert2"; + +var Highcharts = require("highcharts/highstock"); +require("highcharts-annotations")(Highcharts); +require("highcharts-boost")(Highcharts); + +$.widget("crowdeeg.TimeSeriesAnnotator", { + // initial options when the widget is created + options: { + optionsURLParameter: "annotatorOptions", + projectUUID: undefined, + requireConsent: false, + trainingVideo: { + forcePlay: false, + blockInteraction: true, + vimeoId: "169158678", + }, + payment: 0.0, + showConfirmationCode: false, + confirmationCode: undefined, + recordingName: undefined, + allRecordings: undefined, + defaultMontage: undefined, + channelsDisplayed: [0, 1, 2, 3, 4, 6, 7], + channelGains: undefined, + targetSamplingRate: undefined, + useHighPrecisionSampling: false, + channelGainAdjustmentEnabled: false, + showChannelGainAdjustmentButtons: false, + showInputPanelContainer: true, + setVisibilityStatusForInfoPanel: undefined, + toggleInfoPanel: undefined, + staticFrequencyFiltersByChannelType: {}, + staticFrequencyFiltersByDataModality: {}, + preClassification: { + show: false, + title: "Pre-Classification", + source: {}, + uncertaintyInformation: { + quantitative: { + show: true, }, - payment: 0.00, - showConfirmationCode: false, - confirmationCode: undefined, - recordingName: undefined, - defaultMontage: undefined, - channelsDisplayed: [0, 1, 2, 3, 4, 6, 7], - channelGains: undefined, - targetSamplingRate: undefined, - useHighPrecisionSampling: false, - channelGainAdjustmentEnabled: true, - showChannelGainAdjustmentButtons: true, - showInputPanelContainer: true, - setVisibilityStatusForInfoPanel: undefined, - toggleInfoPanel: undefined, - staticFrequencyFiltersByChannelType: {}, - staticFrequencyFiltersByDataModality: {}, - preClassification: { - show: false, - title: 'Pre-Classification', - source: {}, - uncertaintyInformation: { - quantitative: { - show: true, - }, - argumentative: { - show: false, - noiseProbability: 0.0, - }, + argumentative: { + show: false, + noiseProbability: 0.0, + }, + }, + }, + frequencyFilters: [ + { + title: "Lowpass", + type: "lowpass", + options: [ + { + name: "70 Hz", + value: 70, + default: true, + }, + { + name: "30 Hz", + value: 30, + }, + { + name: "15 Hz", + value: 15, + }, + { + name: "off", + value: undefined, + }, + ], + }, + { + title: "Highpass", + type: "highpass", + options: [ + { + name: "10 Hz", + value: 10, + }, + { + name: "3 Hz", + value: 3, + }, + { + name: "1 Hz", + value: 1, + }, + { + name: "0.5 Hz", + value: 0.5, + default: true, + }, + { + name: "off", + value: undefined, + }, + ], + }, + { + title: "Notch", + type: "notch", + options: [ + { + name: "60 Hz", + value: 60, + }, + { + name: "50 Hz", + value: 50, + }, + { + name: "off", + value: undefined, + default: true, + }, + ], + }, + ], + xAxisTimescales: [ + { + title: "Timescale", + options: [ + { + name: "1 Sec/page", + value: 1, + }, + { + name: "2 Sec/page", + value: 2, + }, + { + name: "5 Sec/page", + value: 5, + }, + { + name: "10 Sec/page", + value: 10, + }, + { + name: "15 Sec/page", + value: 15, + }, + { + name: "20 Sec/page", + value: 20, + }, + { + name: "30 Sec/page", + value: 30, + }, + { + name: "60 Sec/page", + value: 60, + default: true, + }, + // below will lower the data sampling rates i.e. lower the resolution + { + name: "5 min/page", + value: 300, + }, + { + name: "1 hour/page", + value: 3600, + }, + { + name: "4 hours/page", + value: 14400, + }, + { + name: "8 hours/page", + value: 28800, + }, + ], + }, + ], + timeSyncOptions: [ + { + title: "Time Sync", + options: [ + { + name: "Off", + value: undefined, + default: true, + }, + { + name: "By Crosshair", + value: "crosshair", + }, + { + name: "No Timelock", + value: "notimelock", + }, + { + name: "Start of File (offset)", + value: "offset", + }, + ], + }, + ], + boxAnnotationUserSelection: [ + { + title: "Box Annotations", + options: [], + }, + ], + annotationType: [ + { + title: "Annotation Type", + options: [], + }, + ], + keyboardInputEnabled: true, + isReadOnly: false, + startTime: 0, + visibleRegion: { + start: undefined, + end: undefined, + showProgress: true, + hitModeEnabled: true, + training: { + enabled: true, + isTrainingOnly: false, + numberOfInitialWindowsUsedForTraining: 0, + windows: [], + }, + }, + graph: { + channelSpacing: 400, + width: undefined, + height: 600, + marginTop: 10, + marginBottom: 30, + marginLeft: 90, + marginRight: 30, + backgroundColor: "#ffffff", + lineWidth: 1.0, + enableMouseTracking: false, + }, + marginTop: null, + marginBottom: null, + windowSizeInSeconds: 30, + windowJumpSizeFastForwardBackward: 1, + preloadEntireRecording: false, + numberOfForwardWindowsToPrefetch: 3, + numberOfFastForwardWindowsToPrefetch: 3, + numberOfBackwardWindowsToPrefetch: 3, + numberOfFastBackwardWindowsToPrefetch: 3, + relativeGainChangePerStep: 0.25, + idleTimeThresholdSeconds: 300, + experiment: {}, + showArtifactButtons: false, + showSleepStageButtons: true, + showNavigationButtons: true, + showBackToLastActiveWindowButton: true, + showBookmarkCurrentPageButton: true, + showFastBackwardButton: true, + showBackwardButton: true, + showForwardButton: true, + showFastForwardButton: true, + showShortcuts: false, + showLogoutButton: false, + showAnnotationTime: false, + showReferenceLines: true, + showTimeLabels: true, + showChannelNames: true, + features: { + examplesModeEnabled: false, + examples: [], + cheatSheetsEnabled: false, + openCheatSheetOnPageLoad: true, + scrollThroughExamplesAutomatically: true, + scrollThroughExamplesSpeedInSeconds: 5, + showUserAnnotations: true, + showAllBoxAnnotations: "", + order: ["sleep_spindle", "k_complex", "rem", "vertex_wave", "delta_wave"], + options: { + sleep_spindle: { + name: "Spindle", + annotation: { + red: 86, + green: 186, + blue: 219, + alpha: { + min: 0.22, + max: 0.45, + }, + }, + answer: { + red: 0, + green: 0, + blue: 0, + alpha: { + min: 0.1, + max: 0.25, }, + }, + training: { + windows: [], + }, }, - frequencyFilters: [{ - title: 'Lowpass', - type: 'lowpass', - options: [ - { - name: '70 Hz', - value: 70, - default: true, - }, - { - name: '30 Hz', - value: 30, - }, - { - name: '15 Hz', - value: 15, - }, - { - name: 'off', - value: undefined, - }, - ], - }, { - title: 'Highpass', - type: 'highpass', - options: [ - { - name: '10 Hz', - value: 10, - }, - { - name: '3 Hz', - value: 3, - }, - { - name: '1 Hz', - value: 1, - }, - { - name: '0.5 Hz', - value: 0.5, - default: true, - }, - { - name: 'off', - value: undefined, - }, - ], - }, { - title: 'Notch', - type: 'notch', - options: [ - { - name: '60 Hz', - value: 60, - }, - { - name: '50 Hz', - value: 50, - }, - { - name: 'off', - value: undefined, - default: true, - }, - ], - }], - keyboardInputEnabled: true, - isReadOnly: false, - startTime: 0, - visibleRegion: { - start: undefined, - end: undefined, - showProgress: true, - hitModeEnabled: true, - training: { - enabled: true, - isTrainingOnly: false, - numberOfInitialWindowsUsedForTraining: 0, - windows: [], - } + k_complex: { + name: "K-Complex", + annotation: { + red: 195, + green: 123, + blue: 225, + alpha: { + min: 0.18, + max: 0.35, + }, + }, + answer: { + red: 0, + green: 0, + blue: 0, + alpha: { + min: 0.1, + max: 0.25, + }, + }, + training: { + windows: [], + }, }, - graph: { - channelSpacing: 400, - width: undefined, - height: 600, - marginTop: 10, - marginBottom: 30, - marginLeft: 90, - marginRight: 30, - backgroundColor: '#ffffff', - lineWidth: 1.0, - enableMouseTracking: false, + rem: { + name: "REM", + annotation: { + red: 238, + green: 75, + blue: 38, + alpha: { + min: 0.18, + max: 0.35, + }, + }, + answer: { + red: 0, + green: 0, + blue: 0, + alpha: { + min: 0.1, + max: 0.25, + }, + }, + training: { + windows: [], + }, }, - marginTop: null, - marginBottom: null, - windowSizeInSeconds: 30, - windowJumpSizeFastForwardBackward: 10, - preloadEntireRecording: false, - numberOfForwardWindowsToPrefetch: 3, - numberOfFastForwardWindowsToPrefetch: 3, - numberOfBackwardWindowsToPrefetch: 3, - numberOfFastBackwardWindowsToPrefetch: 3, - relativeGainChangePerStep: 0.25, - idleTimeThresholdSeconds: 300, - experiment: {}, - showArtifactButtons: false, - showSleepStageButtons: false, - showNavigationButtons: true, - showBackToLastActiveWindowButton: true, - showBookmarkCurrentPageButton: true, - showFastBackwardButton: true, - showBackwardButton: true, - showForwardButton: true, - showFastForwardButton: true, - showShortcuts: false, - showLogoutButton: false, - showAnnotationTime: false, - showReferenceLines: true, - showTimeLabels: true, - showChannelNames: true, - features: { - examplesModeEnabled: false, - examples: [], - cheatSheetsEnabled: false, - openCheatSheetOnPageLoad: true, - scrollThroughExamplesAutomatically: true, - scrollThroughExamplesSpeedInSeconds: 5, - showUserAnnotations: true, - order: ['sleep_spindle', 'k_complex', 'rem', 'vertex_wave'], - options: { - 'sleep_spindle': { - name: 'Spindle', - annotation: { - red: 86, - green: 186, - blue: 219, - alpha: { - min: 0.22, - max: 0.45 - } - }, - answer: { - red: 0, - green: 0, - blue: 0, - alpha: { - min: 0.1, - max: 0.25 - } - }, - training: { - windows: [], - }, - }, - 'k_complex': { - name: 'K-Complex', - annotation: { - red: 195, - green: 123, - blue: 225, - alpha: { - min: 0.18, - max: 0.35 - } - }, - answer: { - red: 0, - green: 0, - blue: 0, - alpha: { - min: 0.1, - max: 0.25 - } - }, - training: { - windows: [], - }, - }, - 'rem': { - name: 'REM', - annotation: { - red: 238, - green: 75, - blue: 38, - alpha: { - min: 0.18, - max: 0.35 - } - }, - answer: { - red: 0, - green: 0, - blue: 0, - alpha: { - min: 0.1, - max: 0.25 - } - }, - training: { - windows: [], - }, - }, - 'vertex_wave': { - name: 'Vertex Wave', - annotation: { - red: 0, - green: 0, - blue: 0, - alpha: { - min: 0.18, - max: 0.35 - } - }, - answer: { - red: 0, - green: 0, - blue: 0, - alpha: { - min: 0.1, - max: 0.25 - } - }, - training: { - windows: [], - }, - }, - 'delta_wave': { - name: 'Delta Wave', - annotation: { - red: 20, - green: 230, - blue: 30, - alpha: { - min: 0.18, - max: 0.35 - } - }, - answer: { - red: 0, - green: 0, - blue: 0, - alpha: { - min: 0.1, - max: 0.25 - } - }, - training: { - windows: [], - }, - } + vertex_wave: { + name: "Vertex Wave", + annotation: { + red: 0, + green: 0, + blue: 0, + alpha: { + min: 0.18, + max: 0.35, }, + }, + answer: { + red: 0, + green: 0, + blue: 0, + alpha: { + min: 0.1, + max: 0.25, + }, + }, + training: { + windows: [], + }, }, - }, - - _create: function() { - var that = this; - that._initializeVariables(); - that._keyDownCallback = that._keyDownCallback.bind(that); - that._reinitChart = that._reinitChart.bind(that); - $(that.element).addClass(that.vars.uniqueClass); - that._fetchOptionsFromURLParameter(); - that._createHTMLContent(); - if (that.options.requireConsent) { - that._showConsentForm(); - } - if (that.options.trainingVideo.forcePlay) { - that._forcePlayTrainingVideo(); - } - that._setupHITMode(); - if (that.options.features.examplesModeEnabled) { - that._setupExamplesMode(); - return; - } - if (that.options.experiment.running) { - that._setupExperiment(); - that._setup(); - } - else { - var recordingNameFromGetParameter = that._getUrlParameter('recording_name'); - if (recordingNameFromGetParameter) { - that.options.recordingName = recordingNameFromGetParameter; - } - that._setup(); - } - }, - - _destroy: function() { - var that = this; - - $(document).off('keydown', that._keyDownCallback); - $(window).off('resize', that._reinitChart); - }, - - _initializeVariables: function() { - var that = this; - that.vars = { - uniqueClass: that._getUUID(), - activeFeatureType: 0, - chart: null, - activeAnnotations: [], - annotationsLoaded: false, - selectedChannelIndex: undefined, - currentWindowData: null, - currentWindowStart: null, - currentWindowStartReactive: new ReactiveVar(null), - lastActiveWindowStart: null, - forwardEnabled: undefined, - fastForwardEnabled: undefined, - backwardEnabled: undefined, - fastBackwardEnabled: undefined, - numberOfAnnotationsInCurrentWindow: 0, - specifiedTrainingWindows: undefined, - currentTrainingWindowIndex: 0, - cheatSheetOpenedBefore: false, - scrollThroughExamplesIntervalId: undefined, - frequencyFilters: JSON.parse(JSON.stringify(that.options.frequencyFilters)), - audioContextSampleRate: 32768, - fullWindowLabels: { - 'artifacts_none': 'ARTIFACT', - 'artifacts_light': 'ARTIFACT', - 'artifacts_medium': 'ARTIFACT', - 'artifacts_strong': 'ARTIFACT', - 'sleep_stage_wake': 'SLEEP_STAGE', - 'sleep_stage_n1': 'SLEEP_STAGE', - 'sleep_stage_n2': 'SLEEP_STAGE', - 'sleep_stage_n3': 'SLEEP_STAGE', - 'sleep_stage_rem': 'SLEEP_STAGE', - 'sleep_stage_unknown': 'SLEEP_STAGE', + delta_wave: { + name: "Delta Wave", + annotation: { + red: 20, + green: 230, + blue: 30, + alpha: { + min: 0.18, + max: 0.35, }, - fullWindowLabelsToHumanReadable: { - 'artifacts_none': 'No artifacts', - 'artifacts_light': 'Light artifacts', - 'artifacts_medium': 'Medium artifacts', - 'artifacts_strong': 'Strong artifacts', - 'sleep_stage_wake': 'Wake', - 'sleep_stage_n1': 'N1 Sleep', - 'sleep_stage_n2': 'N2 Sleep', - 'sleep_stage_n3': 'N3 Sleep', - 'sleep_stage_rem': 'REM Sleep', - 'sleep_stage_unknown': 'Unknown', + }, + answer: { + red: 0, + green: 0, + blue: 0, + alpha: { + min: 0.1, + max: 0.25, }, - windowsCache: {}, - // windowCache is an object, keeping track of data that is loaded in the background: - // - // { - // 'window_identifier_key_1': undefined, // <-- data for this window is not available, but can be requested - // 'window_identifier_key_2': false, // <-- this window does not contain valid data - // 'window_identifier_key_3': { // <-- data for this window has been requested, but not been returned so far - // request: jqXHRObject, - // data: undefined - // }, - // 'window_identifier_key_4': { // <-- data for this window is available - // request: jqXHRObject, - // data: dataObject - // }, - // } - annotationsCache: {}, - // annotationsCache is an object, keeping track of annotations loaded from the server: - // - // { - // 'start_end_answer': undefined, // <-- data for this window is not available, but can be requested - // 'start_end_answer': {}, // <-- data for this window has been requested already - // } - } - if (that._getMontages()) { - that.vars.currentMontage = that.options.defaultMontage || that._getMontages()[0]; - } - if (that.options.channelGains) { - that.vars.channelGains = that.options.channelGains; - } - else { - var montages = that._getMontages(); - if (montages) { - that.vars.channelGains = {}; - montages.forEach(function(montage) { - that.vars.channelGains[montage] = that._getChannelsDisplayed(montage).map(function() { - return 1.0; - }); - }); - } - else { - that.vars.channelGains = that._getChannelsDisplayed().map(function() { - return 1.0; - }); - } - } + }, + training: { + windows: [], + }, + }, + }, }, + }, - _shouldBeMergedDeeply: function(objectA) { - if (!objectA) return false; - if (typeof objectA == 'number') return false; - if (typeof objectA == 'string') return false; - if (typeof objectA == 'boolean') return false; - if (objectA instanceof Array) return false; - return true; - }, + _create: function () { + var that = this; + //console.log("_create.that:", that); + that._initializeVariables(); + // handles key events + that._keyDownCallback = that._keyDownCallback.bind(that); - _mergeObjectsDeeply: function(target) { - var that = this; - var sources = [].slice.call(arguments, 1); - sources.forEach(function (source) { - for (var prop in source) { - if (that._shouldBeMergedDeeply(target[prop]) && that._shouldBeMergedDeeply(source[prop])) { - target[prop] = that._mergeObjectsDeeply(target[prop], source[prop]); - } - else { - target[prop] = source[prop]; - } - } - }); - return target; - }, + // destroys the current charts and reloads them + that._reinitChart = that._reinitChart.bind(that); - _fetchOptionsFromURLParameter: function() { - var that = this; - if (!that.options.optionsURLParameter) return; - var optionsStringFromURL = that._getUrlParameter(that.options.optionsURLParameter); - if (!optionsStringFromURL) return; - try { - var optionsFromURL = JSON.parse(optionsStringFromURL); - that._mergeObjectsDeeply(that.options, optionsFromURL); - } - catch (e) { - console.log('The following options string does not have valid JSON syntax:', optionsStringFromURL); - } - }, + $(that.element).addClass(that.vars.uniqueClass); + that._fetchOptionsFromURLParameter(); + that._createHTMLContent(); + if (that.options.requireConsent) { + that._showConsentForm(); + } + if (that.options.trainingVideo.forcePlay) { + that._forcePlayTrainingVideo(); + } + that._setupHITMode(); + if (that.options.features.examplesModeEnabled) { + that._setupExamplesMode(); + return; + } + if (that.options.experiment.running) { + that._setupExperiment(); + that._setup(); + } else { + var recordingNameFromGetParameter = + that._getUrlParameter("recording_name"); + if (recordingNameFromGetParameter) { + let id = Data.findOne({ + path: recordingNameFromGetParameter, + })._id; + that.options.allRecordings = [ + { _id: id, path: recordingNameFromGetParameter }, + ]; + console.log( + "recordingNameFromGetParameter:", + recordingNameFromGetParameter + ); + } + that._setup(); + } + }, + + _destroy: function () { + var that = this; + + $(document).off("keydown", that._keyDownCallback); + $(window).off("resize", that._reinitChart); + }, + + _initializeVariables: function () { + // initializing variables for future usage by the functions + var that = this; + that.vars = { + currentTimeDiff: 0, + annotationClicks: { + clickOne: null, + clickTwo: null, + }, + recordScalingFactors: true, + recordPolarity: true, + recordTranslation: true, + translation: {}, + scalingFactors: {}, + polarity: {}, + uniqueClass: that._getUUID(), + activeFeatureType: 0, + chart: null, + universalChangePointAnnotationsCache: [], + activeAnnotations: [], + annotationsLoaded: true, + selectedChannelIndex: undefined, + currentWindowData: null, + currentWindowStart: 0, + currentWindowStartReactive: new ReactiveVar(null), + lastActiveWindowStart: null, + forwardEnabled: undefined, + fastForwardEnabled: undefined, + backwardEnabled: undefined, + fastBackwardEnabled: undefined, + currentAnnotationTime: null, + popUpActive: 0, + setupOn: false, + printedBox: false, + channelTimeshift: {}, + timeSyncMode: "", + crosshairMode: false, + crosshairPosition: [], + crosshair: undefined, + recordingMetadata: {}, + recordingLengthInSeconds: 0, + numberOfAnnotationsInCurrentWindow: 0, + specifiedTrainingWindows: undefined, + requiredName: "", + valueOptions: 0, + allChannels: undefined, + currType: "", + increaseOnce: 0, + oldIndex: -1, + xAxisScaleInSeconds: 60, + currentTrainingWindowIndex: 0, + cheatSheetOpenedBefore: false, + scrollThroughExamplesIntervalId: undefined, + frequencyFilters: JSON.parse( + JSON.stringify(that.options.frequencyFilters) + ), + audioContextSampleRate: 32768, + fullWindowLabels: { + artifacts_none: "ARTIFACT", + artifacts_light: "ARTIFACT", + artifacts_medium: "ARTIFACT", + artifacts_strong: "ARTIFACT", + sleep_stage_wake: "SLEEP_STAGE", + sleep_stage_n1: "SLEEP_STAGE", + sleep_stage_n2: "SLEEP_STAGE", + sleep_stage_n3: "SLEEP_STAGE", + sleep_stage_rem: "SLEEP_STAGE", + sleep_stage_unknown: "SLEEP_STAGE", + }, + fullWindowLabelsToHumanReadable: { + artifacts_none: "No artifacts", + artifacts_light: "Light artifacts", + artifacts_medium: "Medium artifacts", + artifacts_strong: "Strong artifacts", + sleep_stage_wake: "Wake", + sleep_stage_n1: "N1 Sleep", + sleep_stage_n2: "N2 Sleep", + sleep_stage_n3: "N3 Sleep", + sleep_stage_rem: "REM Sleep", + sleep_stage_unknown: "Unknown", + }, + windowsCache: {}, + // windowCache is an object, keeping track of data that is loaded in the background: + // + // { + // 'window_identifier_key_1': undefined, // <-- data for this window is not available, but can be requested + // 'window_identifier_key_2': false, // <-- this window does not contain valid data + // 'window_identifier_key_3': { // <-- data for this window has been requested, but not been returned so far + // request: jqXHRObject, + // data: undefined + // }, + // 'window_identifier_key_4': { // <-- data for this window is available + // request: jqXHRObject, + // data: dataObject + // }, + // } + annotationsCache: {}, + // annotationsCache is an object, keeping track of annotations loaded from the server: + // + // { + // 'start_end_answer': undefined, // <-- data for this window is not available, but can be requested + // 'start_end_answer': {}, // <-- data for this window has been requested already + // } + + // annotationCrosshairPositions is used to draw annotation boxes using crosshairs across multiple pages + annotationCrosshairPositions: [], + annotationCrosshairCurrPosition: undefined, + // count the number of crosshairs currently displayed on the screen. + // annotationCrosshairCount: 0, + annotationCrosshairs: [], + annotationMode: undefined, + }; + if (that._getMontages()) { + that.vars.currentMontage = + that.options.defaultMontage || that._getMontages()[0]; + } + if (that.options.channelGains) { + that.vars.channelGains = that.options.channelGains; + } else { + var montages = that._getMontages(); + if (montages) { + that.vars.channelGains = {}; + montages.forEach(function (montage) { + that.vars.channelGains[montage] = that + ._getChannelsDisplayed(montage) + .map(function () { + return 1.0; + }); + }); + } else { + that.vars.channelGains = that._getChannelsDisplayed().map(function () { + return 1.0; + }); + } + } + }, + + _shouldBeMergedDeeply: function (objectA) { + if (!objectA) return false; + if (typeof objectA == "number") return false; + if (typeof objectA == "string") return false; + if (typeof objectA == "boolean") return false; + if (objectA instanceof Array) return false; + return true; + }, + + _mergeObjectsDeeply: function (target) { + var that = this; + var sources = [].slice.call(arguments, 1); + sources.forEach(function (source) { + for (var prop in source) { + if ( + that._shouldBeMergedDeeply(target[prop]) && + that._shouldBeMergedDeeply(source[prop]) + ) { + target[prop] = that._mergeObjectsDeeply(target[prop], source[prop]); + } else { + target[prop] = source[prop]; + } + } + }); + return target; + }, + + _fetchOptionsFromURLParameter: function () { + var that = this; + if (!that.options.optionsURLParameter) return; + var optionsStringFromURL = that._getUrlParameter( + that.options.optionsURLParameter + ); + if (!optionsStringFromURL) return; + try { + var optionsFromURL = JSON.parse(optionsStringFromURL); + that._mergeObjectsDeeply(that.options, optionsFromURL); + } catch (e) { + //console.log('The following options string does not have valid JSON syntax:', optionsStringFromURL); + } + }, - _createHTMLContent: function() { - var that = this; - var content = ' \ + _createHTMLContent: function () { + var that = this; + var content = + //TODO: lines 645 to end of comment is hwere the N1 things are located + ' \
\
\
\
\ + \
\
\
\ @@ -489,7 +650,7 @@ $.widget('crowdeeg.TimeSeriesAnnotator', {
\
\
\ -
\ + \
\ \
\
\ +
\
\ +
\ +
\ + \ +
\ +
\ +
\ +

\ +

\ + \ + \ + \ +
\ + \ + \ +
\ + \ + \ + \ + \ + \ +
\ +
\ \
\ '; - $(that.element).html(content); - }, + $(that.element).html(content); + }, - _adaptContent: function() { - var that = this; - if (!that.options.showChannelGainAdjustmentButtons) { - $(that.element).find('.adjustment_buttons').hide(); - } - if (!that.options.showArtifactButtons) { - $(that.element).find('.artifact_panel').hide(); - } - if (!that.options.showSleepStageButtons) { - $(that.element).find('.sleep_stage_panel').hide(); - } - if (!that.options.showNavigationButtons) { - $(that.element).find('.navigation_panel').hide(); - } - if (!that.options.showBackToLastActiveWindowButton) { - $(that.element).find('.backToLastActiveWindow').hide(); - } - if (!that.options.showBookmarkCurrentPageButton) { - $(that.element).find('.bookmarkCurrentPage').hide(); - } - if (!that._isArbitrating()) { - $(that.element).find('.jumpToLastDisagreementWindow').hide(); - $(that.element).find('.jumpToNextDisagreementWindow').hide(); - } - if (!that.options.showFastBackwardButton) { - $(that.element).find('.fastBackward').hide(); - } - if (!that.options.showBackwardButton) { - $(that.element).find('.backward').hide(); - } - if (!that.options.showForwardButton) { - $(that.element).find('.forward').hide(); - } - if (!that.options.showFastForwardButton) { - $(that.element).find('.fastForward').hide(); - } - if (!that.options.showShortcuts) { - $(that.element).find('.keyboardShortcuts').hide(); - } - if (!that.options.showAnnotationTime) { - $(that.element).find('.annotationTime').hide(); - } - if (!that.options.showLogoutButton) { - $(that.element).find('.logout').hide(); - } - if (!that.options.showInputPanelContainer) { - $(that.element).parents('.annotator-edf').find('.input-panel-container').hide(); - $(that.element).find('.mark-assignment-as-completed').show(); - that._updateMarkAssignmentAsCompletedButtonState(); - } - if (!that._isHITModeEnabled()) { - $(that.element).find('.progress').hide(); - } - $(that.element).css({ - marginTop: that.options.marginTop, - marginBottom: that.options.marginBottom, + _adaptContent: function () { + var that = this; + if (!that.options.showChannelGainAdjustmentButtons) { + $(".adjustment_buttons").hide(); + } + if (!that.options.showArtifactButtons) { + $(".artifact_panel").hide(); + } + if (!that.options.showSleepStageButtons) { + $(".sleep_stage_panel").hide(); + } + if (!that.options.showNavigationButtons) { + $(".navigation_panel").hide(); + } + if (!that.options.showBackToLastActiveWindowButton) { + $(".backToLastActiveWindow").hide(); + } + if (!that.options.showBookmarkCurrentPageButton) { + $(".bookmarkCurrentPage").hide(); + } + if (!that._isArbitrating()) { + $(".jumpToLastDisagreementWindow").hide(); + $(".jumpToNextDisagreementWindow").hide(); + } + if (!that.options.showFastBackwardButton) { + $(".fastBackward").hide(); + } + if (!that.options.showBackwardButton) { + $(".backward").hide(); + } + if (!that.options.showForwardButton) { + $(".forward").hide(); + } + if (!that.options.showFastForwardButton) { + $(".fastForward").hide(); + } + if (!that.options.showShortcuts) { + $(".keyboardShortcuts").hide(); + } + if (!that.options.showAnnotationTime) { + $(".annotationTime").hide(); + } + if (!that.options.showLogoutButton) { + $(".logout").hide(); + } + if (!that.options.showInputPanelContainer) { + $(that.element) + .parents(".annotator-edf") + .find(".input-panel-container") + .hide(); + // $(".mark-assignment-as-completed").show(); + $(".mark-assignment-as-completed").hide(); + + that._updateMarkAssignmentAsCompletedButtonState(); + } + if (!that._isHITModeEnabled()) { + $(".progress").hide(); + } + $(that.element).css({ + marginTop: that.options.marginTop, + marginBottom: that.options.marginBottom, + }); + }, + + _updateMarkAssignmentAsCompletedButtonState: function () { + var that = this; + $(that.element) + .find(".mark-assignment-as-completed") + .prop( + "disabled", + !that.options.context.assignment.canBeMarkedAsCompleted({ + reactive: false, }) - }, - - _updateMarkAssignmentAsCompletedButtonState: function() { - var that = this; - $(that.element).find('.mark-assignment-as-completed').prop('disabled', !that.options.context.assignment.canBeMarkedAsCompleted({ reactive: false })); - }, - - _forcePlayTrainingVideo: function() { - var that = this; - var videoBox = bootbox.dialog({ - title: 'Training Video (PLEASE TURN UP YOUR SOUND VOLUME)', - onEscape: false, - backdrop: false, - closeButton: false, - animate: true, - message: '
', - size: 'large', - }); - videoBox.appendTo(that.element); - videoBox.css({ - backgroundColor: 'rgba(0, 0, 0, 1)', - zIndex: 999999, - }); - if (that.options.trainingVideo.blockInteraction) { - videoBox.find('.interaction-blocker').css({ - position: 'fixed', - width: '100%', - height: '100%', - left: 0, - top: 0, - }); - } - var videoContainer = videoBox.find('.training-video'); - var videoId = that.options.trainingVideo.vimeoId; - var aspectRatio = 513 / 287 - var width = Math.round(videoContainer.width()); - var height = Math.round(width / aspectRatio); - var playerId = that._getUUID(); - $.getJSON('http://www.vimeo.com/api/oembed.json?url=' + encodeURIComponent('http://vimeo.com/' + videoId) + '&title=0&byline=0&portrait=0&badge=0&loop=0&autoplay=1&width=' + width + '&height=' + height + '&api=1&player_id=' + playerId + '&callback=?', function(data) { - var playerIFrame = $(data.html).attr('id', playerId).appendTo(videoContainer); - var player = $f(playerIFrame[0]); - player.addEvent('ready', function() { - player.addEvent('finish',function() { - videoBox.remove(); - }); - }); + ); + }, + + _forcePlayTrainingVideo: function () { + var that = this; + var videoBox = bootbox.dialog({ + title: "Training Video (PLEASE TURN UP YOUR SOUND VOLUME)", + onEscape: false, + backdrop: false, + closeButton: false, + animate: true, + message: + '
', + size: "large", + }); + videoBox.appendTo(that.element); + videoBox.css({ + backgroundColor: "rgba(0, 0, 0, 1)", + zIndex: 999999, + }); + if (that.options.trainingVideo.blockInteraction) { + videoBox.find(".interaction-blocker").css({ + position: "fixed", + width: "100%", + height: "100%", + left: 0, + top: 0, + }); + } + var videoContainer = videoBox.find(".training-video"); + var videoId = that.options.trainingVideo.vimeoId; + var aspectRatio = 513 / 287; + var width = Math.round(videoContainer.width()); + var height = Math.round(width / aspectRatio); + var playerId = that._getUUID(); + $.getJSON( + "http://www.vimeo.com/api/oembed.json?url=" + + encodeURIComponent("http://vimeo.com/" + videoId) + + "&title=0&byline=0&portrait=0&badge=0&loop=0&autoplay=1&width=" + + width + + "&height=" + + height + + "&api=1&player_id=" + + playerId + + "&callback=?", + function (data) { + var playerIFrame = $(data.html) + .attr("id", playerId) + .appendTo(videoContainer); + var player = $f(playerIFrame[0]); + player.addEvent("ready", function () { + player.addEvent("finish", function () { + videoBox.remove(); + }); }); - }, - - _showConsentForm: function() { - var that = this; - var confirmationCodeInfo = ''; - if (that.options.showConfirmationCode && that.options.confirmationCode) { - confirmationCodeInfo = '. For the payment to be processed correctly you need to enter the confirmation code presented to you at the end of the task into the corresponding input field in the instructions panel on Mechanical Turk'; - } - bootbox.dialog({ - onEscape: false, - backdrop: false, - closeButton: false, - animate: true, - title: 'Information Consent', - message: ' \ + } + ); + }, + + _showConsentForm: function () { + var that = this; + var confirmationCodeInfo = ""; + if (that.options.showConfirmationCode && that.options.confirmationCode) { + confirmationCodeInfo = + ". For the payment to be processed correctly you need to enter the confirmation code presented to you at the end of the task into the corresponding input field in the instructions panel on Mechanical Turk"; + } + bootbox + .dialog({ + onEscape: false, + backdrop: false, + closeButton: false, + animate: true, + title: "Information Consent", + message: + ' \
\ You are invited to participate in a research study conducted by Mike Schaekermann under the supervision of Professor Edith Law of the University of Waterloo, Canada. The objectives of the research study are to develop a low cost crowdsourcing system for EEG analysis for use in the third world.

\ - If you decide to participate, you will be asked to complete a 20-30 minute online EEG analysis task, as described on the task listing. Participation in this study is voluntary. You may decline to answer any questions that you do not wish to answer and you can withdraw your participation at any time by closing this browser tab or window. You will be paid $' + that.options.payment.toFixed(2) + ' upon completion of the task' + confirmationCodeInfo + '. Unfortunately we are unable to pay participants who do not complete the task. There are no known or anticipated risks from participating in this study.

\ + If you decide to participate, you will be asked to complete a 20-30 minute online EEG analysis task, as described on the task listing. Participation in this study is voluntary. You may decline to answer any questions that you do not wish to answer and you can withdraw your participation at any time by closing this browser tab or window. You will be paid $' + + that.options.payment.toFixed(2) + + " upon completion of the task" + + confirmationCodeInfo + + ". Unfortunately we are unable to pay participants who do not complete the task. There are no known or anticipated risks from participating in this study.

\ It is important for you to know that any information that you provide will be confidential. All of the data will be summarized and no individual could be identified from these summarized results. Furthermore, the web site is programmed to collect responses alone and will not collect any information that could potentially identify you (such as machine identifiers). The data collected from this study will be maintained on a password-protected computer database in a restricted access area of the university. As well, the data will be electronically archived after completion of the study and maintained for eight years and then erased.

\ This survey uses Mechanical Turk which is a United States of America company. Consequently, USA authorities under provisions of the Patriot Act may access this survey data. If you prefer not to submit your data through Mechanical Turk, please do not participate.

\ Note that the remuneration you receive may be taxable income. You are responsible for reporting this income for tax purposes. Should you have any questions about the study, please contact either Mike Schaekermann (mschaeke@uwaterloo.ca) or Edith Law (edith.law@uwaterloo.ca). Further, if you would like to receive a copy of the results of this study, please contact either investigator.

\ I would like to assure you that this study has been reviewed and received ethics clearance through a University of Waterloo Research Ethics Committee. However, the final decision about participation is yours. Should you have any comments or concerns resulting about your participation in this study, please contact Dr. Maureen Nummelin in the Office of Research Ethics at 1-519-888-4567, Ext. 36005 or maureen.nummelin@uwaterloo.ca.

\
\ - ', - buttons: { - consent: { - label: 'I understand and accept the participant consent agreement', - className: 'btn-success', - } - } - }).css({ - zIndex: 99999, - }).appendTo(that.element); - }, - - _setVisibilityStatusForInfoPanel: function(isVisible) { - var that = this; - const setVisibilityStatus = that.options.setVisibilityStatusForInfoPanel; - if (!setVisibilityStatus) return; - setVisibilityStatus(isVisible); - }, - - _showInfoPanel: function() { - var that = this; - that._setVisibilityStatusForInfoPanel(true); - }, - - _hideInfoPanel: function() { - var that = this; - that._setVisibilityStatusForInfoPanel(false); - }, - - _toggleInfoPanel: function() { - var that = this; - const toggle = that.options.toggleInfoPanel; - if (!toggle) return; - toggle(); - }, - - _isHITModeEnabled: function() { - var that = this; - return ( - that._isVisibleRegionDefined() - && that.options.visibleRegion.hitModeEnabled - ); - }, - - _isVisibleRegionDefined: function() { - var that = this; - return ( - that.options.visibleRegion.start !== undefined - && that.options.visibleRegion.end !== undefined - ); - }, - - _setupHITMode: function() { - var that = this; - if (!that._isHITModeEnabled()) return; - - that.options.showBackToLastActiveWindowButton = false; - that.options.showBookmarkCurrentPageButton = false; - that.options.showFastBackwardButton = false; - that.options.showBackwardButton = false; - that.options.showForwardButton = false; - that.options.showFastForwardButton = false; - that.options.showShortcuts = false; - that.options.showAnnotationTime = false; - - $(that.element).find('.graph_footer .middle').append(' \ + ", + buttons: { + consent: { + label: "I understand and accept the participant consent agreement", + className: "btn-success", + }, + }, + }) + .css({ + zIndex: 99999, + }) + .appendTo(that.element); + }, + + _setVisibilityStatusForInfoPanel: function (isVisible) { + var that = this; + const setVisibilityStatus = that.options.setVisibilityStatusForInfoPanel; + if (!setVisibilityStatus) return; + setVisibilityStatus(isVisible); + }, + + _showInfoPanel: function () { + var that = this; + that._setVisibilityStatusForInfoPanel(true); + }, + + _hideInfoPanel: function () { + var that = this; + that._setVisibilityStatusForInfoPanel(false); + }, + + _toggleInfoPanel: function () { + var that = this; + const toggle = that.options.toggleInfoPanel; + if (!toggle) return; + toggle(); + }, + + _isHITModeEnabled: function () { + var that = this; + return ( + that._isVisibleRegionDefined() && + that.options.visibleRegion.hitModeEnabled + ); + }, + + _isVisibleRegionDefined: function () { + var that = this; + return ( + that.options.visibleRegion.start !== undefined && + that.options.visibleRegion.end !== undefined + ); + }, + + _setupHITMode: function () { + var that = this; + if (!that._isHITModeEnabled()) return; + + that.options.showBackToLastActiveWindowButton = false; + that.options.showBookmarkCurrentPageButton = false; + that.options.showFastBackwardButton = false; + that.options.showBackwardButton = false; + that.options.showForwardButton = false; + that.options.showFastForwardButton = false; + that.options.showShortcuts = false; + that.options.showAnnotationTime = false; + + $(that.element) + .find(".graph_footer .middle") + .append( + ' \ \ \ \ - '); - - $(that.element).find('.submit-annotations').click(function () { - that._blockGraphInteraction(); - if (that._isCurrentWindowTrainingWindow()) { - that._revealCorrectAnnotations(); - } - // log this window as complete and - // set bookmark to next window so that - // on page load, the user cannot change - // any annotations made before submitting - that._saveUserEventWindowComplete(); - that._savePreferences({ startTime: that.vars.currentWindowStart + that.options.windowSizeInSeconds }) - $(that.element).find('.submit-annotations').prop('disabled', true); - $(that.element).find('.next-window').prop('disabled', false); + ' + ); + + $(that.element) + .find(".submit-annotations") + .click(function () { + that._blockGraphInteraction(); + if (that._isCurrentWindowTrainingWindow()) { + that._revealCorrectAnnotations(); + } + // log this window as complete and + // set bookmark to next window so that + // on page load, the user cannot change + // any annotations made before submitting + that._saveUserEventWindowComplete(); + that._savePreferences({ + startTime: + that.vars.currentWindowStart + that.vars.xAxisScaleInSeconds, }); - - $(that.element).find('.next-window').click(function () { - $(that.element).find('.no-features').prop('disabled', false); - $(that.element).find('.submit-features').prop('disabled', true); - $(that.element).find('.next-window').prop('disabled', true); - if (that._isCurrentWindowLastTrainingWindow() && !that._isTrainingOnly()) { - bootbox.alert({ - closeButton: false, - title: 'End of the Training Phase', - message: 'You just completed the last window of the training phase. That means that, from now on, you will not be able to see the correct answer after submitting yours any longer. The examples panel below, however, will stay visible throughout the entire task. Hopefully, the training phase helped you learn more about the signal pattern we are looking for!', - callback: function() { - that._shiftChart(1); - that._unblockGraphInteraction(); - } - }).appendTo(that.element); - } - else { + $(that.element).find(".submit-annotations").prop("disabled", true); + $(".next-window").prop("disabled", false); + }); + + $(that.element) + .find(".next-window") + .click(function () { + $(".no-features").prop("disabled", false); + $(".submit-features").prop("disabled", true); + $(".next-window").prop("disabled", true); + if ( + that._isCurrentWindowLastTrainingWindow() && + !that._isTrainingOnly() + ) { + bootbox + .alert({ + closeButton: false, + title: "End of the Training Phase", + message: + "You just completed the last window of the training phase. That means that, from now on, you will not be able to see the correct answer after submitting yours any longer. The examples panel below, however, will stay visible throughout the entire task. Hopefully, the training phase helped you learn more about the signal pattern we are looking for!", + callback: function () { that._shiftChart(1); that._unblockGraphInteraction(); - } - }); - - that._fetchOptionsFromURLParameter(); - }, - - _getCurrentWindowIndexInVisibleRegion: function() { - var that = this; - if (!that._isHITModeEnabled()) return; - var windowIndex = Math.floor((that.vars.currentWindowStart - that.options.visibleRegion.start) / that.options.windowSizeInSeconds); - return windowIndex; - }, - - _getNumberOfTrainingWindows: function() { - var that = this; - var training = that.options.visibleRegion.training; - if (!that._isTrainingEnabled()) { - return 0; - } - if (training.numberOfInitialWindowsUsedForTraining > 0) { - return training.numberOfInitialWindowsUsedForTraining; - } - return that._getSpecifiedTrainingWindows().length; - }, - - _areTrainingWindowsSpecified: function() { - var that = this; - that._getSpecifiedTrainingWindows(); - return ( - that.vars.specifiedTrainingWindows !== undefined - && that.vars.specifiedTrainingWindows.length > 0 - ); - }, - - _getCurrentTrainingWindow: function() { - var that = this; - if (!that._areTrainingWindowsSpecified()) { - return; - } - var trainingWindows = that._getSpecifiedTrainingWindows(); - var currentIndex = that.vars.currentTrainingWindowIndex; - if (currentIndex > trainingWindows.length - 1) { - return; - } - var trainingWindow = trainingWindows[currentIndex]; - return trainingWindow; - }, - - _isCurrentWindowSpecifiedTrainingWindow: function() { - var that = this; - if (!that._areTrainingWindowsSpecified()) return false; - return that.vars.currentTrainingWindowIndex < that._getNumberOfTrainingWindows(); - }, - - _getSpecifiedTrainingWindows: function() { - var that = this; - if (that.vars.specifiedTrainingWindows) { - return that.vars.specifiedTrainingWindows; - } - var training = that.options.visibleRegion.training; - if (!that._isTrainingEnabled() || training.numberOfInitialWindowsUsedForTraining > 0) { - return []; - } - if (training.windows && training.windows.length > 0) { - that.vars.specifiedTrainingWindows = training.windows; - return that.vars.specifiedTrainingWindows; - } - var windows = []; - var featureOrder = that.options.features.order; - var featureOptions = that.options.features.options; - for (f = 0; f < featureOrder.length; ++f) { - var feature = featureOrder[f]; - var featureTrainingWindows = featureOptions[feature].training.windows; - if (featureTrainingWindows && featureTrainingWindows.length > 0) { - windows.push.apply(windows, featureTrainingWindows); - } - } - that.vars.specifiedTrainingWindows = windows; - return that.vars.specifiedTrainingWindows; - }, - - _isTrainingEnabled: function() { - var that = this; - return (that._isHITModeEnabled() && that.options.visibleRegion.training.enabled); - }, - - _isTrainingOnly: function() { - var that = this; - return that.options.visibleRegion.training.isTrainingOnly; - }, - - _isCurrentWindowTrainingWindow: function() { - var that = this; - if (!that._isHITModeEnabled()) return false; - return that._getWindowIndexForTraining() <= that._getNumberOfTrainingWindows() - 1; - }, - - _isCurrentWindowFirstTrainingWindow: function() { - var that = this; - if (!that._isHITModeEnabled()) return false; - return ( - that._getNumberOfTrainingWindows() > 0 - && that._getWindowIndexForTraining() === 0 - ); - }, - - _isCurrentWindowLastTrainingWindow: function() { - var that = this; - if (!that._isHITModeEnabled()) return false; - return that._getWindowIndexForTraining() == that._getNumberOfTrainingWindows() - 1; - }, - - _getWindowIndexForTraining: function() { - var that = this; - if (!that._isHITModeEnabled()) return false; - if (that._areTrainingWindowsSpecified()) { - return that.vars.currentTrainingWindowIndex; - } - else { - return that._getCurrentWindowIndexInVisibleRegion(); - } - }, - - _revealCorrectAnnotations: function() { - var that = this; - that._getAnnotations(that.vars.currentWindowRecording, that.vars.currentWindowStart, that.vars.currentWindowStart + that.options.windowSizeInSeconds, true); - }, - - _setupExamplesMode: function() { - var that = this; - var examples = that.options.features.examples; - - if (!examples || examples.length == 0) { - console.log('There are no examples for this viewer.'); - return; - } - examples.sort(function(a, b) { - return a.start - b.start; - }); - var firstExample = examples[0]; - var recordingName = firstExample.recording; - var channelsDisplayed = [firstExample.channels_displayed[firstExample.channels]]; - that.options.recordingName = recordingName; - that.options.channelsDisplayed = channelsDisplayed; - that.options.graph.height = 200; - that.options.features.showUserAnnotations = false; - that.options.features.order = [ firstExample.type ]; - that.options.isReadOnly = true; - that.options.channelGainAdjustmentEnabled = false; - that.options.showChannelGainAdjustmentButtons = false; - that.options.keyboardInputEnabled = false; - that.options.showArtifactButtons = false; - that.options.showNavigationButtons = false; - that.options.showReferenceLines = false; - that.options.features.cheatSheetsEnabled = true; - that.options.features.openCheatSheetOnPageLoad = true; - that.options.showTimeLabels = false; - that._fetchOptionsFromURLParameter(); - - $(that.element).find('.button_container').prepend('Examples for:'); - $(that.element).find('.button_container').append(' \ + }, + }) + .appendTo(that.element); + } else { + that._shiftChart(1); + that._unblockGraphInteraction(); + } + }); + + that._fetchOptionsFromURLParameter(); + }, + + _getCurrentWindowIndexInVisibleRegion: function () { + var that = this; + if (!that._isHITModeEnabled()) return; + var windowIndex = Math.floor( + (that.vars.currentWindowStart - that.options.visibleRegion.start) / + that.vars.xAxisScaleInSeconds + ); + return windowIndex; + }, + + _getNumberOfTrainingWindows: function () { + var that = this; + var training = that.options.visibleRegion.training; + if (!that._isTrainingEnabled()) { + return 0; + } + if (training.numberOfInitialWindowsUsedForTraining > 0) { + return training.numberOfInitialWindowsUsedForTraining; + } + return that._getSpecifiedTrainingWindows().length; + }, + + _areTrainingWindowsSpecified: function () { + var that = this; + that._getSpecifiedTrainingWindows(); + return ( + that.vars.specifiedTrainingWindows !== undefined && + that.vars.specifiedTrainingWindows.length > 0 + ); + }, + + _getCurrentTrainingWindow: function () { + var that = this; + if (!that._areTrainingWindowsSpecified()) { + return; + } + var trainingWindows = that._getSpecifiedTrainingWindows(); + var currentIndex = that.vars.currentTrainingWindowIndex; + if (currentIndex > trainingWindows.length - 1) { + return; + } + var trainingWindow = trainingWindows[currentIndex]; + return trainingWindow; + }, + + _isCurrentWindowSpecifiedTrainingWindow: function () { + var that = this; + if (!that._areTrainingWindowsSpecified()) return false; + return ( + that.vars.currentTrainingWindowIndex < that._getNumberOfTrainingWindows() + ); + }, + + _getSpecifiedTrainingWindows: function () { + var that = this; + if (that.vars.specifiedTrainingWindows) { + return that.vars.specifiedTrainingWindows; + } + var training = that.options.visibleRegion.training; + if ( + !that._isTrainingEnabled() || + training.numberOfInitialWindowsUsedForTraining > 0 + ) { + return []; + } + if (training.windows && training.windows.length > 0) { + that.vars.specifiedTrainingWindows = training.windows; + return that.vars.specifiedTrainingWindows; + } + var windows = []; + var featureOrder = that.options.features.order; + var featureOptions = that.options.features.options; + for (f = 0; f < featureOrder.length; ++f) { + var feature = featureOrder[f]; + var featureTrainingWindows = featureOptions[feature].training.windows; + if (featureTrainingWindows && featureTrainingWindows.length > 0) { + windows.push.apply(windows, featureTrainingWindows); + } + } + that.vars.specifiedTrainingWindows = windows; + return that.vars.specifiedTrainingWindows; + }, + + _isTrainingEnabled: function () { + var that = this; + return ( + that._isHITModeEnabled() && that.options.visibleRegion.training.enabled + ); + }, + + _isTrainingOnly: function () { + var that = this; + return that.options.visibleRegion.training.isTrainingOnly; + }, + + _isCurrentWindowTrainingWindow: function () { + var that = this; + if (!that._isHITModeEnabled()) return false; + return ( + that._getWindowIndexForTraining() <= + that._getNumberOfTrainingWindows() - 1 + ); + }, + + _isCurrentWindowFirstTrainingWindow: function () { + var that = this; + if (!that._isHITModeEnabled()) return false; + return ( + that._getNumberOfTrainingWindows() > 0 && + that._getWindowIndexForTraining() === 0 + ); + }, + + _isCurrentWindowLastTrainingWindow: function () { + var that = this; + if (!that._isHITModeEnabled()) return false; + return ( + that._getWindowIndexForTraining() == + that._getNumberOfTrainingWindows() - 1 + ); + }, + + _getWindowIndexForTraining: function () { + var that = this; + if (!that._isHITModeEnabled()) return false; + if (that._areTrainingWindowsSpecified()) { + return that.vars.currentTrainingWindowIndex; + } else { + return that._getCurrentWindowIndexInVisibleRegion(); + } + }, + + _revealCorrectAnnotations: function () { + var that = this; + that._getAnnotations( + that.vars.currentWindowRecording, + that.vars.currentWindowStart, + that.vars.currentWindowStart + that.vars.xAxisScaleInSeconds, + true + ); + }, + + _setupExamplesMode: function () { + var that = this; + var examples = that.options.features.examples; + + if (!examples || examples.length == 0) { + //console.log('There are no examples for this viewer.'); + return; + } + examples.sort(function (a, b) { + return a.start - b.start; + }); + var firstExample = examples[0]; + var recordingName = firstExample.recording; + var channelsDisplayed = [ + firstExample.channels_displayed[firstExample.channels], + ]; + let id = Data.findOne({ path: recordingName })._id; + that.options.allRecordings = [{ _id: id, path: recordingName }]; + that.options.channelsDisplayed = channelsDisplayed; + that.options.graph.height = 200; + that.options.features.showUserAnnotations = false; + that.options.features.order = [firstExample.type]; + that.options.isReadOnly = true; + that.options.channelGainAdjustmentEnabled = true; + that.options.showChannelGainAdjustmentButtons = true; + that.options.keyboardInputEnabled = false; + that.options.showArtifactButtons = false; + that.options.showNavigationButtons = false; + that.options.showReferenceLines = false; + that.options.features.cheatSheetsEnabled = true; + that.options.features.openCheatSheetOnPageLoad = true; + that.options.showTimeLabels = true; + that._fetchOptionsFromURLParameter(); + + $(that.element) + .find(".button_container") + .prepend('Examples for:'); + $(that.element) + .find(".button_container") + .append( + ' \
\ \
\ - '); - - if (!that.options.features.cheatSheetsEnabled) { - $(that.element).find('.open-cheat-sheet').remove(); - } - else { - $(that.element).find('.open-cheat-sheet').click(function() { - that._saveUserEvent('open_cheat_sheet', { - feature: firstExample.type, - }); - openCheatSheet(); - }); - if (that.options.features.openCheatSheetOnPageLoad) { - $(that.element).hover(function() { - if (that.vars.cheatSheetOpenedBefore) return; - openCheatSheet(); - }); - } - function openCheatSheet() { - that.vars.cheatSheetOpenedBefore = true; - bootbox.dialog({ - title: 'PLEASE READ CAREFULLY', - message: '', - buttons: { - close: { - label: 'Close', - } - }, - size: 'large', - }).appendTo(that.element); - } - } - - that.vars.currentExampleIndex = 0; - that.options.startTime = that._getWindowStartForTime(examples[that.vars.currentExampleIndex].start); + ' + ); + + if (!that.options.features.cheatSheetsEnabled) { + $(".open-cheat-sheet").remove(); + } else { + $(that.element) + .find(".open-cheat-sheet") + .click(function () { + that._saveUserEvent("open_cheat_sheet", { + feature: firstExample.type, + }); + openCheatSheet(); + }); + if (that.options.features.openCheatSheetOnPageLoad) { + $(that.element).hover(function () { + if (that.vars.cheatSheetOpenedBefore) return; + openCheatSheet(); + }); + } + function openCheatSheet() { + that.vars.cheatSheetOpenedBefore = true; + bootbox + .dialog({ + title: "PLEASE READ CAREFULLY", + message: + '', + buttons: { + close: { + label: "Close", + }, + }, + size: "large", + }) + .appendTo(that.element); + } + } - $(that.element).find('.next-example').click(function() { - that._saveUserEvent('view_example_window', { - feature: firstExample.type, - direction: 'next', - }); - that._clearScrollThroughExamplesInterval(); - that._showNextExample(1); + that.vars.currentExampleIndex = 0; + that.options.startTime = that._getWindowStartForTime( + examples[that.vars.currentExampleIndex].start + ); + + $(that.element) + .find(".next-example") + .click(function () { + that._saveUserEvent("view_example_window", { + feature: firstExample.type, + direction: "next", }); - $(that.element).find('.previous-example').click(function() { - that._saveUserEvent('view_example_window', { - feature: firstExample.type, - direction: 'previous', - }); - that._clearScrollThroughExamplesInterval(); - that._showNextExample(-1); + that._clearScrollThroughExamplesInterval(); + that._showNextExample(1); + }); + $(that.element) + .find(".previous-example") + .click(function () { + that._saveUserEvent("view_example_window", { + feature: firstExample.type, + direction: "previous", }); - if (that.options.features.scrollThroughExamplesAutomatically) { - $(that.element).hover(function() { - if (that.vars.scrollThroughExamplesIntervalId !== undefined) return; - that.vars.scrollThroughExamplesIntervalId = window.setInterval(function() { - that._showNextExample(1); - }, that.options.features.scrollThroughExamplesSpeedInSeconds * 1000); - }); - } + that._clearScrollThroughExamplesInterval(); + that._showNextExample(-1); + }); + if (that.options.features.scrollThroughExamplesAutomatically) { + $(that.element).hover(function () { + if (that.vars.scrollThroughExamplesIntervalId !== undefined) return; + that.vars.scrollThroughExamplesIntervalId = window.setInterval( + function () { + that._showNextExample(1); + }, + that.options.features.scrollThroughExamplesSpeedInSeconds * 1000 + ); + }); + } - var wrapper = $('
').addClass('well'); - $(that.element).children().wrap(wrapper); - that.options.graph.backgroundColor = 'none'; + var wrapper = $("
").addClass("well"); + $(that.element).children().wrap(wrapper); + that.options.graph.backgroundColor = "none"; + + that._setup(); + }, + + _clearScrollThroughExamplesInterval: function () { + var that = this; + if ( + that.vars.scrollThroughExamplesIntervalId !== undefined && + that.vars.scrollThroughExamplesIntervalId !== false + ) { + window.clearInterval(that.vars.scrollThroughExamplesIntervalId); + that.vars.scrollThroughExamplesIntervalId = false; + } + }, + + _showNextExample: function (stepLength) { + var that = this; + do { + that.vars.currentExampleIndex += stepLength; + that.vars.currentExampleIndex %= that.options.features.examples.length; + while (that.vars.currentExampleIndex < 0) { + that.vars.currentExampleIndex += that.options.features.examples.length; + } + var example = + that.options.features.examples[that.vars.currentExampleIndex]; + var nextWindowStart = that._getWindowStartForTime(example.start); + } while (nextWindowStart == that.vars.currentWindowStart); + that._switchToWindow( + that.options.allRecordings, + nextWindowStart, + that.vars.xAxisScaleInSeconds + ); + }, + + _getMontages: function () { + // get the name of all montages, which is defined in annotatorConfig under task collection + var that = this; + if (that.options.channelsDisplayed instanceof Array) { + return; + } + return Object.keys(that.options.channelsDisplayed); + }, - that._setup(); - }, + _getChannelsDisplayed: function (montage) { + // return the channel to display given the montage name + var that = this; - _clearScrollThroughExamplesInterval: function() { - var that = this; - if ( - that.vars.scrollThroughExamplesIntervalId !== undefined - && that.vars.scrollThroughExamplesIntervalId !== false - ) { - window.clearInterval(that.vars.scrollThroughExamplesIntervalId); - that.vars.scrollThroughExamplesIntervalId = false; + if (that.options.channelsDisplayed instanceof Array) { + return that.options.channelsDisplayed; + } + if (montage) { + return that.options.channelsDisplayed[montage]; + } + if ( + that.vars.currentMontage && + that.options.channelsDisplayed[that.vars.currentMontage] + ) { + return that.options.channelsDisplayed[that.vars.currentMontage]; + } else if ( + that.options.defaultMontage && + that.options.channelsDisplayed[that.options.defaultMontage] + ) { + return that.options.channelsDisplayed[that.options.defaultMontage]; + } + return that.options.channelsDisplayed[that._getMontages()[0]]; + }, + + _getChannelGains: function (montage) { + // return the gain of each channel given the montage name + var that = this; + if (that.vars.channelGains instanceof Array) { + return that.vars.channelGains; + } + if (montage) { + return that.vars.channelGains[montage]; + } + if ( + that.vars.currentMontage && + that.vars.channelGains[that.vars.currentMontage] + ) { + return that.vars.channelGains[that.vars.currentMontage]; + } else if ( + that.options.defaultMontage && + that.vars.channelGains[that.options.defaultMontage] + ) { + return that.vars.channelGains[that.options.defaultMontage]; + } + return that.vars.channelGains[that._getMontages()[0]]; + }, + + _getGainForChannelIndex: function (index, montage) { + var that = this; + return that._getChannelGains(montage)[index]; + }, + + _getOffsetForChannelIndexPostScale: function (index) { + var that = this; + var offset = + (that.vars.currentWindowData.channels.length - 1 - index) * + that.options.graph.channelSpacing; + return offset; + }, + + _getWindowStartForTime: function (time) { + var that = this; + var windowStart = Math.floor(time / that.vars.xAxisScaleInSeconds); + windowStart *= that.vars.xAxisScaleInSeconds; + return windowStart; + }, + + _setup: function () { + var that = this; + //console.log("_setup.that:", that); + that._adaptContent(); + that._setupTimer(); + that._setupFeaturePanel(); + that._setupNavigationPanel(); + that._setupArtifactPanel(); + that._setupSleepStagePanel(); + that._setupTimeSyncPanel(); + that._setupTrainingPhase(); + that._setupArbitration(); + that + ._getRecordingMetadata() + .then(that._setupDownsampledRecording) // downsample the recording if loading for the first time + .then(() => { + if (that.options.preloadEntireRecording) { + //console.log("preloadEntireRecording", that.options.preloadEntireRecording); + that._preloadEntireRecording(function () { + that._getUserStatus(); + }); + } else { + that._getUserStatus(); } - }, + }) + .catch((error) => console.error(error)); + + }, - _showNextExample: function(stepLength) { - var that = this; - do { - that.vars.currentExampleIndex += stepLength; - that.vars.currentExampleIndex %= that.options.features.examples.length; - while (that.vars.currentExampleIndex < 0) { - that.vars.currentExampleIndex += that.options.features.examples.length; - } - var example = that.options.features.examples[that.vars.currentExampleIndex]; - var nextWindowStart = that._getWindowStartForTime(example.start); - } while (nextWindowStart == that.vars.currentWindowStart); - that._switchToWindow(that.options.recordingName, nextWindowStart, that.options.windowSizeInSeconds); - }, + _getUrlParameter: function (sParam) { + var sPageURL = decodeURIComponent(window.location.search.substring(1)), + sURLVariables = sPageURL.split("&"), + sParameterName, + i; - _getMontages: function() { - var that = this; - if (that.options.channelsDisplayed instanceof Array) { - return; - } - return Object.keys(that.options.channelsDisplayed); - }, - - _getChannelsDisplayed: function(montage) { - var that = this; - if (that.options.channelsDisplayed instanceof Array) { - return that.options.channelsDisplayed; - } - if (montage) { - return that.options.channelsDisplayed[montage]; - } - if (that.vars.currentMontage && that.options.channelsDisplayed[that.vars.currentMontage]) { - return that.options.channelsDisplayed[that.vars.currentMontage]; - } - else if (that.options.defaultMontage && that.options.channelsDisplayed[that.options.defaultMontage]) { - return that.options.channelsDisplayed[that.options.defaultMontage]; - } - return that.options.channelsDisplayed[that._getMontages()[0]]; - }, - - _getChannelGains: function(montage) { - var that = this; - if (that.vars.channelGains instanceof Array) { - return that.vars.channelGains; - } - if (montage) { - return that.vars.channelGains[montage]; - } - if (that.vars.currentMontage && that.vars.channelGains[that.vars.currentMontage]) { - return that.vars.channelGains[that.vars.currentMontage]; - } - else if (that.options.defaultMontage && that.vars.channelGains[that.options.defaultMontage]) { - return that.vars.channelGains[that.options.defaultMontage]; - } - return that.vars.channelGains[that._getMontages()[0]]; - }, - - _getGainForChannelIndex: function(index, montage) { - var that = this; - return that._getChannelGains(montage)[index]; - }, - - _getOffsetForChannelIndexPostScale: function(index) { - var that = this; - var offset = (that.vars.currentWindowData.channels.length - 1 - index) * that.options.graph.channelSpacing; - return offset; - }, + for (i = 0; i < sURLVariables.length; i++) { + sParameterName = sURLVariables[i].split("="); - _getWindowStartForTime: function(time) { - var that = this; - var windowStart = Math.floor(time / that.options.windowSizeInSeconds); - windowStart *= that.options.windowSizeInSeconds; - return windowStart; - }, - - _setup: function() { - var that = this; - that._adaptContent(); - that._setupTimer(); - that._setupFeaturePanel(); - that._setupNavigationPanel(); - that._setupArtifactPanel(); - that._setupSleepStagePanel(); - that._setupTrainingPhase(); - that._setupArbitration(); - that._getRecordingMetadata(function(metadata) { - that.vars.recordingMetadata = metadata; - if (that.options.preloadEntireRecording) { - that._preloadEntireRecording(function() { - that._getUserStatus(); - }); - } - else { - that._getUserStatus(); - } + if (sParameterName[0] === sParam) { + return sParameterName[1] === undefined ? true : sParameterName[1]; + } + } + }, + + _getPreClassificationAnnotations: function (filter, callback) { + var that = this; + filter = filter || {}; + return that.options.context.assignment.preClassificationAnnotations( + filter, + { sort: { "value.position.start": 1 } } + ); + }, + + _setupExperiment: function () { + var that = this; + if (!that.options.experiment.running) return; + var temporalContextHint; + switch (that.options.experiment.current_condition.temporal_context) { + case "continuous": + temporalContextHint = "Continuous sequence of windows"; + break; + case "shuffled": + temporalContextHint = "Shuffled sequence of windows"; + break; + } + var hint = $("

").html(temporalContextHint); + $(that.element).find(".experiment_container .hints_container").append(hint); + }, + + _updateNavigationStatusForExperiment: function () { + var that = this; + if (!that.options.experiment.running) return; + var currentWindowIndex = + that.options.experiment.current_condition.current_window_index; + var conditionWindows = that.options.experiment.current_condition.windows; + var lastWindowIndex = conditionWindows.length - 1; + var windowsRemaining = lastWindowIndex - currentWindowIndex; + that._setForwardEnabledStatus(windowsRemaining >= 1); + if (windowsRemaining < 1) { + that._lastWindowReached(); + } + var fastForwardEnabled = windowsRemaining >= that.options.windowJumpSizeFastForwardBackward; + + + that._setFastForwardEnabledStatus( + fastForwardEnabled + ); + that._setBackwardEnabledStatus(currentWindowIndex >= 1); + that._setFastBackwardEnabledStatus( + currentWindowIndex >= that.options.windowJumpSizeFastForwardBackward + ); + if ( + that.options.experiment.current_condition.temporal_context == "shuffled" + ) { + that._setFastForwardEnabledStatus(false); + that._setFastBackwardEnabledStatus(false); + $(".fastForward").hide(); + $(".fastBackward").hide(); + } + }, + + _setupTimer: function () { + var that = this; + that.vars.totalAnnotationTimeSeconds = 0; + var preferences = {}; + if (that.options.context.preferences) { + preferences = that.options.context.preferences.annotatorConfig; + } + if (preferences.totalAnnotationTimeSeconds) { + that.vars.totalAnnotationTimeSeconds = parseFloat( + preferences.totalAnnotationTimeSeconds + ); + } + that.vars.lastAnnotationTime = that._getCurrentServerTimeMilliSeconds(); + if (preferences.lastAnnotationTime) { + that.vars.lastAnnotationTime = parseInt(preferences.lastAnnotationTime); + } + var timerContainer = $(".annotation-time-container"); + var timeContainer = $("").addClass("time form-control"); + timerContainer.append(timeContainer); + that.vars.annotationTimeContainer = timeContainer; + that._setTotalAnnotationTimeSeconds(that.vars.totalAnnotationTimeSeconds); + }, + + _setTotalAnnotationTimeSeconds: function (timeSeconds) { + var that = this; + that.vars.totalAnnotationTimeSeconds = timeSeconds; + if (!that.vars.annotationTimeContainer) { + return; + } + that.vars.annotationTimeContainer + .timer("remove") + .timer({ + seconds: that.vars.totalAnnotationTimeSeconds, + format: "%H:%M:%S", + }) + .timer("pause"); + }, + + _updateLastAnnotationTime: function () { + var that = this; + var currentTime = that._getCurrentServerTimeMilliSeconds(); + var timeDifferenceSeconds = + (currentTime - that.vars.lastAnnotationTime) / 1000; + that.vars.lastAnnotationTime = currentTime; + var preferencesUpdates = { + lastAnnotationTime: that.vars.lastAnnotationTime, + }; + if (timeDifferenceSeconds <= that.options.idleTimeThresholdSeconds) { + that.vars.totalAnnotationTimeSeconds += timeDifferenceSeconds; + that._setTotalAnnotationTimeSeconds(that.vars.totalAnnotationTimeSeconds); + preferencesUpdates.totalAnnotationTimeSeconds = + that.vars.totalAnnotationTimeSeconds; + } + that.vars.lastActiveWindowStart = that.vars.currentWindowStart; + that._savePreferences(preferencesUpdates); + }, + + _getCurrentServerTimeMilliSeconds: function () { + var today = new Date(); + var serverOffset = -5; + var date = new Date().getTime() + serverOffset * 3600 * 1000; + return date; + }, + + _setupMontageSelector: function () { + // montage selector should be removed if we are fixing channels to be displayed by its montage or alignment mode + var that = this; + if (!that._getMontages()) { + return; + } + var selectContainer = $("
").appendTo( + that.element.find(".montage_panel") + ); + var select = selectContainer.find("select"); + that._getMontages().forEach(function (montage) { + var selectedString = ""; + if (montage == that.vars.currentMontage) { + selectedString = ' selected="selected"'; + } + select.append( + '" + ); + }); + select.material_select(); + //console.log("_setupMontageSelector before change"); + select.change(function () { + //console.log("_setupMontageSelector onchange"); + that.vars.currentMontage = select.val(); + that._savePreferences({ + defaultMontage: that.vars.currentMontage, + }); + that._reinitChart(); + }); + //console.log("Finish _setupMontageSelector function"); + }, + + _setupFrequencyFilterSelector: function () { + var that = this; + var frequencyFilters = that.options.frequencyFilters || []; + frequencyFilters.forEach((frequencyFilter, f) => { + var filterSettings = frequencyFilter.options; + if (!filterSettings) { + return; + } + var selectContainer = $( + '
' + ).appendTo(that.element.find(".frequency_filter_panel")); + var select = selectContainer.find("select"); + + filterSettings.forEach(function (filterSetting) { + var selectedString = ""; + if (filterSetting.default) { + selectedString = ' selected="selected"'; + } + select.append( + '" + ); + }); + select.material_select(); + select.change(function () { + //console.log("freqFilter onchange"); + filterSettings.forEach(function (filterSetting) { + delete filterSetting.default; }); - }, - - _getUrlParameter: function(sParam) { - var sPageURL = decodeURIComponent(window.location.search.substring(1)), - sURLVariables = sPageURL.split('&'), - sParameterName, - i; - - for (i = 0; i < sURLVariables.length; i++) { - sParameterName = sURLVariables[i].split('='); - - if (sParameterName[0] === sParam) { - return sParameterName[1] === undefined ? true : sParameterName[1]; - } - } - }, - - _getPreClassificationAnnotations: function(filter, callback) { - var that = this; - filter = filter || {}; - return that.options.context.assignment.preClassificationAnnotations(filter, { sort: { 'value.position.start': 1 } }); - }, - - _setupExperiment: function() { - var that = this; - if (!that.options.experiment.running) return; - var temporalContextHint; - switch (that.options.experiment.current_condition.temporal_context) { - case 'continuous': - temporalContextHint = 'Continuous sequence of windows'; - break; - case 'shuffled': - temporalContextHint = 'Shuffled sequence of windows'; - break; - } - var hint = $('

').html(temporalContextHint); - $(that.element).find('.experiment_container .hints_container').append(hint); - }, - - _updateNavigationStatusForExperiment: function() { - var that = this; - if (!that.options.experiment.running) return; - var currentWindowIndex = that.options.experiment.current_condition.current_window_index; - var conditionWindows = that.options.experiment.current_condition.windows; - var lastWindowIndex = conditionWindows.length - 1; - var windowsRemaining = lastWindowIndex - currentWindowIndex; - that._setForwardEnabledStatus(windowsRemaining >= 1); - if (windowsRemaining < 1) { - that._lastWindowReached(); - } - that._setFastForwardEnabledStatus(windowsRemaining >= that.options.windowJumpSizeFastForwardBackward); - that._setBackwardEnabledStatus(currentWindowIndex >= 1); - that._setFastBackwardEnabledStatus(currentWindowIndex >= that.options.windowJumpSizeFastForwardBackward); - if (that.options.experiment.current_condition.temporal_context == 'shuffled') { - that._setFastForwardEnabledStatus(false); - that._setFastBackwardEnabledStatus(false); - $(that.element).find('.fastForward').hide(); - $(that.element).find('.fastBackward').hide(); - } - }, - - _setupTimer: function() { - var that = this; - that.vars.totalAnnotationTimeSeconds = 0 - var preferences = {}; - if (that.options.context.preferences) { - preferences = that.options.context.preferences.annotatorConfig; - } - if (preferences.totalAnnotationTimeSeconds) { - that.vars.totalAnnotationTimeSeconds = parseFloat(preferences.totalAnnotationTimeSeconds); - } - that.vars.lastAnnotationTime = that._getCurrentServerTimeMilliSeconds(); - if (preferences.lastAnnotationTime) { - that.vars.lastAnnotationTime = parseInt(preferences.lastAnnotationTime); - } - var timerContainer = $(that.element).find('.annotation-time-container'); - var timeContainer = $('').addClass('time form-control'); - timerContainer.append(timeContainer); - that.vars.annotationTimeContainer = timeContainer; - that._setTotalAnnotationTimeSeconds(that.vars.totalAnnotationTimeSeconds); - }, - - _setTotalAnnotationTimeSeconds: function(timeSeconds) { - var that = this; - that.vars.totalAnnotationTimeSeconds = timeSeconds; - if (!that.vars.annotationTimeContainer) { - return; + filterSettings[select.prop("selectedIndex")].default = true; + that._savePreferences({ + frequencyFilters: frequencyFilters, + }); + that.vars.frequencyFilters[f].selectedValue = select.val(); + that._reloadCurrentWindow(); + //console.log("freqFilter here"); + }); + select.change(); + }); + }, + + _setupAnnotationDisplayType: function () { + var that = this; + //that.vars.printedBox = true; + $(".frequency_filter_panel").after( + $('
') + ); + that.options.boxAnnotationUserSelection[0].options = []; + var temp = that.options.boxAnnotationUserSelection; + temp[0].options.push( + { + name: "Off", + value: "none", + default: true, + }, + { + name: "Show My", + value: "my", + } + ); + //Allow admins to see all annotations from differnt users, by adding them to dropdown + if (Roles.userIsInRole(Meteor.userId(), "admin")) { + temp[0].options.push({ name: "Show All", value: "all" }); + assign = Assignments.find( + { + task: that.options.context.task._id, + dataFiles: that.options.context.dataset.map((data) => data._id), + }, + { + sort: { updatedAt: -1 }, + } + ).fetch(); + + var users = assign[0].users; + var userNames = Meteor.users + .find({ _id: { $in: users } }, { username: 1, sort: { updatedAt: -1 } }) + .fetch() + .map((u) => u.username); + for (var i = 0; i < users.length; i++) { + var user = users[i]; + var username = userNames[i]; + if (user == Meteor.userId()) { + continue; + } + temp[0].options.push({ name: username, value: user }); + } + } + if (!that.vars.printedBox) { + that.vars.printedBox = true; + that.__setupAnnotationChoice(); + } + var selection = that.options.boxAnnotationUserSelection || []; + selection.forEach((boxAnnotation, t) => { + var boxAnnotationSettings = boxAnnotation.options; + var selectContainer = $( + '
' + ).appendTo(that.element.find(".user_selection_panel")); + var select = selectContainer.find("select"); + + boxAnnotationSettings.forEach(function (boxAnnotationSetting) { + var selectedString = ""; + if (boxAnnotationSetting.default) { + selectedString = ' selected="selected"'; + } + select.append( + '" + ); + if ( + boxAnnotationSetting.value == "my" && + Roles.userIsInRole(Meteor.userId(), "admin") + ) { + select.append( + '' + ); } - that.vars.annotationTimeContainer - .timer('remove') - .timer({ - seconds: that.vars.totalAnnotationTimeSeconds, - format: '%H:%M:%S' - }) - .timer('pause'); - }, + }); - _updateLastAnnotationTime: function() { - var that = this; - var currentTime = that._getCurrentServerTimeMilliSeconds(); - var timeDifferenceSeconds = (currentTime - that.vars.lastAnnotationTime) / 1000; - that.vars.lastAnnotationTime = currentTime; - var preferencesUpdates = { - lastAnnotationTime: that.vars.lastAnnotationTime - } - if (timeDifferenceSeconds <= that.options.idleTimeThresholdSeconds) { - that.vars.totalAnnotationTimeSeconds += timeDifferenceSeconds; - that._setTotalAnnotationTimeSeconds(that.vars.totalAnnotationTimeSeconds); - preferencesUpdates.totalAnnotationTimeSeconds = that.vars.totalAnnotationTimeSeconds; - } - that.vars.lastActiveWindowStart = that.vars.currentWindowStart; - that._savePreferences(preferencesUpdates); - }, + select.material_select(); + select.change(function () { + that.options.features.showAllBoxAnnotations = select.val(); + that.vars.annotationsLoaded = false; + that.vars.annotationsCache = []; - _getCurrentServerTimeMilliSeconds: function() { - var today = new Date(); - var serverOffset = -5; - var date = new Date().getTime() + serverOffset * 3600 * 1000; - return date; - }, + that._removeAnnotationBox(); - _setupMontageSelector: function() { - var that = this; - if (!that._getMontages()) { - return; - } - var selectContainer = $('
').appendTo(that.element.find('.montage_panel')); - var select = selectContainer.find('select'); - that._getMontages().forEach(function(montage) { - var selectedString = ''; - if (montage == that.vars.currentMontage) { - selectedString = ' selected="selected"'; - } - select.append(''); - }); - select.material_select(); - select.change(function() { - that.vars.currentMontage = select.val(); - that._savePreferences({ - defaultMontage: that.vars.currentMontage, - }) - that._reinitChart(); - }); - }, + that._refreshAnnotations(); + }); + select.change(); + }); + }, + + _setupAnnotationChoice: function () { + var that = this; + that.vars.printedBox = true; + + that.options.annotationType[0].options = []; + var temp = that.options.annotationType; + temp[0].options.push( + { + name: "Off", + value: "none", + default: true, + }, + // { + // name: "Start & End Point Annotation (All)", + // value: "sne", + // }, + { + name: "Change Point Annotation (All)", + value: "cpointall", + }, + { + name: "Change Point Annotation (Channel)", + value: "cpoint", + }, + { + name: "Box Annotation", + value: "box", + }, + ); + + var selection = that.options.annotationType || []; + selection.forEach((typeAnnotation, t) => { + var typeAnnotationSettings = typeAnnotation.options; + var selectContainer = $( + '
' + ).appendTo(that.element.find(".annotation_type_select_panel")); + + var select = selectContainer.find("select"); + typeAnnotationSettings.forEach(function (typeAnnotationSetting) { + var selectedString = ""; + if (typeAnnotationSetting.default) { + selectedString = ' selected="selected"'; + } + select.append( + '" + ); + }); - _setupFrequencyFilterSelector: function() { - var that = this; - var frequencyFilters = that.options.frequencyFilters || []; - frequencyFilters.forEach((frequencyFilter, f) => { - var filterSettings = frequencyFilter.options; - if (!filterSettings) { - return; - } - var selectContainer = $('
').appendTo(that.element.find('.frequency_filter_panel')); - var select = selectContainer.find('select'); - filterSettings.forEach(function(filterSetting) { - var selectedString = ''; - if (filterSetting.default) { - selectedString = ' selected="selected"'; - } - select.append(''); - }); - select.material_select(); - select.change(function() { - filterSettings.forEach(function(filterSetting) { - delete filterSetting.default; - }); - filterSettings[select.prop('selectedIndex')].default = true; - that._savePreferences({ - frequencyFilters: frequencyFilters, - }) - that.vars.frequencyFilters[f].selectedValue = select.val(); - that._reloadCurrentWindow(); - }); - select.change(); + select.material_select(); + // $(document).ready(function() { + select.change(function () { + that.options.features.annotationType = select.val(); + that._setupAnnotationInteraction(); + }); + // }); + select.change(); + }); + }, + + _setupXAxisScaleSelector: function () { + let that = this; + let timescales = that.options.xAxisTimescales || []; + timescales.forEach((timescaleSetting) => { + let selectContainer = $( + '
' + ).appendTo(that.element.find(".timescale_panel")); + let select = selectContainer.find("select"); + + let defaultOptionIndex = null; + timescaleSetting.options.forEach((timescale, t) => { + let selectedString = ""; + + if (timescale.default) { + selectedString = ' selected="selected"'; + defaultOptionIndex = t; + } + select.append( + `" + ); + }); + + select.material_select(); + select.change(function () { + //console.log("timescale onchange"); + if (defaultOptionIndex) + delete timescaleSetting.options[defaultOptionIndex].default; + timescaleSetting.options[select.prop("selectedIndex")].default = true; + that.vars.xAxisScaleInSeconds = +select.val(); + that._reloadCurrentWindow(); + //console.log("timescale here"); + }); + select.change(); + }); + }, + + _setupDownsampledRecording: function (that) { + // downsample all recordings in the assignment to lower resolution (sampling rate) for future usage + return new Promise((resolve, reject) => { + Meteor.call( + "setup.edf.downsampled", + that.options.allRecordings, + that.vars.recordingMetadata, + (error, results) => { + if (error) throw new Error("Cannot downsample EDF file\n" + error); + else resolve(); + } + ); + }); + }, + + _setupFeaturePanel: function () { + var that = this; + $('[data-toggle="popover"]').popover({ trigger: "hover" }); + + var firstFeature = that.options.features.order[0]; + that.vars.activeFeatureType = firstFeature; + + // for (var i = 0; i < that.options.features.order.length; i++) { + // var feature_key = that.options.features.order[i]; + // var feature_name = that.options.features.options[feature_key].name; + // var featureButton = $( + // '" + // ).data("annotation-type", feature_key); + // $(".feature_panel").append(featureButton); + // $( + // '" + // ).appendTo("head"); + // } + $(that.element) + .find(".feature") + .click(function (event) { + that._selectFeatureClass($(this)); + }); + $(that.element) + .find(".feature." + firstFeature) + .addClass("active") + .siblings() + .removeClass("active"); + }, + + _preloadEntireRecording: function (callback) { + var that = this; + if (that._isArbitrating()) { + callback(); + return; + } + var montages = that._getMontages(); + var channelsDisplayedPerMontage = []; + if (montages) { + montages.forEach(function (montage) { + channelsDisplayedPerMontage.push(that._getChannelsDisplayed(montage)); + }); + } else { + channelsDisplayedPerMontage = [that._getChannelsDisplayed()]; + } + var recordingLengthInSeconds = that.vars.recordingLengthInSeconds; + ////console.log(that.vars.recordingMetadata); + // For local debugging, only + // preload a maximum of 10 epochs + if (!Meteor.isProduction) { + recordingLengthInSeconds = Math.min( + 10 * that.vars.xAxisScaleInSeconds, + recordingLengthInSeconds + ); + } + var numWindowsToRequestPerMontage = Math.ceil( + recordingLengthInSeconds / that.vars.xAxisScaleInSeconds + ); + var numWindowsToRequestTotal = + numWindowsToRequestPerMontage * channelsDisplayedPerMontage.length; + var numWindowsLoaded = 0; + var progressBar = $( + '
' + ).appendTo(that.element.find(".graph_container")); + var loadingCompleted = false; + function updateLoadingProgress() { + var percentage = Math.max( + 0, + Math.min( + 100, + Math.round((numWindowsLoaded / numWindowsToRequestTotal) * 100) + ) + ); + progressBar.find(".indicator").css("width", percentage + "%"); + if (percentage >= 100 && !loadingCompleted) { + loadingCompleted = true; + setTimeout(() => { + progressBar.remove(); + callback(); + }, 500); + } + } + updateLoadingProgress(); + //console.log("channelsDisplayedPerMontage:", channelsDisplayedPerMontage); + channelsDisplayedPerMontage.forEach(function (channelsDisplayed, m) { + for (var i = 0; i < numWindowsToRequestPerMontage; ++i) { + var startTime = i * that.vars.xAxisScaleInSeconds; + var options = { + recordings: that.options.allRecordings, + channels_displayed: channelsDisplayed, + start_time: i * that.vars.xAxisScaleInSeconds + 90, + window_length: that.vars.xAxisScaleInSeconds, + target_sampling_rate: that.options.targetSamplingRate, + use_high_precision_sampling: that.options.useHighPrecisionSampling, + }; + ////console.log(options); + that._requestData(options, function (data, error) { + if (error) { + console.log(error); + } + ++numWindowsLoaded; + updateLoadingProgress(); }); - }, - - _setupFeaturePanel: function() { - var that = this; - $('[data-toggle="popover"]').popover({ trigger: 'hover' }); - - var firstFeature = that.options.features.order[0]; - that.vars.activeFeatureType = firstFeature; - - for (var i = 0; i < that.options.features.order.length; i++) { - var feature_key = that.options.features.order[i]; - var feature_name = that.options.features.options[feature_key].name; - var featureButton = $('').data('annotation-type', feature_key); - $(that.element).find('.feature_panel').append(featureButton); - $('').appendTo('head'); - } - $(that.element).find('.feature').click(function(event) { - that._selectFeatureClass($(this)); + } + }); + }, + + _getRecordingMetadata: function () { + // get the metadata and total length of the recording + var that = this; + return new Promise((resolve, reject) => { + if ( + Object.keys(that.vars.recordingMetadata).length === + that.options.allRecordings.length + ) { + return resolve(that); + } + Meteor.call( + "get.edf.metadata.and.length", + that.options.allRecordings, + (error, results) => { + if (error) { + throw new Error("Cannot get recording metadata\n" + error); + } + that.vars.recordingLengthInSeconds = results.lengthInSeconds; + that.vars.recordingMetadata = results.allMetadata; + return resolve(that); + } + ); + }); + }, + + _triggerOnReadyEvent: function () { + var that = this; + $(that.element).parents(".assignment-container").trigger("readyToStart"); + }, + + _getUserStatus: function () { + var that = this; + //console.log("_getUserStatus.that:", that); + that._setupMontageSelector(); + //console.log("Finish _setupMontageSelector"); + that._setupFrequencyFilterSelector(); + //console.log("Finish _setupFrequencyFilterSelector"); + that._setupXAxisScaleSelector(); + //console.log("Finish _setupXAxisScaleSelector"); + that._setupAnnotationChoice(); + //console.log("Finish _setupAnnotationChoice"); + that._setupAnnotationDisplayType(); + //console.log("Finish _setupAnnotationDisplayType"); + + //console.log("before ifs"); + if (that.options.experiment.running) { + //console.log("that.options.experiment.running"); + that._updateNavigationStatusForExperiment(); + var currentWindowIndex = + that.options.experiment.current_condition.current_window_index; + var conditionWindows = that.options.experiment.current_condition.windows; + let id = Data.findOne({ + path: that.options.experiment.current_condition.recording_name, + })._id; + that.options.allRecordings = [ + { + _id: id, + path: that.options.experiment.current_condition.recording_name, + }, + ]; + var initialWindowStart = conditionWindows[currentWindowIndex]; + that.vars.lastActiveWindowStart = initialWindowStart; + that._switchToWindow( + that.options.allRecordings, + initialWindowStart, + that.vars.xAxisScaleInSeconds + ); + } else if (that._areTrainingWindowsSpecified()) { + //console.log("that._areTrainingWindowsSpecified") + var trainingWindow = that._getCurrentTrainingWindow(); + let id = Data.findOne({ path: trainingWindow.recordingName })._id; + trainingWindow.recordingName = [ + { _id: id, path: trainingWindow.recordingName }, + ]; + that._switchToWindow( + trainingWindow.recordingName, + trainingWindow.timeStart, + trainingWindow.windowSizeInSeconds + ); + } else if (that.options.allRecordings.length > 0) { + //console.log("that.options.allRecordings") + that._loadChannelTimeshiftFromPreference(); + that.vars.lastActiveWindowStart = +that.options.startTime; + const context = that.options.context; + const preferencesArbitrationRoundNumber = !!context.preferences + .arbitrationRoundNumber + ? context.preferences.arbitrationRoundNumber + : 0; + if ( + that._isArbitrating() && + preferencesArbitrationRoundNumber < that._getArbitrationRoundNumberInt() + ) { + that.vars.currentWindowStart = 0; + that.vars.lastActiveWindowStart = + that._getClosestDisagreementWindowStartTimeInSeconds(1); + if (that.vars.lastActiveWindowStart === false) { + that.vars.lastActiveWindowStart = that.options.startTime; + } + } + that._switchToWindow( + that.options.allRecordings, + that.vars.lastActiveWindowStart, + that.vars.xAxisScaleInSeconds + ); + } else { + alert("Could not retrieve user data."); + return; + } + // that._triggerOnReadyEvent(); + // $(window).resize(that._reinitChart); + }, + + _setupArtifactPanel: function () { + var activeClass = "teal darken-4"; + var that = this; + $(that.element) + .find(".artifact_panel button.artifact") + .click(function () { + var button = $(this); + var type = button.data("annotation-type"); + that._saveArtifactAnnotation(type); + button.addClass(activeClass).siblings().removeClass(activeClass); + }); + }, + + _setupSleepStagePanel: function () { + // //console.log("inside sleep"); + var inactiveClass = "grey lighten-1"; + var activeClassSelectedInPreviousRound = + "selected-in-previous-round " + inactiveClass; + var activeClassSelectedInCurrentRound = "teal"; + var activeClasses = + activeClassSelectedInPreviousRound + + " " + + activeClassSelectedInCurrentRound; + var that = this; + $(that.element) + .find(".sleep_stage_panel button.sleep_stage") + .click(function () { + // //console.log("clicked"); + + var button = $(this); + var type = button.data("annotation-type"); + button + .removeClass(inactiveClass) + .addClass(activeClassSelectedInCurrentRound) + .siblings() + .removeClass(activeClasses) + .addClass(inactiveClass); + + that.vars.currType = type; + that._setupAnnotationInteraction(); + that._saveSleepStageAnnotation(type); + }); + }, + + _setupNavigationPanel: function () { + var that = this; + that._setJumpToLastDisagreementWindowEnabledStatus(false); + that._setJumpToNextDisagreementWindowEnabledStatus(false); + that._setForwardEnabledStatus(false); + that._setFastForwardEnabledStatus(false); + that._setBackwardEnabledStatus(false); + that._setFastBackwardEnabledStatus(false); + + if (that.options.showBackToLastActiveWindowButton) { + $(that.element) + .find(".backToLastActiveWindow") + .click(function () { + that._switchBackToLastActiveWindow(); }); - $(that.element).find('.feature.' + firstFeature) - .addClass('active') - .siblings() - .removeClass('active'); - }, - - _preloadEntireRecording: function(callback) { - var that = this; - if (that._isArbitrating()) { - callback(); - return; - } - var montages = that._getMontages(); - var channelsDisplayedPerMontage = []; - if (montages) { - montages.forEach(function(montage) { - channelsDisplayedPerMontage.push(that._getChannelsDisplayed(montage)); - }); - } - else { - channelsDisplayedPerMontage = [ that._getChannelsDisplayed() ]; - } - var recordingLengthInSeconds = that.vars.recordingMetadata.LengthInSeconds; - // For local debugging, only - // preload a maximum of 10 epochs - if (!Meteor.isProduction) { - recordingLengthInSeconds = Math.min(10 * that.options.windowSizeInSeconds, recordingLengthInSeconds); - } - var numWindowsToRequestPerMontage = Math.ceil(recordingLengthInSeconds / that.options.windowSizeInSeconds); - var numWindowsToRequestTotal = numWindowsToRequestPerMontage * channelsDisplayedPerMontage.length; - var numWindowsLoaded = 0; - var progressBar = $('
').appendTo(that.element.find('.graph_container')); - var loadingCompleted = false; - function updateLoadingProgress() { - var percentage = Math.max(0, Math.min(100, Math.round(numWindowsLoaded / numWindowsToRequestTotal * 100))); - progressBar.find('.indicator').css('width', percentage + '%'); - if (percentage >= 100 && !loadingCompleted) { - loadingCompleted = true; - setTimeout(() => { - progressBar.remove(); - callback(); - }, 500); - } - } - updateLoadingProgress(); - channelsDisplayedPerMontage.forEach(function(channelsDisplayed, m) { - for (var i = 0; i < numWindowsToRequestPerMontage; ++i) { - var startTime = i * that.options.windowSizeInSeconds; - var options = { - recording_name: that.options.recordingName, - channels_displayed: channelsDisplayed, - start_time: i * that.options.windowSizeInSeconds, - window_length: that.options.windowSizeInSeconds, - target_sampling_rate: that.options.targetSamplingRate, - use_high_precision_sampling: that.options.useHighPrecisionSampling, - }; - that._requestData(options, function(data, error) { - if (error) { - console.log(error); - } - ++numWindowsLoaded; - updateLoadingProgress(); - }); - } + } + if (that.options.showBookmarkCurrentPageButton) { + $(that.element) + .find(".bookmarkCurrentPage") + .click(function () { + that._toggleBookmarkCurrentPage(); }); - }, - - _getRecordingMetadata: function(callback) { - var that = this; - if (that.vars.recordingMetadata) { - callback(that.vars.recordingMetadata); - return; - } - Meteor.call('get.edf.metadata', that.options.recordingName, function(error, metadata) { - if (error) { - callback(null, error.message); - return; - } - that.vars.recordingMetadata = metadata; - callback(that.vars.recordingMetadata); + } + if (that._isArbitrating()) { + $(that.element) + .find(".jumpToLastDisagreementWindow") + .click(function () { + that._jumpToClosestDisagreementWindow(-1); }); - }, - - _triggerOnReadyEvent: function() { - var that = this; - $(that.element).parents('.assignment-container').trigger('readyToStart'); - }, - - _getUserStatus: function() { - var that = this; - that._setupMontageSelector(); - that._setupFrequencyFilterSelector(); - if (that.options.experiment.running) { - that._updateNavigationStatusForExperiment(); - var currentWindowIndex = that.options.experiment.current_condition.current_window_index; - var conditionWindows = that.options.experiment.current_condition.windows; - that.options.recordingName = that.options.experiment.current_condition.recording_name; - var initialWindowStart = conditionWindows[currentWindowIndex]; - that.vars.lastActiveWindowStart = initialWindowStart; - that._switchToWindow(that.options.recordingName, initialWindowStart, that.options.windowSizeInSeconds); - } - else if (that._areTrainingWindowsSpecified()) { - var trainingWindow = that._getCurrentTrainingWindow(); - that._switchToWindow(trainingWindow.recordingName, trainingWindow.timeStart, trainingWindow.windowSizeInSeconds); - } - else if (that.options.recordingName) { - that.vars.lastActiveWindowStart = that.options.startTime; - const context = that.options.context; - const preferencesArbitrationRoundNumber = !!context.preferences.arbitrationRoundNumber ? context.preferences.arbitrationRoundNumber : 0; - if ( - that._isArbitrating() - && preferencesArbitrationRoundNumber < that._getArbitrationRoundNumberInt() - ) { - that.vars.currentWindowStart = 0; - that.vars.lastActiveWindowStart = that._getClosestDisagreementWindowStartTimeInSeconds(1); - if (that.vars.lastActiveWindowStart === false) { - that.vars.lastActiveWindowStart = that.options.startTime; - } - } - that._switchToWindow(that.options.recordingName, that.vars.lastActiveWindowStart, that.options.windowSizeInSeconds); - } - else { - alert('Could not retrieve user data.'); - return; - } - that._triggerOnReadyEvent(); - $(window).resize(that._reinitChart); - }, - - _setupArtifactPanel: function() { - var activeClass = 'teal darken-4'; - var that = this; - $(that.element).find('.artifact_panel button.artifact').click(function() { - var button = $(this); - var type = button.data('annotation-type'); - that._saveArtifactAnnotation(type); - button - .addClass(activeClass) - .siblings() - .removeClass(activeClass); + $(that.element) + .find(".jumpToNextDisagreementWindow") + .click(function () { + that._jumpToClosestDisagreementWindow(1); }); - }, - - _setupSleepStagePanel: function() { - var inactiveClass = 'grey lighten-1'; - var activeClassSelectedInPreviousRound = 'selected-in-previous-round ' + inactiveClass; - var activeClassSelectedInCurrentRound = 'teal'; - var activeClasses = activeClassSelectedInPreviousRound + ' ' + activeClassSelectedInCurrentRound; - var that = this; - $(that.element).find('.sleep_stage_panel button.sleep_stage').click(function() { - var button = $(this); - var type = button.data('annotation-type'); - button - .removeClass(inactiveClass) - .addClass(activeClassSelectedInCurrentRound) - .siblings() - .removeClass(activeClasses) - .addClass(inactiveClass); - that._saveSleepStageAnnotation(type); + } + if (that.options.showForwardButton) { + $(that.element) + .find(".forward") + .click(function () { + that._shiftChart(1/5); }); - }, - - _setupNavigationPanel: function() { - var that = this; - that._setJumpToLastDisagreementWindowEnabledStatus(false); - that._setJumpToNextDisagreementWindowEnabledStatus(false); - that._setForwardEnabledStatus(false); - that._setFastForwardEnabledStatus(false); - that._setBackwardEnabledStatus(false); - that._setFastBackwardEnabledStatus(false); - - if (that.options.showBackToLastActiveWindowButton) { - $(that.element).find('.backToLastActiveWindow').click(function() { - that._switchBackToLastActiveWindow(); - }); - } - if (that.options.showBookmarkCurrentPageButton) { - $(that.element).find('.bookmarkCurrentPage').click(function() { - that._toggleBookmarkCurrentPage(); - }); - } - if (that._isArbitrating()) { - $(that.element).find('.jumpToLastDisagreementWindow').click(function() { - that._jumpToClosestDisagreementWindow(-1); - }); - $(that.element).find('.jumpToNextDisagreementWindow').click(function() { - that._jumpToClosestDisagreementWindow(1); - }); - } - if (that.options.showForwardButton) { - $(that.element).find('.forward').click(function() { - that._shiftChart(1); - }); - } - if (that.options.showBackwardButton) { - $(that.element).find('.backward').click(function() { - that._shiftChart(-1); - }); - } - if (that.options.showFastForwardButton) { - $(that.element).find('.fastForward').click(function() { - that._shiftChart(that.options.windowJumpSizeFastForwardBackward); - }); - } - if (that.options.showFastBackwardButton) { - $(that.element).find('.fastBackward').click(function() { - that._shiftChart(-that.options.windowJumpSizeFastForwardBackward); - }); - } - $(that.element).find('.gainUp').click(function() { - that._updateChannelGain('step_increase'); + } + if (that.options.showBackwardButton) { + $(that.element) + .find(".backward") + .click(function () { + that._shiftChart(-1/5); }); - $(that.element).find('.gainDown').click(function() { - that._updateChannelGain('step_decrease'); + } + if (that.options.showFastForwardButton) { + $(that.element) + .find(".fastForward") + .click(function () { + that._shiftChart(that.options.windowJumpSizeFastForwardBackward); }); - $(that.element).find('.gainReset').click(function() { - that._updateChannelGain('reset'); + } + if (that.options.showFastBackwardButton) { + $(that.element) + .find(".fastBackward") + .click(function () { + that._shiftChart(-that.options.windowJumpSizeFastForwardBackward); }); - if (that.options.keyboardInputEnabled) { - // setup arrow key navigation - $(document).on('keydown', that._keyDownCallback); - } - }, - - _getClosestDisagreementWindowStartTimeInSeconds: function(direction) { - var that = this; - if (!that._isArbitrating()) return false; - annotationsWithDisagreement = that.options.context.assignment.annotationsWithDisagreementForCurrentArbitrationRound({ reactive: false }); - let notReclassified = Object.values(annotationsWithDisagreement.notReclassified).map(values => values[0].annotation.value.position.start); - if (direction < 0) { - notReclassified = notReclassified.filter(start => start < that.vars.currentWindowStart); - if (notReclassified.length == 0) return false; - return Math.max.apply(null, notReclassified); - } - else { - notReclassified = notReclassified.filter(start => start > that.vars.currentWindowStart); - if (notReclassified.length == 0) return false; - return Math.min.apply(null, notReclassified); - } - return false; - }, - - _updateJumpToClosestDisagreementWindowButtonsEnabledStatus: function() { - var that = this; - const lastDisagreementWindowExists = that._getClosestDisagreementWindowStartTimeInSeconds(-1) !== false; - that._setJumpToLastDisagreementWindowEnabledStatus(lastDisagreementWindowExists); - const nextDisagreementWindowExists = that._getClosestDisagreementWindowStartTimeInSeconds(1) !== false; - that._setJumpToNextDisagreementWindowEnabledStatus(nextDisagreementWindowExists); - }, - - _jumpToClosestDisagreementWindow: function(direction) { - var that = this; - var startInSeconds = that._getClosestDisagreementWindowStartTimeInSeconds(direction); - if (startInSeconds === false) return; - that.jumpToEpochWithStartTime(startInSeconds); - }, - - _keyDownCallback: function(e) { - var that = this; - if ($(e.target).is('input, textarea, select')) { - return; - } - if (swal.isVisible()) { - return; - } - var keyCode = e.which; - var metaKeyPressed = e.metaKey; - if (keyCode == 82 && metaKeyPressed) { - // Suppress any action on CTRL+R / CMD+R page reload - return; - } - if (keyCode == 72) { - that._toggleClassificationSummary(); - } else if(keyCode == 66 && that.options.showBookmarkCurrentPageButton) { - that._toggleBookmarkCurrentPage(); - return; - } else if((keyCode == 37 /* || keyCode == 65 */ || keyCode == 34) && that.options.showBackwardButton) { // left arrow, a, page down - // backward - e.preventDefault(); - that._shiftChart(-1); - return; - } else if ((keyCode == 39 /* || keyCode == 68 */ || keyCode == 33) && that.options.showForwardButton) { // right arrow, d, page up - // forward - e.preventDefault(); - that._shiftChart(1); - return; - } else if (keyCode == 38) { // up arrow - // fast foward - e.preventDefault(); - that._updateChannelGain('step_increase'); - return; - } else if (keyCode == 40) { // down arrow - // fast backward - e.preventDefault(); - that._updateChannelGain('step_decrease'); - return; - } else if (keyCode == 65) { - that._jumpToClosestDisagreementWindow(-1); - } else if (keyCode == 68) { - that._jumpToClosestDisagreementWindow(1); - } else if (that.options.showSleepStageButtons) { - var sleepStageShortCutPressed = false; - $(that.element).find('.sleep_stage_panel .shortcut-key').each(function() { - var character = $(this).text(); - var characterKeyCodeLowerCase = character.toLowerCase().charCodeAt(0); - var characterKeyCodeAlternative = character.toUpperCase().charCodeAt(0); - if (characterKeyCodeLowerCase >= 48 && characterKeyCodeLowerCase <= 57) { - characterKeyCodeAlternative = characterKeyCodeLowerCase + 48; - } - if (keyCode == characterKeyCodeLowerCase || keyCode == characterKeyCodeAlternative) { - e.preventDefault(); - sleepStageShortCutPressed = true; - var button = $(this).parents('.sleep_stage').first(); - button.click(); - } - }); - if (sleepStageShortCutPressed) { - return; - } - // make it possible to choose feature classificaiton using number keys - } else if (keyCode >= 49 && keyCode <= 57) { - e.preventDefault(); - var featureClassButton = $(that.element).find('.feature').eq(keyCode - 49) - if (featureClassButton) { - that._selectFeatureClass(featureClassButton); - } - return; - // separate case for the numpad keys, because javascript is a stupid language - } else if (keyCode >= 97 && keyCode <= 105) { - e.preventDefault(); - var featureClassButton = $(that.element).find('.feature').eq(keyCode - 97) - if (featureClassButton) { - that._selectFeatureClass(featureClassButton); - } - return; - } - }, - - _toggleClassificationSummary: function() { - var that = this; - var classificationSummaryElement = that._getClassificationSummaryElement(); - if (classificationSummaryElement.is(':visible')) { - classificationSummaryElement.hide(); - classificationSummaryElement.css({ height: 0 }); - } - else { - classificationSummaryElement.show(); - classificationSummaryElement.css({ height: '' }); - } - that._reinitChart(); - }, + } + $(that.element) + .find(".gainUp") + .click(function () { + that._updateChannelGain("step_increase"); + }); + $(that.element) + .find(".gainDown") + .click(function () { + that._updateChannelGain("step_decrease"); + }); + $(that.element) + .find(".gainReset") + .click(function () { + that._updateChannelGain("reset"); + }); + if (that.options.keyboardInputEnabled) { + // setup arrow key navigation + $(document).on("keydown", that._keyDownCallback); + } + }, - _toggleBookmarkCurrentPage: function() { - var that = this; - const bookmarkedPages = that.options.context.preferences.annotatorConfig.bookmarkedPages || {}; - const pageKey = that.vars.currentWindowStart; - if (bookmarkedPages[pageKey] === true) { - delete bookmarkedPages[pageKey]; - } - else { - bookmarkedPages[pageKey] = true; - } - that._reinitChart(); - that._updateBookmarkCurrentPageButton(); + _destroyCrosshair: function () { + var that = this; + if (that.vars.crosshair) { + that.vars.crosshair.destroy(); + that.vars.crosshair = undefined; + } + return; + }, + + _isInCrosshairSyncMode: function () { + var that = this; + return that.vars.timeSyncMode === "crosshair"; + }, + + _isInOffsetSyncMode: function () { + var that = this; + return that.vars.timeSyncMode === "offset"; + }, + + _isInNoTimelockMode: function () { + var that = this; + return that.vars.timeSyncMode === "notimelock"; + }, + + _toggleNoTimelockScroll: function (toggle) { + var that = this; + var chart = that.vars.chart; + + function scroll(e) { + let dist = e.deltaY * 0.025; + let channelIndex = that.vars.selectedChannelIndex; + if (channelIndex !== undefined) { + let modifiedDataId = chart.series[channelIndex].options.custom.dataId; + let timeshift = that.vars.channelTimeshift[modifiedDataId]; + timeshift = timeshift ? timeshift + dist : dist; + let recordingLength = + that.vars.recordingMetadata[modifiedDataId].LengthInSeconds; + that.vars.channelTimeshift[modifiedDataId] = Math.min( + recordingLength, + Math.max(0, timeshift) + ); + // that.vars.reprint = 1; that._savePreferences({ - bookmarkedPages: bookmarkedPages, + channelTimeshift: that.vars.channelTimeshift, }); - }, - - _updateBookmarkCurrentPageButton: function() { - var that = this; - const bookmarkedPages = that.options.context.preferences.annotatorConfig.bookmarkedPages || {}; - const pageKey = that.vars.currentWindowStart; - const isBookmarked = bookmarkedPages[pageKey] === true; - $(that.element).find('.bookmarkCurrentPage').prop('disabled', null).toggleClass('active', isBookmarked); - }, - - _setupTrainingPhase: function() { - var that = this; - if (!that._areTrainingWindowsSpecified()) return; - that._setForwardEnabledStatus(true); - that._getSpecifiedTrainingWindows(); - }, - - _setupArbitration: function() { - var that = this; - if (!that._isArbitrating()) return; - that.options.numberOfForwardWindowsToPrefetch = 2; - that.options.numberOfFastForwardWindowsToPrefetch = 0; - that.options.numberOfBackwardWindowsToPrefetch = 2; - that.options.numberOfFastBackwardWindowsToPrefetch = 0; - }, - - _setJumpToLastDisagreementWindowEnabledStatus: function(status) { - var that = this; - var status = !!status; - - that.vars.jumpToLastDisagreementWindow = status; - $(that.element).find('.jumpToLastDisagreementWindow').prop('disabled', !status); - }, - - _setJumpToNextDisagreementWindowEnabledStatus: function(status) { - var that = this; - var status = !!status; - - that.vars.jumpToNextDisagreementWindow = status; - $(that.element).find('.jumpToNextDisagreementWindow').prop('disabled', !status); - }, - - _setForwardEnabledStatus: function(status) { - var that = this; - var status = !!status; - - that.vars.forwardEnabled = status; - $(that.element).find('.forward').prop('disabled', !status); - }, - - _setFastForwardEnabledStatus: function(status) { - var that = this; - var status = !!status; - - that.vars.fastForwardEnabled = status; - $(that.element).find('.fastForward').prop('disabled', !status); - }, - - _setBackwardEnabledStatus: function(status) { - var that = this; - var status = !!status; - - that.vars.backwardEnabled = status; - $(that.element).find('.backward').prop('disabled', !status); - }, - - _setFastBackwardEnabledStatus: function(status) { - var that = this; - var status = !!status; - - that.vars.fastBackwardEnabled = status; - $(that.element).find('.fastBackward').prop('disabled', !status); - }, + that._reloadCurrentWindow(); + } + } - _selectFeatureClass: function(featureClassButton) { - /* called with the user clicks on one of the feature toggle buttons, or presses one of the + if (toggle) { + Highcharts.addEvent(document, "wheel", scroll); + } else { + Highcharts.removeEvent(document, "wheel", scroll); + that.vars.reprint = 1; + that._reloadCurrentWindow(); + } + }, + + _toggleTimeSyncMode: function (mode) { + var that = this; + switch (mode) { + case "crosshair": + $(".timesync").prop("disabled", false); + that._toggleNoTimelockScroll(false); + that._displayCrosshair(that.vars.crosshairPosition); + break; + case "notimelock": + $(".timesync").prop("disabled", true); + that._destroyCrosshair(); + that._toggleNoTimelockScroll(true); + $(".time_sync").text(""); + that.vars.currentTimeDiff = 0; + break; + case "offset": + // $(".time_sync").text(""); + $(".timesync").prop("disabled", false); + that._toggleNoTimelockScroll(false); + that._destroyCrosshair(); + break; + case "undefined": + default: + $(".timesync").prop("disabled", true); + that._toggleNoTimelockScroll(false); + that._destroyCrosshair(); + break; + } + }, + + _performCrosshairSync: function () { + var that = this; + let crosshairPosition = that.vars.crosshairPosition; + let ids = crosshairPosition.map((rec) => rec.dataId); + let currentDiff = ids.map((id) => that.vars.channelTimeshift[id]); + if (crosshairPosition.length === 2) { + // calculate the difference between two recordings after adding the current difference + let diff = + crosshairPosition[0].timeInSeconds - crosshairPosition[1].timeInSeconds; + that.vars.currentTimeDiff += diff; + console.log("=======" + diff + "======"); + $(".time_sync").text("Time Difference: " + that.vars.currentTimeDiff); + if (diff > 0) { + if (currentDiff[1]) { + let remainder = diff - currentDiff[1]; + if (remainder > 0) { + that.vars.channelTimeshift[ids[1]] = 0; + that.vars.channelTimeshift[ids[0]] = currentDiff[0] + ? currentDiff[0] + remainder + : remainder; + } else { + that.vars.channelTimeshift[ids[1]] = -remainder; + } + } else { + that.vars.channelTimeshift[ids[0]] = currentDiff[0] + ? currentDiff[0] + diff + : diff; + } + } else if (diff < 0) { + diff = -diff; + if (currentDiff[0]) { + let remainder = diff - currentDiff[0]; + if (remainder > 0) { + that.vars.channelTimeshift[ids[0]] = 0; + that.vars.channelTimeshift[ids[1]] = currentDiff[1] + ? currentDiff[1] + remainder + : remainder; + } else { + that.vars.channelTimeshift[ids[0]] = -remainder; + } + } else { + that.vars.channelTimeshift[ids[1]] = currentDiff[1] + ? currentDiff[1] + diff + : diff; + } + } + that.vars.reprint = 1; + that.vars.crosshairPosition = []; + $(this.element) + .find(".timesync_panel select") + .val("undefined") + .change() + .material_select(); + that._savePreferences({ + channelTimeshift: that.vars.channelTimeshift, + }); + + $("#alignment-alert").hide(); + that._reloadCurrentWindow(); + } + }, + + _performOffsetSync: function () { + var that = this; + that.vars.channelTimeshift = {}; + that.vars.reprint = 1; + $(this.element) + .find(".timesync_panel select") + .val("undefined") + .change() + .material_select(); + that._savePreferences({ channelTimeshift: that.vars.channelTimeshift }); + that._reloadCurrentWindow(); + console.log(that.vars.channelTimeshift); + that.vars.currentTimeDiff = 0; + $(".time_sync").text(""); + }, + + _setupTimeSyncPanel: function () { + var that = this; + let timeSyncOptions = that.options.timeSyncOptions || []; + timeSyncOptions.forEach((timeSyncOption) => { + let selectContainer = $( + '
' + ).appendTo(that.element.find(".timesync_panel")); + let select = selectContainer.find("select"); + let defaultOptionIndex = null; + timeSyncOption.options.forEach((option, i) => { + let selectedString = ""; + if (option.default) { + selectedString = ' selected="selected"'; + defaultOptionIndex = i; + } + select.append( + `" + ); + }); + + select.material_select(); + select.change(function () { + if (defaultOptionIndex) + delete timeSyncOption.options[defaultOptionIndex].default; + timeSyncOption.options[select.prop("selectedIndex")].default = true; + that.vars.timeSyncMode = select.val(); + that._toggleTimeSyncMode(select.val()); + that._renderAlignmentAlert(); + }); + }); + $(that.element) + .find(".timesync") + .click(function () { + if (that._isInCrosshairSyncMode()) { + that._performCrosshairSync(); + } else if (that._isInOffsetSyncMode()) { + that._performOffsetSync(); + } else if (that._isInNoTimelockMode()) { + // future implementations: + // besides free scrolling by mouse wheel, + // adding [+/-] hh:mm:ss option and [shift left/right] buttons + } + }); + }, + + _isInCrosshairWindow: function (crosshair) { + var that = this; + return ( + that.vars.currentWindowStart <= crosshair.timeInSeconds && + crosshair.timeInSeconds <= + that.vars.currentWindowStart + that.vars.xAxisScaleInSeconds + ); + }, + + _displayCrosshair: function (crosshairPosition) { + var that = this; + that._destroyCrosshair(); + if (!that._isInCrosshairSyncMode()) return; + let chart = that.vars.chart; + that.vars.crosshair = chart.renderer.g().add(); + crosshairPosition.forEach((crosshair) => { + if (!that._isInCrosshairWindow(crosshair)) return; + let left = chart.plotLeft; + let top = chart.plotTop; + let height = chart.plotHeight; + let heightPerChannel = + height / that.vars.currentWindowData.channels.length; + let firstIndexOfChannel = undefined; + let lastIndexOfChannel = undefined; + chart.series.forEach((channel, i) => { + if ( + channel.options.custom.dataId === crosshair.dataId && + channel.points.length + ) { + if (typeof firstIndexOfChannel === "undefined") { + firstIndexOfChannel = i; + } + lastIndexOfChannel = i; + } + }); + let crosshairTop = firstIndexOfChannel * heightPerChannel; + let crosshairBottom = (lastIndexOfChannel + 1) * heightPerChannel; + // draw the crosshair using svgPath and add it as a highchart SVGElement + let svgPath = [ + "M", + left + crosshair.plotX, + top + crosshairTop, + "L", + left + crosshair.plotX, + top + crosshairBottom, + ]; + chart.renderer + .path(svgPath) + .attr({ + "stroke-width": 1, + stroke: "blue", + }) + .add(that.vars.crosshair); + }); + }, + + //checks what the dataId is of the top/bottom channels when aligning 2 channels + //used to check if the channel is the top or bottom one when using crosshair alignment + _getTopDataId: function () { + //get all the channels + var that = this; + const channels = that.vars.currentWindowData.channels; + //get the first channel + const firstChannel = channels[0]; + //get the dataId of the first channel + const firstChannelDataId = firstChannel.dataId; + return firstChannelDataId; + }, + + _getBottomDataId: function () { + //get all the channels + var that = this; + const channels = that.vars.currentWindowData.channels; + //get the last channel + const lastChannel = channels[channels.length - 1]; + //get the dataId of the last channel + const lastChannelDataId = lastChannel.dataId; + return lastChannelDataId; + }, + + _isFromSource: function (dataId, source) { + var that = this; + // console.log("dataId: " + dataId); + // console.log("source: " + source); + let found = false; + + that.options.context.dataset.forEach((dataset) => { + // console.log(dataset, dataset._id, dataset.source); + // console.log(dataset._id === dataId); + // console.log(dataset.source === source); + if (dataset._id === dataId && dataset.source === source) { + // console.log(true) + found = true; + } + }); + + return found; + }, + + _isFromPSG: function (dataId) { + var that = this; + return that._isFromSource(dataId, "PSG"); + }, + + _setCrosshair: function (point) { + var that = this; + + // console.log("======getTopDataId()======"); + // console.log(that._getTopDataId()); + // console.log("======getBottomDataId()====="); + // console.log(that._getBottomDataId()); + // console.log("======that.options======"); + // console.log(that.options); + // console.log("======that.isFromPSG======"); + // console.log("-top") + // console.log(that._isFromPSG(that._getTopDataId())); + // console.log("-bottom") + // console.log(that._isFromPSG(that._getBottomDataId())); + console.log(that._isFromPSG(point.dataId)); + + if (!that._isInCrosshairSyncMode()) return; + + if ( + that.vars.crosshairPosition.length === 0 && + !that._isFromPSG(point.dataId) + ) + return; + let crosshairPosition = that.vars.crosshairPosition; + let sameRecording = false; + let index = undefined; + crosshairPosition.forEach((crosshair, i) => { + if (crosshair.dataId === point.dataId) { + sameRecording = true; + index = i; + } + }); + if (sameRecording) { + crosshairPosition[index] = point; + } else { + if (crosshairPosition.length < 2) { + crosshairPosition.push(point); + } + // if (crosshairPosition.length > 2) { + // crosshairPosition.shift(); + // } + } + that.vars.crosshairPosition = crosshairPosition; + that._displayCrosshair(crosshairPosition); + that._renderAlignmentAlert(); + }, + + _getClosestDisagreementWindowStartTimeInSeconds: function (direction) { + var that = this; + if (!that._isArbitrating()) return false; + annotationsWithDisagreement = + that.options.context.assignment.annotationsWithDisagreementForCurrentArbitrationRound( + { reactive: false } + ); + let notReclassified = Object.values( + annotationsWithDisagreement.notReclassified + ).map((values) => values[0].annotation.value.position.start); + if (direction < 0) { + notReclassified = notReclassified.filter( + (start) => start < that.vars.currentWindowStart + ); + if (notReclassified.length == 0) return false; + return Math.max.apply(null, notReclassified); + } else { + notReclassified = notReclassified.filter( + (start) => start > that.vars.currentWindowStart + ); + if (notReclassified.length == 0) return false; + return Math.min.apply(null, notReclassified); + } + return false; + }, + + _updateJumpToClosestDisagreementWindowButtonsEnabledStatus: function () { + var that = this; + const lastDisagreementWindowExists = + that._getClosestDisagreementWindowStartTimeInSeconds(-1) !== false; + that._setJumpToLastDisagreementWindowEnabledStatus( + lastDisagreementWindowExists + ); + const nextDisagreementWindowExists = + that._getClosestDisagreementWindowStartTimeInSeconds(1) !== false; + that._setJumpToNextDisagreementWindowEnabledStatus( + nextDisagreementWindowExists + ); + }, + + _jumpToClosestDisagreementWindow: function (direction) { + var that = this; + var startInSeconds = + that._getClosestDisagreementWindowStartTimeInSeconds(direction); + if (startInSeconds === false) return; + that.jumpToEpochWithStartTime(startInSeconds); + }, + + // handles keypress events + _keyDownCallback: function (e) { + var that = this; + if ($(e.target).is("input, textarea, select")) { + return; + } + if (swal.isVisible()) { + return; + } + var keyCode = e.which; + var metaKeyPressed = e.metaKey; + if (keyCode == 82 && metaKeyPressed) { + // Suppress any action on CTRL+R / CMD+R page reload + return; + } + if (keyCode == 72) { + that._toggleClassificationSummary(); + } else if (keyCode == 66 && that.options.showBookmarkCurrentPageButton) { + that._toggleBookmarkCurrentPage(); + return; + } else if ( + (keyCode == 37 /* || keyCode == 65 */ || keyCode == 34) && + that.options.showBackwardButton + ) { + // left arrow, a, page down + // backward + e.preventDefault(); + that._shiftChart(-1); + return; + } else if ( + (keyCode == 39 /* || keyCode == 68 */ || keyCode == 33) && + that.options.showForwardButton + ) { + // right arrow, d, page up + // forward + e.preventDefault(); + that._shiftChart(1); + return; + } else if (keyCode == 38) { + // up arrow + // fast foward + e.preventDefault(); + that._updateChannelGain("step_increase"); + return; + } else if (keyCode == 40) { + // down arrow + // fast backward + e.preventDefault(); + that._updateChannelGain("step_decrease"); + return; + } else if (keyCode == 65) { + that._jumpToClosestDisagreementWindow(-1); + } else if (keyCode == 68) { + that._jumpToClosestDisagreementWindow(1); + } else if (that.options.showSleepStageButtons) { + var sleepStageShortCutPressed = false; + $(that.element) + .find(".sleep_stage_panel .shortcut-key") + .each(function () { + var character = $(this).text(); + var characterKeyCodeLowerCase = character.toLowerCase().charCodeAt(0); + var characterKeyCodeAlternative = character + .toUpperCase() + .charCodeAt(0); + if ( + characterKeyCodeLowerCase >= 48 && + characterKeyCodeLowerCase <= 57 + ) { + characterKeyCodeAlternative = characterKeyCodeLowerCase + 48; + } + if ( + keyCode == characterKeyCodeLowerCase || + keyCode == characterKeyCodeAlternative + ) { + e.preventDefault(); + sleepStageShortCutPressed = true; + var button = $(this).parents(".sleep_stage").first(); + button.click(); + } + }); + if (sleepStageShortCutPressed) { + return; + } + // make it possible to choose feature classificaiton using number keys + } else if (keyCode >= 49 && keyCode <= 57) { + e.preventDefault(); + var featureClassButton = $(that.element) + .find(".feature") + .eq(keyCode - 49); + if (featureClassButton) { + that._selectFeatureClass(featureClassButton); + } + return; + // separate case for the numpad keys, because javascript is a stupid language + } else if (keyCode >= 97 && keyCode <= 105) { + e.preventDefault(); + var featureClassButton = $(that.element) + .find(".feature") + .eq(keyCode - 97); + if (featureClassButton) { + that._selectFeatureClass(featureClassButton); + } + return; + } + }, + + _toggleClassificationSummary: function () { + var that = this; + var classificationSummaryElement = that._getClassificationSummaryElement(); + if (classificationSummaryElement.is(":visible")) { + classificationSummaryElement.hide(); + classificationSummaryElement.css({ height: 0 }); + } else { + classificationSummaryElement.show(); + classificationSummaryElement.css({ height: "" }); + } + that._reinitChart(); + }, + + _toggleBookmarkCurrentPage: function () { + var that = this; + const bookmarkedPages = + that.options.context.preferences.annotatorConfig.bookmarkedPages || {}; + const pageKey = that.vars.currentWindowStart; + if (bookmarkedPages[pageKey] === true) { + delete bookmarkedPages[pageKey]; + } else { + bookmarkedPages[pageKey] = true; + } + that._reinitChart(); + that._updateBookmarkCurrentPageButton(); + that._savePreferences({ + bookmarkedPages: bookmarkedPages, + }); + }, + + _updateBookmarkCurrentPageButton: function () { + var that = this; + const bookmarkedPages = + that.options.context.preferences.annotatorConfig.bookmarkedPages || {}; + const pageKey = that.vars.currentWindowStart; + const isBookmarked = bookmarkedPages[pageKey] === true; + $(that.element) + .find(".bookmarkCurrentPage") + .prop("disabled", null) + .toggleClass("active", isBookmarked); + }, + + _setupTrainingPhase: function () { + var that = this; + if (!that._areTrainingWindowsSpecified()) return; + that._setForwardEnabledStatus(true); + that._getSpecifiedTrainingWindows(); + }, + + _setupArbitration: function () { + var that = this; + if (!that._isArbitrating()) return; + that.options.numberOfForwardWindowsToPrefetch = 2; + that.options.numberOfFastForwardWindowsToPrefetch = 0; + that.options.numberOfBackwardWindowsToPrefetch = 2; + that.options.numberOfFastBackwardWindowsToPrefetch = 0; + }, + + _setJumpToLastDisagreementWindowEnabledStatus: function (status) { + var that = this; + var status = !!status; + + that.vars.jumpToLastDisagreementWindow = status; + $(that.element) + .find(".jumpToLastDisagreementWindow") + .prop("disabled", !status); + }, + + _setJumpToNextDisagreementWindowEnabledStatus: function (status) { + var that = this; + var status = !!status; + + that.vars.jumpToNextDisagreementWindow = status; + $(that.element) + .find(".jumpToNextDisagreementWindow") + .prop("disabled", !status); + }, + + _setForwardEnabledStatus: function (status) { + var that = this; + var status = !!status; + + that.vars.forwardEnabled = status; + $(".forward").prop("disabled", !status); + }, + + _setFastForwardEnabledStatus: function (status) { + var that = this; + var status = !!status; + + that.vars.fastForwardEnabled = status; + $(".fastForward").prop("disabled", !status); + }, + + _setBackwardEnabledStatus: function (status) { + var that = this; + var status = !!status; + + that.vars.backwardEnabled = status; + $(".backward").prop("disabled", !status); + }, + + _setFastBackwardEnabledStatus: function (status) { + var that = this; + var status = !!status; + + that.vars.fastBackwardEnabled = status; + $(".fastBackward").prop("disabled", !status); + }, + + _selectFeatureClass: function (featureClassButton) { + /* called with the user clicks on one of the feature toggle buttons, or presses one of the relevant number keys, this method updates the state of the toggle buttons, and sets the feature type */ - var that = this; - featureClassButton.addClass('active'); - featureClassButton.siblings().removeClass('active'); - that.vars.activeFeatureType = featureClassButton.data('annotation-type'); - }, - - _shiftChart: function(windows) { - var that = this; - if (!that.vars.forwardEnabled && windows >= 1) return; - if (!that.vars.fastForwardEnabled && windows >= that.options.windowJumpSizeFastForwardBackward) return; - if (!that.vars.backwardEnabled && windows <= -1) return; - if (!that.vars.fastBackwardEnabled && windows <= -that.options.windowJumpSizeFastForwardBackward) return; - var nextRecordingName = that.options.recordingName; - var nextWindowSizeInSeconds = that.options.windowSizeInSeconds; - var nextWindowStart = that.options.currentWindowStart; - if (that.options.experiment.running) { - var currentWindowIndex = that.options.experiment.current_condition.current_window_index; - currentWindowIndex += windows; - that.options.experiment.current_condition.current_window_index = currentWindowIndex; - nextWindowStart = that.options.experiment.current_condition.windows[currentWindowIndex]; - that._updateNavigationStatusForExperiment(); - } - else if ( - that._areTrainingWindowsSpecified() - && that._isCurrentWindowTrainingWindow() - ) { - that.vars.currentTrainingWindowIndex += windows; - if (that._isCurrentWindowTrainingWindow()) { - var nextTrainingWindow = that._getCurrentTrainingWindow(); - nextRecordingName = nextTrainingWindow.recordingName; - nextWindowStart = nextTrainingWindow.timeStart; - nextWindowSizeInSeconds = nextTrainingWindow.windowSizeInSeconds; - } - else { - nextWindowStart = that.options.startTime; - nextWindowSizeInSeconds = that.options.windowSizeInSeconds; - } - that._flushAnnotations(); - } - else { - if (that._areTrainingWindowsSpecified()) { - that.vars.currentTrainingWindowIndex += windows; - } - nextWindowStart = Math.max(0, that.vars.currentWindowStart + that.options.windowSizeInSeconds * windows); - } - that._switchToWindow(nextRecordingName, nextWindowStart, nextWindowSizeInSeconds); - }, - - _switchToWindow: function (recording_name, start_time, window_length) { - var that = this; - if (!that._isCurrentWindowSpecifiedTrainingWindow()) { - if (that.options.visibleRegion.start !== undefined) { - start_time = Math.max(that.options.visibleRegion.start, start_time); - start_time = window_length * Math.ceil(start_time / window_length); - that._setBackwardEnabledStatus(start_time - window_length >= that.options.visibleRegion.start); - that._setFastBackwardEnabledStatus(start_time - window_length * that.options.windowJumpSizeFastForwardBackward >= that.options.visibleRegion.start); - } - - if (that.options.visibleRegion.end !== undefined) { - start_time = Math.min(that.options.visibleRegion.end - window_length, start_time); - start_time = window_length * Math.floor(start_time / window_length); - var forwardEnabled = start_time + window_length <= that.options.visibleRegion.end - window_length; - that._setForwardEnabledStatus(forwardEnabled); - if (!forwardEnabled) { - that._lastWindowReached(); - } - that._setFastForwardEnabledStatus(start_time + window_length * that.options.windowJumpSizeFastForwardBackward < that.options.visibleRegion.end - window_length); - } - } + var that = this; + featureClassButton.addClass("active"); + featureClassButton.siblings().removeClass("active"); + that.vars.activeFeatureType = featureClassButton.data("annotation-type"); + }, + + _shiftChart: function (windows) { + var that = this; + if (!that.vars.forwardEnabled && windows >= 1) return; + if ( + !that.vars.fastForwardEnabled && + windows >= that.options.windowJumpSizeFastForwardBackward + ) + return; + if (!that.vars.backwardEnabled && windows <= -1) return; + if ( + !that.vars.fastBackwardEnabled && + windows <= -that.options.windowJumpSizeFastForwardBackward + ) + return; + var nextRecordings = that.options.allRecordings; + var nextWindowSizeInSeconds = that.vars.xAxisScaleInSeconds; + var nextWindowStart = that.options.currentWindowStart; + if (that.options.experiment.running) { + var currentWindowIndex = + that.options.experiment.current_condition.current_window_index; + currentWindowIndex += windows; + that.options.experiment.current_condition.current_window_index = + currentWindowIndex; + nextWindowStart = + that.options.experiment.current_condition.windows[currentWindowIndex]; + that._updateNavigationStatusForExperiment(); + } else if ( + that._areTrainingWindowsSpecified() && + that._isCurrentWindowTrainingWindow() + ) { + that.vars.currentTrainingWindowIndex += windows; + if (that._isCurrentWindowTrainingWindow()) { + var nextTrainingWindow = that._getCurrentTrainingWindow(); + let id = Data.findOne({ + path: nextTrainingWindow.recordingName, + })._id; + nextRecordings = [{ _id: id, path: nextTrainingWindow.recordingName }]; + nextWindowStart = nextTrainingWindow.timeStart; + nextWindowSizeInSeconds = nextTrainingWindow.windowSizeInSeconds; + } else { + nextWindowStart = that.options.startTime; + nextWindowSizeInSeconds = that.vars.xAxisScaleInSeconds; + } + that._flushAnnotations(); + } else { + if (that._areTrainingWindowsSpecified()) { + that.vars.currentTrainingWindowIndex += windows; + } + nextWindowStart = Math.max( + 0, + that.vars.currentWindowStart + that.vars.xAxisScaleInSeconds * windows + ); + } + that._switchToWindow( + nextRecordings, + nextWindowStart, + nextWindowSizeInSeconds + ); + }, + + _switchToWindow: function (allRecordings, start_time, window_length) { + // the main funciton called when navigating to another window + var that = this; + //console.log("_switchToWindow.that:", that); + + // can be ignored for now, something to do with the machine learning component of the app + // console.log(!that._isCurrentWindowSpecifiedTrainingWindow()); + + if (!that._isCurrentWindowSpecifiedTrainingWindow()) { + //console.log("0"); + if (that.options.visibleRegion.start !== undefined) { + //console.log("0-1"); + start_time = Math.max(that.options.visibleRegion.start, start_time); + start_time = window_length * Math.ceil(start_time / window_length); + + that._setBackwardEnabledStatus( + start_time - window_length >= that.options.visibleRegion.start + ); + that._setFastBackwardEnabledStatus( + start_time - + window_length * that.options.windowJumpSizeFastForwardBackward >= + that.options.visibleRegion.start + ); + } + // console.log(that.options.visibleRegion.end); + if (that.options.visibleRegion.end !== undefined) { + //console.log("0-2"); + start_time = Math.min( + that.options.visibleRegion.end - window_length, + start_time + ); + start_time = window_length * Math.floor(start_time / window_length); + var forwardEnabled = + start_time + window_length <= + that.options.visibleRegion.end - window_length; + that._setForwardEnabledStatus(forwardEnabled); + if (!forwardEnabled) { + //console.log("0-2-1"); + that._lastWindowReached(); + } + var fastForwardEnabled = start_time + + window_length * that.options.windowJumpSizeFastForwardBackward < + that.options.visibleRegion.end - window_length; + + // console.log(fastForwardEnabled); - if (that.vars.currentWindowStart != start_time) { - that._setNumberOfAnnotationsInCurrentWindow(0); - } + that._setFastForwardEnabledStatus( + fastForwardEnabled + ); + } + } - that.vars.currentWindowStart = start_time; - that.vars.currentWindowRecording = recording_name; - that._updateJumpToClosestDisagreementWindowButtonsEnabledStatus(); + if (that.vars.currentWindowStart != start_time) { + //if the window has changed (i.e. the user has clicked on a new window) + //console.log("1"); + that._setNumberOfAnnotationsInCurrentWindow(0); // reset the number of annotations in the current window + } - if (that._isVisibleRegionDefined()) { - var progress = that._getProgressInPercent(); - $(that.element).find('.progress-bar').css('width', progress + '%'); - } + that.vars.currentWindowStart = start_time; // update the current window start + that.vars.currentWindowRecording = that.options.recordingName; // update the current window recording + that._updateJumpToClosestDisagreementWindowButtonsEnabledStatus(); + + // + if (that._isVisibleRegionDefined()) { + //console.log("2"); + var progress = that._getProgressInPercent(); + $(that.element) + .find(".progress-bar") + .css("width", progress + "%"); + } - if (that._isCurrentWindowLastTrainingWindow() && that._isTrainingOnly()) { - that._lastWindowReached(); - } + if (that._isCurrentWindowLastTrainingWindow() && that._isTrainingOnly()) { + //console.log("3"); + that._lastWindowReached(); + } - if (that._isCurrentWindowFirstTrainingWindow() && !that._isTrainingOnly()) { - bootbox.alert({ - closeButton: false, - title: 'Beginning of the Training Phase', - message: 'Welcome to our CrowdEEG experiment for scientific crowdsourcing!

This is the beginning of the training phase, meaning that, for the next ' + that._getNumberOfTrainingWindows() + ' window(s), we will show you the correct answer after you have submitted yours. The examples panel below will be visible throughout the entire task. We hope the training phase will help you learn more about the signal pattern we are looking for.', - callback: function() { - that._saveUserEventWindowBegin(); - }, - }).css({zIndex: 1}).appendTo(that.element); - } - else { + if (that._isCurrentWindowFirstTrainingWindow() && !that._isTrainingOnly()) { + //console.log("4"); + bootbox + .alert({ + closeButton: false, + title: "Beginning of the Training Phase", + message: + "Welcome to our CrowdEEG experiment for scientific crowdsourcing!

This is the beginning of the training phase, meaning that, for the next " + + that._getNumberOfTrainingWindows() + + " window(s), we will show you the correct answer after you have submitted yours. The examples panel below will be visible throughout the entire task. We hope the training phase will help you learn more about the signal pattern we are looking for.", + callback: function () { that._saveUserEventWindowBegin(); - } - - that._savePreferences({ startTime: start_time }) + }, + }) + .css({ zIndex: 1 }) + .appendTo(that.element); + } else { + //console.log("4-1"); + that._saveUserEventWindowBegin(); + } - var windowsToRequest = [ - start_time - ]; + that._savePreferences({ startTime: start_time }); + + var windowsToRequest = [ + //stores all "pre loaded" windows + start_time, + ]; + if ( + !that._isCurrentWindowSpecifiedTrainingWindow() && + !that.options.experiment.running && + !that._isInNoTimelockMode() // stop caching windows if in no timelock mode + ) { + //console.log("5"); + for (var i = 1; i <= that.options.numberOfForwardWindowsToPrefetch; ++i) { + windowsToRequest.push(start_time + i * window_length); + } + for ( + var i = 1; + i <= that.options.numberOfFastForwardWindowsToPrefetch; + ++i + ) { + let window = start_time + i * that.options.windowJumpSizeFastForwardBackward * window_length; + if (!windowsToRequest.includes(window)) { + windowsToRequest.push(window); + } + } + for ( + var i = 1; + i <= that.options.numberOfBackwardWindowsToPrefetch; + ++i + ) { + let window = start_time - i * window_length; + if (!windowsToRequest.includes(window)) { + windowsToRequest.push(window); + } + } + for ( + var i = 1; + i <= that.options.numberOfFastBackwardWindowsToPrefetch; + ++i + ) { + let window = start_time - i * that.options.windowJumpSizeFastForwardBackward * window_length; + if (!windowsToRequest.includes(window)) { + windowsToRequest.push(window); + } + } + } + windowsToRequest.forEach((windowStartTime) => { + //console.log("6, windowStartTime:", windowStartTime); + // gets the data for all the prefetched windows + var startTime = (windowStartTime > 0 ? + (windowStartTime < that.vars.recordingLengthInSeconds + window_length ? Math.min(that.vars.recordingLengthInSeconds, windowStartTime) : windowStartTime): + (windowStartTime > -window_length ? Math.max(0, windowStartTime) : windowStartTime) + ); + + var options = { + recordings: allRecordings, + channels_displayed: that._getChannelsDisplayed(), // get all channels we would like to display + start_time: startTime, + channel_timeshift: that.vars.channelTimeshift, + window_length: window_length, + target_sampling_rate: that.options.targetSamplingRate, + use_high_precision_sampling: that.options.useHighPrecisionSampling, + }; + that._requestData(options, (data, errorData) => { + //console.log("7, data:", data); + var windowAvailable = !errorData; + // console.log(errorData); if ( - !that._isCurrentWindowSpecifiedTrainingWindow() - && !that.options.experiment.running + windowAvailable && + windowStartTime == that.vars.currentWindowStart ) { - for (var i = 1; i <= that.options.numberOfForwardWindowsToPrefetch; ++i) { - windowsToRequest.push(start_time + i * window_length); - } - for (var i = 1; i <= that.options.numberOfFastForwardWindowsToPrefetch; ++i) { - windowsToRequest.push(start_time + i * that.options.windowJumpSizeFastForwardBackward * window_length); - } - for (var i = 1; i <= that.options.numberOfBackwardWindowsToPrefetch; ++i) { - windowsToRequest.push(start_time - i * window_length); - } - for (var i = 1; i <= that.options.numberOfFastBackwardWindowsToPrefetch; ++i) { - windowsToRequest.push(start_time - i * that.options.windowJumpSizeFastForwardBackward * window_length); - } - } - - windowsToRequest.forEach((windowStartTime) => { - var options = { - recording_name: recording_name, - channels_displayed: that._getChannelsDisplayed(), - start_time: windowStartTime, - window_length: window_length, - target_sampling_rate: that.options.targetSamplingRate, - use_high_precision_sampling: that.options.useHighPrecisionSampling, - }; - that._requestData(options, (data, errorData) => { - var windowAvailable = !errorData; - if (windowAvailable && windowStartTime == that.vars.currentWindowStart) { - that._applyFrequencyFilters(data, (dataFiltered) => { - that.vars.currentWindowData = dataFiltered; - that._populateGraph(that.vars.currentWindowData); - }); - } - if (!that.options.experiment.running) { - switch (windowStartTime) { - case that.vars.currentWindowStart + window_length: - if (that.options.visibleRegion.end === undefined) { - that._setForwardEnabledStatus(windowAvailable); - if (!windowAvailable) { - that._lastWindowReached(); - } - } - break; - case that.vars.currentWindowStart + window_length * that.options.windowJumpSizeFastForwardBackward: - if (that.options.visibleRegion.end === undefined) { - that._setFastForwardEnabledStatus(windowAvailable); - } - break; - case that.vars.currentWindowStart - window_length: - if (that.options.visibleRegion.start === undefined) { - that._setBackwardEnabledStatus(windowAvailable); - } - break; - case that.vars.currentWindowStart - window_length * that.options.windowJumpSizeFastForwardBackward: - if (that.options.visibleRegion.start === undefined) { - that._setFastBackwardEnabledStatus(windowAvailable); - } - break; - } - } - }); - }); - }, - - jumpToEpochWithStartTime: function(epochStartTimeInSeconds) { - var that = this; - if (isNaN(epochStartTimeInSeconds) || epochStartTimeInSeconds === undefined) { - console.error('Cannot jump to epoch with start time', epochStartTimeInSeconds); - return; - } - that._switchToWindow(that.options.recordingName, epochStartTimeInSeconds, that.options.windowSizeInSeconds); - }, - - getCurrentWindowStartReactive: function() { - var that = this; - return that.vars.currentWindowStartReactive.get(); - }, - - _reloadCurrentWindow: function() { - var that = this; - that._switchToWindow(that.options.recordingName, that.vars.currentWindowStart, that.options.windowSizeInSeconds); - }, - - _reinitChart: function() { - var that = this; - if (that.vars.chart) { - that.vars.chart.destroy(); + that._applyFrequencyFilters(data, (dataFiltered) => { + that.vars.currentWindowData = dataFiltered; + that._populateGraph(that.vars.currentWindowData); + }); } - that.vars.chart = undefined; - that.vars.annotationsLoaded = false; - that.vars.annotationsCache = {}; - that._reloadCurrentWindow(); - }, - - _getProgressInPercent: function() { - var that = this; - if (!that._isVisibleRegionDefined()) return; - var windowSize = that.options.windowSizeInSeconds; - var start = windowSize * Math.ceil(that.options.visibleRegion.start / windowSize); - var end = windowSize * Math.floor((that.options.visibleRegion.end - windowSize) / windowSize); - if (!that._areTrainingWindowsSpecified()) { - var progress = (that.vars.currentWindowStart - start + windowSize) / (end - start + 2 * windowSize); - } - else { - var numberOfTrainingWindows = that._getNumberOfTrainingWindows(); - var numberOfWindowsInVisibleRegion = Math.floor((end - start) / windowSize); - var numberOfWindowsTotal = numberOfWindowsInVisibleRegion + numberOfTrainingWindows; - var currentWindowIndex = that.vars.currentTrainingWindowIndex; - var progress = (currentWindowIndex + 1) / (numberOfWindowsTotal + 2); - } - var progressInPercent = Math.ceil(progress * 100); - return progressInPercent; - }, - - _switchBackToLastActiveWindow: function() { - var that = this; - if (!that.vars.lastActiveWindowStart) { - that.vars.lastActiveWindowStart = 0; - } that._switchToWindow(that.options.recordingName, that.vars.lastActiveWindowStart, that.options.windowSizeInSeconds); - - }, - - _requestData: function(options, callback) { - var that = this; - var identifierKey = that._getIdentifierKeyForDataRequest(options); - var noDataError = 'No data available for window with options ' + JSON.stringify(options); - if (options.start_time < 0) { - that.vars.windowsCache[identifierKey] = false; - } - else if (options.start_time > that.vars.recordingMetadata.LengthInSeconds) { - that.vars.windowsCache[identifierKey] = false; - } - - if (that.vars.windowsCache[identifierKey] === false) { - if (callback) { - callback(null, noDataError); - } - return; - } - - if (that.vars.windowsCache[identifierKey]) { - if (that.vars.windowsCache[identifierKey].data && callback) { - callback(that.vars.windowsCache[identifierKey].data); - } - return; - } - - const numSecondsToPadBeforeAndAfter = 2; - - const optionsPadded = JSON.parse(JSON.stringify(options)); - optionsPadded.start_time -= numSecondsToPadBeforeAndAfter; - optionsPadded.start_time = Math.max(0, optionsPadded.start_time); - const numSecondsPaddedBefore = options.start_time - optionsPadded.start_time; - optionsPadded.window_length = options.window_length + numSecondsPaddedBefore + numSecondsToPadBeforeAndAfter; + that._displayCrosshair(that.vars.crosshairPosition); + if (!that.options.experiment.running) { + if (that._isInNoTimelockMode()) { + that._setForwardEnabledStatus(false); + that._setFastForwardEnabledStatus(false); + that._setBackwardEnabledStatus(false); + that._setFastBackwardEnabledStatus(false); + } else { + // enable/disable the forward backward buttons according to the current position + - Meteor.call('get.edf.data', optionsPadded, (error, data) => { - if (error) { - console.log(error.message); - callback(null, error.message); - return; - } - if (!that._isDataValid(data)) { - that.vars.windowsCache[identifierKey] = false; - if (callback) { - callback(null, noDataError); - } - } - else { - that.vars.windowsCache[identifierKey].data = that._transformData(data, numSecondsPaddedBefore, options.window_length, numSecondsToPadBeforeAndAfter); - if (callback) { - callback(that.vars.windowsCache[identifierKey].data); + switch (windowStartTime) { + case that.vars.currentWindowStart + window_length: + if (that.options.visibleRegion.end === undefined) { + //console.log('winAva:', windowAvailable); + that._setForwardEnabledStatus(windowAvailable); + if (!windowAvailable) { + that._lastWindowReached(); + } } - } - }); - - that.vars.windowsCache[identifierKey] = { - request: 'placeholder' - }; - }, + + case that.vars.currentWindowStart + + window_length * that.options.windowJumpSizeFastForwardBackward: + if (that.options.visibleRegion.end === undefined) { - _transformData: function(input, numSecondsPaddedBefore, numSecondsDataOfInterest, numSecondsPaddedAfter) { - var that = this; - var channels = []; - var channelAudioRepresentations = {}; - var channelNumSamples = {}; - var samplingRate = input.sampling_rate; - for (var name in input.channel_values) { - var values = input.channel_values[name]; - var offlineCtx = new OfflineAudioContext(1, values.length, that.vars.audioContextSampleRate); - var audioBuffer = offlineCtx.createBuffer(1, values.length, offlineCtx.sampleRate); - var scaleFactorFrequency = offlineCtx.sampleRate / samplingRate; - var scaleFactorAmplitude = values.map(Math.abs).reduce((a, b) => Math.max(a, b)); - var valuesScaled = values; - if (scaleFactorAmplitude != 0) { - valuesScaled = values.map(v => v / scaleFactorAmplitude); - } - audioBuffer.copyToChannel(valuesScaled, 0, 0); - channelAudioRepresentations[name] = { - buffer: audioBuffer, - scaleFactors: { - frequency: scaleFactorFrequency, - amplitude: scaleFactorAmplitude, - }, - }; - var numSamplesPaddedBefore = numSecondsPaddedBefore * samplingRate; - var numSamplesDataOfInterest = Math.min(numSecondsDataOfInterest * samplingRate, values.length - numSamplesPaddedBefore); - var numSamplesPaddedAfter = values.length - numSamplesPaddedBefore - numSamplesDataOfInterest; - channelNumSamples[name] = { - paddedBefore: numSamplesPaddedBefore, - dataOfInterest: numSamplesDataOfInterest, - paddedAfter: numSamplesPaddedAfter, - }; - } - for (var i = 0; i < input.channel_order.length; ++i) { - var name = input.channel_order[i]; - var channel = { - name: name, - audio: channelAudioRepresentations[name], - numSamples: channelNumSamples[name], - } - channel.valuesPadded = new Float32Array(channel.audio.buffer.length - channel.numSamples.paddedBefore); - channels.push(channel); - } - var output = { - channels: channels, - sampling_rate: input.sampling_rate - } - return output; - }, - - _applyFrequencyFilters: function(data, callback) { - var that = this; - var numRemainingChannelsToFilter = data.channels.length; - var maxDetectableFrequencyInHz = data.sampling_rate / 2; - var frequencyFilters = that.vars.frequencyFilters || []; - data.channels.forEach((channel, c) => { - var staticFrequencyFilters = that._getStaticFrequencyFiltersForChannel(channel); - var buffer = channel.audio.buffer; - var offlineCtx = new OfflineAudioContext(1, buffer.length, that.vars.audioContextSampleRate); - var valuesFiltered = data.channels[c].valuesFilteredHolder; - var bufferSource = offlineCtx.createBufferSource(); - bufferSource.buffer = buffer; - var currentNode = bufferSource; - const allFrequencyFilters = staticFrequencyFilters.concat(frequencyFilters); - allFrequencyFilters.forEach((frequencyFilter) => { - var filterType = frequencyFilter.type; - var frequencyInHz = parseFloat(frequencyFilter.selectedValue); - if (isNaN(frequencyInHz) || frequencyInHz === undefined || frequencyInHz <= 0) { - return; + that._setFastForwardEnabledStatus(windowAvailable); } - if (frequencyInHz > maxDetectableFrequencyInHz) { - // console.log('Not applying ' + filterType + ' filter with frequency ' + frequencyInHz + 'Hz as it exceeds the maximum detectable frequency of ' + maxDetectableFrequencyInHz + 'Hz for this data, sampled at ' + data.sampling_rate + 'Hz.'); - return; + // break; + case that.vars.currentWindowStart - window_length: + if (that.options.visibleRegion.start === undefined) { + that._setBackwardEnabledStatus(windowAvailable); } - // console.log('Applying ' + filterType + ' filter with frequency ' + frequencyInHz + 'Hz.'); - var filterNode = offlineCtx.createBiquadFilter(); - filterNode.type = filterType; - var scaleFactorFrequency = channel.audio.scaleFactors.frequency; - filterNode.frequency.value = frequencyInHz * scaleFactorFrequency; - // If filter results are not as expected, - // double check settings for Q factor: - // - https://developer.mozilla.org/en-US/docs/Web/API/BiquadFilterNode - // - https://electronics.stackexchange.com/questions/221887/does-q-factor-matter-for-low-pass-and-high-pass-filters - // - https://stackoverflow.com/questions/33540440/bandpass-filter-which-frequency-and-q-value-to-represent-frequency-range - // filterNode.Q.value = 1 (default) - currentNode.connect(filterNode); - currentNode = filterNode; - }); - currentNode.connect(offlineCtx.destination); - bufferSource.start(); - offlineCtx.startRendering().then((bufferFiltered) => { - bufferFiltered.copyFromChannel(channel.valuesPadded, 0, channel.numSamples.paddedBefore); - channel.values = channel.valuesPadded.subarray(0, channel.numSamples.dataOfInterest); - var scaleFactorAmplitude = channel.audio.scaleFactors.amplitude; - if (scaleFactorAmplitude != 0) { - channel.values = channel.values.map(v => v * scaleFactorAmplitude); - } - --numRemainingChannelsToFilter; - if (numRemainingChannelsToFilter <= 0) { - callback(data); + // break; + case that.vars.currentWindowStart - + window_length * that.options.windowJumpSizeFastForwardBackward: + if (that.options.visibleRegion.start === undefined) { + that._setFastBackwardEnabledStatus(windowAvailable); } - }).catch((error) => { - console.error('Rendering failed', error); - }); - }); - }, + // break; + } + } + } + }); + }); + }, + + jumpToEpochWithStartTime: function (epochStartTimeInSeconds) { + var that = this; + if ( + isNaN(epochStartTimeInSeconds) || + epochStartTimeInSeconds === undefined + ) { + console.error( + "Cannot jump to epoch with start time", + epochStartTimeInSeconds + ); + return; + } + that._switchToWindow( + that.options.allRecordings, + epochStartTimeInSeconds, + that.vars.xAxisScaleInSeconds + ); + }, + + getCurrentWindowStartReactive: function () { + var that = this; + return that.vars.currentWindowStartReactive.get(); + }, + + _reloadCurrentWindow: function () { + //console.log("_reloadCurrentWindow"); + var that = this; + // reloads the current window by "switching" to it using the current window start time, the current x axis scale and the current recordings + that._switchToWindow( + that.options.allRecordings, + that.vars.currentWindowStart, + that.vars.xAxisScaleInSeconds + ); + }, + + _reinitChart: function () { + //function that reinitializes the charts + var that = this; + if (that.vars.chart) { + that.vars.chart.destroy(); //destroys the chart + } - _getIdentifierObjectForDataRequest: function(options) { - var options = options || {}; - var relevantOptions = [ - 'recording_name', - 'start_time', - 'window_length', - 'channels_displayed', - ]; - var identifierObject = {}; - for (var i = 0; i < relevantOptions.length; ++i) { - identifierObject[relevantOptions[i]] = options[relevantOptions[i]]; - } - return identifierObject; - }, + // deletes all annotations currently loaded and cached + that.vars.chart = undefined; + that.vars.annotationsLoaded = false; + that.vars.annotationsCache = {}; + + //console.log("_reinitChart"); + that._reloadCurrentWindow(); + }, + + _getProgressInPercent: function () { + var that = this; + if (!that._isVisibleRegionDefined()) return; + var windowSize = that.vars.xAxisScaleInSeconds; + var start = + windowSize * Math.ceil(that.options.visibleRegion.start / windowSize); + var end = + windowSize * + Math.floor((that.options.visibleRegion.end - windowSize) / windowSize); + if (!that._areTrainingWindowsSpecified()) { + var progress = + (that.vars.currentWindowStart - start + windowSize) / + (end - start + 2 * windowSize); + } else { + var numberOfTrainingWindows = that._getNumberOfTrainingWindows(); + var numberOfWindowsInVisibleRegion = Math.floor( + (end - start) / windowSize + ); + var numberOfWindowsTotal = + numberOfWindowsInVisibleRegion + numberOfTrainingWindows; + var currentWindowIndex = that.vars.currentTrainingWindowIndex; + var progress = (currentWindowIndex + 1) / (numberOfWindowsTotal + 2); + } + var progressInPercent = Math.ceil(progress * 100); + return progressInPercent; + }, + + _switchBackToLastActiveWindow: function () { + var that = this; + if (!that.vars.lastActiveWindowStart) { + that.vars.lastActiveWindowStart = 0; + } + that._switchToWindow( + that.options.allRecordings, + that.vars.lastActiveWindowStart, + that.vars.xAxisScaleInSeconds + ); + }, + + _requestData: function (options, callback) { + var that = this; + // console.log(that.vars.allRecordings); + // identifierKey includes: + // 'recordings' + // 'start_time' + // 'window_length' + // 'channels_displayed' + var identifierKey = that._getIdentifierKeyForDataRequest(options); + //console.log("identifierKey", identifierKey); + var noDataError = + "No data available for window with options " + JSON.stringify(options); + + if (options.start_time < 0) { + that.vars.windowsCache[identifierKey] = false; + //console.log("options.start_time < 0"); + } else if (options.start_time > that.vars.recordingLengthInSeconds) { + that.vars.windowsCache[identifierKey] = false; + //console.log("options.start_time > that.vars.recordingLengthInSeconds"); + } + if (that.vars.windowsCache[identifierKey] === false) { + if (callback) { + //console.log("that.vars.windowsCache[identifierKey] === false"); - _getIdentifierKeyForDataRequest: function(options) { - var that = this; - var identifierKey = JSON.stringify(that._getIdentifierObjectForDataRequest(options)); - return identifierKey; - }, + callback(null, noDataError); + } + return; + } - _isDataValid: function(data) { - if (!data) return false; - if (!data.sampling_rate) return false; - if (!data.channel_order) return false; - if (!data.channel_values) return false; - for (let c = 0; c < data.channel_values.length; ++c) { - if (data.channel_values[c].length == 0) { - return false; - } - } - return true; - }, + var reprint = that.vars.reprint; + //console.log("reprint", reprint); + if (reprint === 1 || that._isInNoTimelockMode()) { + that.vars.windowsCache = {}; + } else if ( + that.vars.windowsCache[identifierKey] && + that.vars.windowsCache[identifierKey].data && + callback + ) { + callback(that.vars.windowsCache[identifierKey].data); + return; + } + const numSecondsToPadBeforeAndAfter = 2; + + const optionsPadded = JSON.parse(JSON.stringify(options)); + + optionsPadded.start_time -= numSecondsToPadBeforeAndAfter; + + optionsPadded.start_time = Math.max(0, optionsPadded.start_time); + + const numSecondsPaddedBefore = + options.start_time - optionsPadded.start_time; + + optionsPadded.window_length = + options.window_length + + numSecondsPaddedBefore + + numSecondsToPadBeforeAndAfter; + + optionsPadded.low_resolution_data = + that._isInNoTimelockMode() || + optionsPadded.window_length > + 300 + numSecondsPaddedBefore + numSecondsToPadBeforeAndAfter; + + Meteor.call("get.edf.data", optionsPadded, (error, data) => { + if (error) { + //console.log(error.message); + callback(null, error.message); + return; + } + //console.log("edf.data", data); + if (!that._isDataValid(data)) { + that.vars.windowsCache[identifierKey] = false; + + if (callback) { + callback(null, noDataError); + } + } else { + that.vars.windowsCache[identifierKey] = {}; + // transform the data before storing or displaying them + that.vars.windowsCache[identifierKey].data = that._transformData( + data, + numSecondsPaddedBefore, + options.window_length, + numSecondsToPadBeforeAndAfter + ); + that.vars.reprint = 0; + if (callback) { + callback(that.vars.windowsCache[identifierKey].data); + } + } + }); + + that.vars.windowsCache[identifierKey] = { + request: "placeholder", + }; + }, + + _transformData: function ( + input, + numSecondsPaddedBefore, + numSecondsDataOfInterest, + numSecondsPaddedAfter + ) { + // //console.log("inside here"); + var that = this; + var channels = []; + var options = that.vars.valueOptions; + if (options == null) { + options = 0; + } - _populateGraph: function(data) { - /* plot all of the points to the chart */ - var that = this; - // if the chart object does not yet exist, because the user is loading the page for the first time - // or refreshing the page, then it's necessary to initialize the plot area - if (!that.vars.chart) { - // if this is the first pageload, then we'll need to load the entire - that._initGraph(data); - // if the plot area has already been initialized, simply update the data displayed using AJAX calls - } - that._updateChannelDataInSeries(that.vars.chart.series, data); - that.vars.chart.xAxis[0].setExtremes(that.vars.currentWindowStart, that.vars.currentWindowStart + that.options.windowSizeInSeconds, false, false); - that.vars.chart.redraw(); // efficiently redraw the entire window in one go - var chart = that.vars.chart; - // use the chart start/end so that data and annotations can never - // get out of synch - that._refreshAnnotations(); - that._renderChannelSelection(); - that._updateBookmarkCurrentPageButton(); - that.vars.currentWindowStartReactive.set(that.vars.currentWindowStart); - }, + var channelAudioRepresentations = {}; + var channelNumSamples = {}; + var samplingRate = input.sampling_rate; + // console.log("samplingRate", samplingRate); + // console.log(that.options.targetSamplingRate); + // for each dataId in the channelvalues array + for (var dataId in input.channel_values) { + // console.log(that._getCurrentMontage()); + //console.log( + // "===============================================================================================" + // ); + for (var name in input.channel_values[dataId]) { + var values = input.channel_values[dataId][name]; + // console.log("Channel Name:" + name); + // console.log(values); + ////console.log(that.vars.audioContextSampleRate); + var offlineCtx = new OfflineAudioContext( + 1, + values.length, + that.vars.audioContextSampleRate + ); + var audioBuffer = offlineCtx.createBuffer( + 1, + values.length, + offlineCtx.sampleRate + ); + var scaleFactorFrequency = offlineCtx.sampleRate / samplingRate; + + //gets the max value in the values array + var scaleFactorAmplitude = Math.max(...values.map(Math.abs)); + + var maxIn = scaleFactorAmplitude; + + var valuesLength = values.length; + + // var y95 = 0; + // var y05 = 0; + // scaleFactorAmplitude = values + // .map(Math.abs) + // .reduce((a, b) => Math.max(a, b)); //that._findMeanPercentile(0.95,0.005,values, valuesLength); + // ////console.log(name); + + // is the average of the values + var avg = values.reduce((a, b) => a + b) / valuesLength; + avg = Math.abs(avg); + ////console.log(avg); + + // adds up the difference of the values from the average + var changeVal = values.reduce((a, b) => a + Math.abs(avg - b)); + + ////console.log(changeVal); + + // //console.log(name); + // //console.log(scaleFactorAmplitude) ; + + // scaleValueChange = Math.pow(scaleFactorAmplitude, 2); + var scaleValueChange = maxIn * scaleFactorAmplitude; + + // checks if there are negative and positive values in the values array + const hasNegativeValues = values.some((v) => v <= 0); + const hasPositiveValues = values.some((v) => v >= 0); + + // if the array has only negative or positive values, then we add/subtract the avg + if (!(hasNegativeValues && hasPositiveValues)) { + if (hasPositiveValues) { + values = values.map((v) => v - avg); + } else { + values = values.map((v) => v + avg); + } + } + + if (scaleFactorAmplitude != 0) { + // //console.log(name); + // gets every value and divides it by the scaleFactorAmplitude, the max value in the values array + // console.log(name); + // console.log(scaleFactorAmplitude); + + valuesScaled = values.map((v) => v / scaleFactorAmplitude); + // //console.log(valuesScaled); + } + + // console.log(valuesScaled); + audioBuffer.copyToChannel(valuesScaled, 0, 0); + + var scaleFault = 0; + if (options == 0) { + // if the amplitude is not scaled (default amplitude) + switch (name) { + case "F4-A1": + scaleFault = 1; + break; + + case "C4-A1": + scaleFault = 1; + break; + case "O2-A1": + scaleFault = 1; + break; + case "LOC-A2": + scaleFault = 1; + break; + case "ROC-A1": + scaleFault = 1; + break; + case "Chin 1-Chin 2": + scaleFault = 500; + break; + // case "eeg-ch1": + // scaleFault = 0.05; + // break; + // case "eeg-ch2": + // scaleFault = 0.5; + // break; + // case "eeg-ch3": + // scaleFault = 0.05; + // break; + // case "eeg-ch4": + // scaleFault = 0.05; + // break; + + case "ECG": + scaleFault = 100; + break; + case "Leg/L": + scaleFault = 50; + break; + case "Leg/R": + scaleFault = 50; + break; + case "Snore": + scaleFault = 600; + break; + case "Airflow": + scaleFault = 100; + break; + case "Nasal Pressure": + scaleFault = 100; + break; + case "Thor": + scaleFault = 500; + break; + + case "Abdo": + scaleFault = 100; + break; + case "SpO2": + scaleFault = 1.5; + break; + case "Pleth": + scaleFault = 0.03; + break; + case "Accl Pitch": + scaleFault = 10; + break; + case "Accl Roll": + scaleFault = 10; + break; + case "Resp Effort": + scaleFault = 100; + break; + case "HR(bpm)": + scaleFault = 3.5; + break; + case "SpO2(%)": + scaleFault = 3.5; + break; + case "PI(%)": + scaleFault = 85; + break; + case "PAT(ms)": + scaleFault = 0.1; + break; + case "Chest Temp(A C)": + scaleFault = 10; + break; + case "Limb Temp(A C)": + scaleFault = 10; + break; + case "Temp": + scaleFault = 100; + break; + case "light": + scaleFault = 100; + break; + case "ENMO": + scaleFault = 100; + break; + case "z-angle": + scaleFault = 100; + break; + } + scaleFactorAmplitude = scaleFactorAmplitude * scaleFault; + sessionStorage.setItem( + dataId + name + "scaleFactorAmplitude", + scaleFault + ); + + ////console.log(name); + // //console.log(scaleFactorAmplitude); + } else if (options == 2) { + ////console.log(changeVal); + // //console.log(scaleFactorAmplitude); + while ( + changeVal > 0 && + scaleValueChange > 0 && + changeVal < 10000 && + scaleValueChange * 10 < 500 + ) { + // //console.log(changeVal); + scaleFactorAmplitude = scaleFactorAmplitude * 3; + //scaleValueChange = scaleFactorAmplitude*maxIn; + // //console.log(scaleFactorAmplitude*maxIn); + changeVal = changeVal * 7; + } + // //console.log(name); + // //console.log(scaleFactorAmplitude); + sessionStorage.setItem( + dataId + name + "scaleFactorAmplitude", + scaleFactorAmplitude + ); + } else if (options == 1) { + //if the amplitude has to be scaled + //requiredName = "Thor"//sessionStorage.getItem("requiredName"); + let channelOnChange = that.vars.channelAmplitudeOnChange; + // //console.log(requiredName); + var scaleFault = sessionStorage.getItem( + dataId + name + "scaleFactorAmplitude" + ); + var oncecheck = that.vars.increaseOnce; + if ( + name === channelOnChange.name && + dataId === channelOnChange.dataId && + oncecheck == 1 + ) { + scaleFault = scaleFault * 5; + that.vars.increaseOnce = 0; + } + + sessionStorage.setItem( + dataId + name + "scaleFactorAmplitude", + scaleFault + ); + //sessionStorage.setItem(("requiredName"),""); + scaleFactorAmplitude = scaleFactorAmplitude * scaleFault; + // //console.log(sessionStorage.setItem((name+"scaleFactorAmplitude"), scaleFault)); + // //console.log(scaleFault); + ////console.log(scaleFactorAmplitude); + } else if (options == -1) { + //requiredName = "Thor"//sessionStorage.getItem("requiredName"); + var scaleFault = sessionStorage.getItem( + dataId + name + "scaleFactorAmplitude" + ); + let channelOnChange = that.vars.channelAmplitudeOnChange; + var oncecheck = that.vars.increaseOnce; + if ( + name === channelOnChange.name && + dataId === channelOnChange.dataId && + oncecheck == 1 + ) { + scaleFault = scaleFault / 5; + that.vars.increaseOnce = 0; + } + // //console.log(sessionStorage.setItem((name+"scaleFactorAmplitude"), scaleFault)); + ////console.log(scaleFault); + sessionStorage.setItem( + dataId + name + "scaleFactorAmplitude", + scaleFault + ); + scaleFactorAmplitude = scaleFactorAmplitude * scaleFault; + } + /* + if(scaleValueChange > 500 ){ + scaleValueChange = 500/maxIn; + scaleFactorAmplitude = scaleValueChange; + } + */ + if (!channelAudioRepresentations[dataId]) + channelAudioRepresentations[dataId] = {}; + channelAudioRepresentations[dataId][name] = { + buffer: audioBuffer, + scaleFactors: { + frequency: scaleFactorFrequency, + amplitude: scaleFactorAmplitude, + }, + }; + var numSamplesPaddedBefore = numSecondsPaddedBefore * samplingRate; + var numSamplesDataOfInterest = Math.min( + numSecondsDataOfInterest * samplingRate, + values.length - numSamplesPaddedBefore + ); - _updateChannelDataInSeries: function(series, data) { - var that = this; - var channels = data.channels; - var xValues = Array.from(data.channels[0].values.map(function(value, v) { - return that.vars.currentWindowStart + v / data.sampling_rate; - })); - var recordingEndInSecondsSnapped = that._getRecordingEndInSecondsSnapped(); - return channels.map(function (channel, c) { - var flipFactor = that._getFlipFactorForChannel(channel); - var gain = that._getGainForChannelIndex(c); - if (gain === undefined) { - gain = 1.0; - } - var flipFactorAndGain = flipFactor * gain; - var offsetPreScale = that._getOffsetForChannelPreScale(channel); - var offsetPostScale = that._getOffsetForChannelIndexPostScale(c); - samplesScaledAndOffset = channel.values.map(function(value, v) { - return (value + offsetPreScale) * flipFactorAndGain + offsetPostScale; - }); - var seriesData = xValues.map(function(x, i) { - return [x, samplesScaledAndOffset[i]]; - }); - seriesData.unshift([-that.options.windowSizeInSeconds, offsetPostScale]); - seriesData.push([recordingEndInSecondsSnapped, offsetPostScale]); - series[c].setData(seriesData, false, false, false); - }); - }, + var numSamplesPaddedAfter = + values.length - numSamplesPaddedBefore - numSamplesDataOfInterest; + if (!channelNumSamples[dataId]) channelNumSamples[dataId] = {}; + channelNumSamples[dataId][name] = { + paddedBefore: numSamplesPaddedBefore, + dataOfInterest: numSamplesDataOfInterest, + paddedAfter: numSamplesPaddedAfter, + }; + } + } - _initSeries: function(data) { - var that = this; - var samplingRate = data.sampling_rate; - var channels = data.channels; - var recordingEndInSecondsSnapped = that._getRecordingEndInSecondsSnapped(); - return channels.map(function (channel, c) { - var offset = that._getOffsetForChannelIndexPostScale(c); - return { - name: channel.name, - data: [[0, offset], [recordingEndInSecondsSnapped, offset]], - }; + for (var i = 0; i < input.channel_order.length; ++i) { + var name = input.channel_order[i].name; + let dataId = input.channel_order[i].dataId; + if (channelNumSamples[dataId][name]) { + var channel = { + name: name, + dataId: dataId, + audio: channelAudioRepresentations[dataId][name], + numSamples: channelNumSamples[dataId][name], + }; + let arrayLength = Math.max( + channel.audio.buffer.length - channel.numSamples.paddedBefore, + 1 + ); + channel.valuesPadded = new Float32Array(arrayLength); + channels.push(channel); + } + } + var output = { + channels: channels, + sampling_rate: input.sampling_rate, + }; + return output; + }, + + _findMeanPercentile: function (top, bottom, values, thisCounting) { + var sortValues = values; + sortValues.sort(); + var upside = top * thisCounting; + var downside = bottom * thisCounting; + + var topValue = sortValues[Math.ceil(upside)]; + var bottomValue = sortValues[Math.floor(downside)]; + + return (topValue + bottomValue) / 2, topValue, bottomValue; + }, + + _applyFrequencyFilters: function (data, callback) { + var that = this; + var numRemainingChannelsToFilter = data.channels.length; + var maxDetectableFrequencyInHz = data.sampling_rate / 2; + var frequencyFilters = that.vars.frequencyFilters || []; + data.channels.forEach((channel, c) => { + var staticFrequencyFilters = + that._getStaticFrequencyFiltersForChannel(channel); + var buffer = channel.audio.buffer; + var offlineCtx = new OfflineAudioContext( + 1, + buffer.length, + that.vars.audioContextSampleRate + ); + var valuesFiltered = data.channels[c].valuesFilteredHolder; + var bufferSource = offlineCtx.createBufferSource(); + bufferSource.buffer = buffer; + var currentNode = bufferSource; + const allFrequencyFilters = + staticFrequencyFilters.concat(frequencyFilters); + allFrequencyFilters.forEach((frequencyFilter) => { + var filterType = frequencyFilter.type; + var frequencyInHz = parseFloat(frequencyFilter.selectedValue); + if ( + isNaN(frequencyInHz) || + frequencyInHz === undefined || + frequencyInHz <= 0 + ) { + return; + } + if (frequencyInHz > maxDetectableFrequencyInHz) { + // //console.log('Not applying ' + filterType + ' filter with frequency ' + frequencyInHz + 'Hz as it exceeds the maximum detectable frequency of ' + maxDetectableFrequencyInHz + 'Hz for this data, sampled at ' + data.sampling_rate + 'Hz.'); + return; + } + // //console.log('Applying ' + filterType + ' filter with frequency ' + frequencyInHz + 'Hz.'); + var filterNode = offlineCtx.createBiquadFilter(); + filterNode.type = filterType; + var scaleFactorFrequency = channel.audio.scaleFactors.frequency; + filterNode.frequency.value = frequencyInHz * scaleFactorFrequency; + // If filter results are not as expected, + // double check settings for Q factor: + // - https://developer.mozilla.org/en-US/docs/Web/API/BiquadFilterNode + // - https://electronics.stackexchange.com/questions/221887/does-q-factor-matter-for-low-pass-and-high-pass-filters + // - https://stackoverflow.com/questions/33540440/bandpass-filter-which-frequency-and-q-value-to-represent-frequency-range + // filterNode.Q.value = 1 (default) + currentNode.connect(filterNode); + currentNode = filterNode; + }); + currentNode.connect(offlineCtx.destination); + bufferSource.start(); + offlineCtx + .startRendering() + .then((bufferFiltered) => { + bufferFiltered.copyFromChannel( + channel.valuesPadded, + 0, + channel.numSamples.paddedBefore + ); + channel.values = channel.valuesPadded.subarray( + 0, + channel.numSamples.dataOfInterest + ); + var scaleFactorAmplitude = channel.audio.scaleFactors.amplitude; + if (scaleFactorAmplitude != 0) { + channel.values = channel.values.map( + (v) => v * scaleFactorAmplitude + ); + // //console.log(buffer); + // //console.log(channel.values); + } + --numRemainingChannelsToFilter; + if (numRemainingChannelsToFilter <= 0) { + callback(data); + } + }) + .catch((error) => { + console.error("Rendering failed", error); }); - }, + }); + }, + + _getIdentifierObjectForDataRequest: function (options) { + var options = options || {}; + var relevantOptions = [ + "recordings", + "start_time", + "window_length", + "channels_displayed", + ]; + var identifierObject = {}; + for (var i = 0; i < relevantOptions.length; ++i) { + identifierObject[relevantOptions[i]] = options[relevantOptions[i]]; + } + return identifierObject; + }, + + _getIdentifierKeyForDataRequest: function (options) { + var that = this; + var identifierKey = JSON.stringify( + that._getIdentifierObjectForDataRequest(options) + ); + return identifierKey; + }, + + _isDataValid: function (data) { + if (!data) return false; + if (!data.sampling_rate) return false; + if (!data.channel_order) return false; + if (!data.channel_values) return false; + for (let dataId in data.channel_values) { + if (Object.keys(data.channel_values[dataId]).length == 0) return false; + } + return true; + }, + + _populateGraph: function (data) { + /* plot all of the points to the chart */ + var that = this; + + // if the chart object does not yet exist, because the user is loading the page for the first time + // or refreshing the page, then it's necessary to initialize the plot area + if (!that.vars.chart) { + // if this is the first pageload, then we'll need to load the entire + console.time("_initGraph"); + that._initGraph(data); + //console.log("[[time end]]"); + console.timeEnd("_initGraph"); + // if the plot area has already been initialized, simply update the data displayed using AJAX calls + + that._updateChannelDataInSeries(that.vars.chart.series, data); + + // console.log("here we scale all channels to screen"); + that._scaleAllToScreen(); + that.vars.chart.redraw(); + + + that._addChangePointLabelFixed(); + // see http://jsfiddle.net/ajxyuax2/1/ + } - _getRecordingEndInSecondsSnapped: function() { - var that = this; - return (Math.ceil(that.vars.recordingMetadata.LengthInSeconds / that.options.windowSizeInSeconds) + 1) * that.options.windowSizeInSeconds; - }, + // updates the data that will be displayed in the chart + // by storing the new data in this.vars.chart.series + that._updateChannelDataInSeries(that.vars.chart.series, data); + + // sets the min and max values for the chart + that.vars.chart.xAxis[0].setExtremes( + that.vars.currentWindowStart, + that.vars.currentWindowStart + that.vars.xAxisScaleInSeconds, + false, + false + ); + + that.vars.recordScalingFactors = false; + that.vars.recordPolarity = false; + that.vars.recordTranslation = false; + + // checks if the object is empty + if (!that._objectIsEmpty(that.vars.scalingFactors)) { + for (const index in that.vars.scalingFactors) { + that._customAmplitude( + index, + 100 * (that.vars.scalingFactors[index] - 1) + ); + // console.log("scaling after page change"); + // console.log(that.vars.chart.series[index].yData); + } - _getLatestPossibleWindowStartInSeconds: function() { - var that = this; - return Math.floor(that.vars.recordingMetadata.LengthInSeconds / that.options.windowSizeInSeconds) * that.options.windowSizeInSeconds; - }, + } - _getClassificationSummaryElement: function() { - var that = this; - return $(that.element).parents('.annotator-edf').find('.classification-summary'); - }, + if (!that._objectIsEmpty(that.vars.translation)) { + for (const index in that.vars.translation) { + that._customTranslation(index, that.vars.translation[index]); + } + } + + if (!that._objectIsEmpty(that.vars.polarity)) { + for (const index in that.vars.polarity) { + that._reversePolarity(index); + } + } - _initGraph: function(data) { - /* This method is called only when the page is first loaded, it sets up the plot area, the + that.vars.recordPolarity = true; + that.vars.recordScalingFactors = true; + that.vars.recordTranslation = true; + + that.vars.chart.redraw(); // efficiently redraw the entire window in one go + + // use the chart start/end so that data and annotations can never + // get out of synch + that._refreshAnnotations(); + that._renderChannelSelection(); + that._updateBookmarkCurrentPageButton(); + that.vars.currentWindowStartReactive.set(that.vars.currentWindowStart); + + that._updateChangePointLabelFixed(); + }, + + //checks if an object is empty + _objectIsEmpty: function (obj) { + return JSON.stringify(obj) === "{}"; + }, + + _updateChannelDataInSeries: function (series, data) { + var that = this; + var channels = data.channels; // gets the channels from the data object + + //gets the xValues for the graph using from the data object i.e the time values + var xValues = Array.from( + data.channels[0].values.map(function (value, index) { + return that.vars.currentWindowStart + index / data.sampling_rate; + }) + ); + + // gets the recording end in seconds snapped to the nearest second + var recordingEndInSecondsSnapped = that._getRecordingEndInSecondsSnapped(); + + return channels.map(function (channel, c) { + // for each channel, we get the channel name (channel) + // and the channel index (c) + + // using them, we get the flipfactor and gain + var flipFactor = that._getFlipFactorForChannel(channel); + var gain = that._getGainForChannelIndex(c); + + if (gain === undefined) { + gain = 1.0; + } + + var flipFactorAndGain = flipFactor * gain; + + // gets some additional information needed to graph using channel and c + var offsetPreScale = that._getOffsetForChannelPreScale(channel); + var offsetPostScale = that._getOffsetForChannelIndexPostScale(c); + + // gets the values + samplesScaledAndOffset = channel.values.map(function (value, v) { + return (value + offsetPreScale) * flipFactorAndGain + offsetPostScale; + }); + + // creates an array that stores all the data + var seriesData = xValues.map(function (x, i) { + return [x, samplesScaledAndOffset[i]]; + }); + + // adds the offset needed to the start of the graph + seriesData.unshift([-that.vars.xAxisScaleInSeconds, offsetPostScale]); + + // adds the offset needed to the end of the graphID + seriesData.push([recordingEndInSecondsSnapped, offsetPostScale]); + + // stores in the series that we input into the funciton, at index c + series[c].setData(seriesData, false, false, false); + }); + }, + + _initSeries: function (data) { + var that = this; + var samplingRate = data.sampling_rate; + var channels = data.channels; + var recordingEndInSecondsSnapped = that._getRecordingEndInSecondsSnapped(); + return channels.map(function (channel, c) { + var offset = that._getOffsetForChannelIndexPostScale(c); + return { + name: channel.name, + custom: { + dataId: channel.dataId, + }, + data: [ + [0, offset], + [recordingEndInSecondsSnapped, offset], + ], + }; + }); + }, + + _getRecordingEndInSecondsSnapped: function () { + var that = this; + return ( + (Math.ceil( + that.vars.recordingLengthInSeconds / that.vars.xAxisScaleInSeconds + ) + + 1) * + that.vars.xAxisScaleInSeconds + ); + }, + + _getLatestPossibleWindowStartInSeconds: function () { + var that = this; + return ( + Math.floor( + that.vars.recordingLengthInSeconds / that.vars.xAxisScaleInSeconds + ) * that.vars.xAxisScaleInSeconds + ); + }, + + _getClassificationSummaryElement: function () { + var that = this; + return $(that.element) + .parents(".annotator-edf") + .find(".classification-summary"); + }, + + _initGraph: function (data) { + /* This method is called only when the page is first loaded, it sets up the plot area, the axis, channel name labels, time formatting and everything else displayed on the plot subsequent changes to this plot to scroll through the signal use the much computationally expensive series update and axis update methods. */ - var that = this; - var channels = data.channels; - - that.vars.graphID = 'time-series-graph-' + that._getUUID(); - var graph = $(that.element).find('.graph'); - graph.children().remove(); - graph.append('
'); - - var classificationSummaryContainer = that._getClassificationSummaryElement(); - if (classificationSummaryContainer.length == 0) { - var classificationSummaryHeight = 0; - } else { - var classificationSummaryHeight = classificationSummaryContainer.height() + parseInt(classificationSummaryContainer.css('margin-top')) + parseInt(classificationSummaryContainer.css('margin-bottom')); - } - var graphHeight = $(that.element).height() - (parseInt(graph.css('padding-top')) + parseInt(graph.css('padding-bottom')) + parseInt(graph.css('margin-top')) + parseInt(graph.css('margin-bottom'))) - classificationSummaryHeight; - var graphContainerOtherChildren = $(that.element).find('.graph_container > *').not(graph); - graphContainerOtherChildren.each(function() { - var child = $(this); - graphHeight -= (child.height() + parseInt(child.css('margin-top')) + parseInt(child.css('margin-bottom'))); - }); - - bookmarkData = []; - const bookmarkedPages = that.options.context.preferences.annotatorConfig.bookmarkedPages; - for (pageKey in bookmarkedPages) { - if (bookmarkedPages[pageKey] === true) { - bookmarkData.push(parseInt(pageKey)); - } - } - bookmarkData.sort((a, b) => a - b); - bookmarkData = bookmarkData.map((pageKey) => { - return [pageKey + that.options.windowSizeInSeconds / 2, 1]; - }); - - that.vars.chart = new Highcharts.Chart({ - boost: { - enabled: true, - seriesThreshold: 1, - }, - chart: { - animation: false, - renderTo: that.vars.graphID, - width: that.options.graph.width, - height: graphHeight, - marginTop: that.options.graph.marginTop, - marginBottom: that.options.graph.marginBottom, - marginLeft: that.options.graph.marginLeft, - marginRight: that.options.graph.marginRight, - backgroundColor: that.options.graph.backgroundColor, - events: { - load: function(event) { - that._setupLabelHighlighting(); - }, - redraw: function(event) { - that._setupLabelHighlighting(); - that._setupYAxisLinesAndLabels(); - } - }, + //console.log("!!!!!!init graph"); + var that = this; + var channels = data.channels; + + that.vars.graphID = "time-series-graph-" + that._getUUID(); + var graph = $(".graph"); + graph.children().remove(); + graph.append( + '
' + ); + + var classificationSummaryContainer = + that._getClassificationSummaryElement(); + if (classificationSummaryContainer.length == 0) { + var classificationSummaryHeight = 0; + } else { + var classificationSummaryHeight = + classificationSummaryContainer.height() + + parseInt(classificationSummaryContainer.css("margin-top")) + + parseInt(classificationSummaryContainer.css("margin-bottom")); + } + var graphHeight = + $(that.element).height() - + (parseInt(graph.css("padding-top")) + + parseInt(graph.css("padding-bottom")) + + parseInt(graph.css("margin-top")) + + parseInt(graph.css("margin-bottom"))) - + classificationSummaryHeight; + var graphContainerOtherChildren = $(that.element) + .find(".graph_container > *") + .not(graph); + graphContainerOtherChildren.each(function () { + var child = $(this); + graphHeight -= + child.height() + + parseInt(child.css("margin-top")) + + parseInt(child.css("margin-bottom")); + }); + + bookmarkData = []; + const bookmarkedPages = + that.options.context.preferences.annotatorConfig.bookmarkedPages; + for (pageKey in bookmarkedPages) { + if (bookmarkedPages[pageKey] === true) { + bookmarkData.push(parseInt(pageKey)); + } + } + bookmarkData.sort((a, b) => a - b); + bookmarkData = bookmarkData.map((pageKey) => { + return [pageKey + that.vars.xAxisScaleInSeconds / 2, 1]; + }); + + (myFunction = function () { + var popup = document.getElementById("myPopup"); + popup.classList.toggle("show"); + }), + (that.vars.chart = new Highcharts.chart({ + boost: { + // speed up + enabled: true, + seriesThreshold: 1, + }, + chart: { + animation: false, + renderTo: that.vars.graphID, + width: that.options.graph.width, + height: graphHeight, + marginTop: that.options.graph.marginTop, + marginBottom: that.options.graph.marginBottom, + marginLeft: that.options.graph.marginLeft, + marginRight: that.options.graph.marginRight, + backgroundColor: that.options.graph.backgroundColor, + events: { + load: function (event) { + that._setupLabelHighlighting(); }, - credits: { - enabled: false + redraw: function (event) { + that._setupLabelHighlighting(); + that._setupYAxisLinesAndLabels(); }, - title: { - text: '' + }, + //TODO: change how chart zooms, does not work well with annotations + // zoomType: "xy", + + resetZoomButton: { + position: { + align: "left", + verticalAlign: "bottom", + x: -87.5, + y: 25, }, - tooltip: { - enabled: false + relativeTo: "plotBox", + }, + }, + credits: { + enabled: false, + }, + title: { + text: that.options.recordingName, + }, + tooltip: { + enabled: true, + formatter: function () { + var x = this.x; + try { + var annotation = that.vars.universalChangePointAnnotationsCache[ + that._getUniversalAnnotationIndexByXVal(x) + ]; + var label; + if (annotation !== undefined) { + label = annotation.metadata.annotationLabel; + } + // console.log(label); + + return "Time Stamp: " + "" + this.x + "" + " s" + '
' + + "Previous Universal Change Point:" + "
" + + "" + label + ""; + } catch { + return "Error"; + } + }, + }, + annotations: null, + plotOptions: { + series: { + animation: false, + turboThreshold: 0, + boostThreshold: 1, + type: "line", + color: "black", + lineWidth: that.options.graph.lineWidth, + enableMouseTracking: that.options.graph.enableMouseTracking, + stickyTracking: true, + events: { + // mouseOver: function (e) { + // // that._selectChannel(e.target.index); + // }, + // mouseOut: function (e) { + // // that._unselectChannels(); + // }, }, - plotOptions: { - series: { - animation: false, - turboThreshold: 0, - type: 'line', - color: 'black', - lineWidth: that.options.graph.lineWidth, - enableMouseTracking: that.options.graph.enableMouseTracking, - stickyTracking: true, - events: { - mouseOver: function(e) { - that._selectChannel(e.target.index); - }, - mouseOut: function(e) { - that._unselectChannels(); - }, - } - }, - line: { - marker: { - enabled: false, - } - }, - polygon: { + point: { + events: { + click: function (e) { + var crosshairPosition = { + plotX: this.plotX, + plotY: this.plotY, + timeInSeconds: e.point.x, + channelName: e.point.series.name, + channelIndex: e.point.series.index, + dataId: e.point.series.options.custom.dataId, + }; + + that.vars.annotationCrosshairCurrPosition = ({...crosshairPosition}); + that._setCrosshair(crosshairPosition); }, - }, - navigator: { - enabled: true, - adaptToUpdatedData: true, - height: 20, - margin: 25, - handles: { - enabled: false, - }, - xAxis: { - labels: { - formatter: that._formatXAxisLabel, - style: { - textOverflow: 'none', - } - }, + // workaround to trigger click event handler on point under boost mode + // https://github.com/highcharts/highcharts/issues/14067 + mouseOver: function () { + if (this.series.halo) { + this.series.halo + .attr({ + class: "highcharts-tracker", + }) + .toFront(); + } }, - series: { - type: 'area', - color: '#26a69a', - fillOpacity: 1.0, - lineWidth: 1, - marker: { - enabled: false - }, - data: bookmarkData, - } + }, }, - xAxis: { - gridLineWidth: 1, - labels: { - enabled: that.options.showTimeLabels, - crop: false, - style: { - textOverflow: 'none', - }, - step: 5, - formatter: that._formatXAxisLabel, - }, - tickInterval: 1, - minorTickInterval: 0.5, - min: that.vars.currentWindowStart, - max: that.vars.currentWindowStart + that.options.windowSizeInSeconds, - unit: [ - ['second', 1] - ], - events: { - setExtremes: function (e) { - if (e.trigger == 'navigator') { - var startTimeSnapped = Math.round(e.min / that.options.windowSizeInSeconds) * that.options.windowSizeInSeconds; - startTimeSnapped = Math.max(0, startTimeSnapped); - startTimeSnapped = Math.min(that._getLatestPossibleWindowStartInSeconds(), startTimeSnapped); - that._switchToWindow(that.options.recordingName, startTimeSnapped, that.options.windowSizeInSeconds); - return false; - } - } - } + }, + line: { + marker: { + enabled: false, }, - yAxis: { - tickInterval: 100, - minorTickInterval: 50, - min: -0.75 * that.options.graph.channelSpacing * 0.75, - max: (channels.length - 0.25) * that.options.graph.channelSpacing, - gridLineWidth: 0, - minorGridLineWidth: 0, - labels: { - enabled: that.options.showChannelNames, - step: 1, - useHTML: true, - formatter: function() { - if ( - this.value < 0 - || this.value > channels.length * that.options.graph.channelSpacing - || this.value % that.options.graph.channelSpacing !== 0 - ) { - return null; - }; - var index = that._getChannelIndexFromY(this.value); - var channel = channels[index]; - var html = '' + channel.name + ""; - return html; - }, - }, - title: { - text: null - } + }, + polygon: {}, + }, + navigator: { + enabled: true, + adaptToUpdatedData: true, + height: 20, + margin: 25, + handles: { + enabled: false, + }, + xAxis: { + labels: { + formatter: that._formatXAxisLabel, + style: { + textOverflow: "none", + }, + }, + }, + series: { + type: "area", + color: "#26a69a", + fillOpacity: 1.0, + lineWidth: 1, + marker: { + enabled: false, + }, + data: bookmarkData, + }, + }, + xAxis: { + gridLineWidth: 1, + labels: { + enabled: that.options.showTimeLabels, + crop: false, + style: { + textOverflow: "none", }, - legend: { - enabled: false + step: that.vars.xAxisScaleInSeconds / 6, + formatter: that._formatXAxisLabel, + }, + tickInterval: 1, + minorTickInterval: 0.5, + min: that.vars.currentWindowStart, + max: that.vars.currentWindowStart + that.vars.xAxisScaleInSeconds, + unit: [["second", 1]], + events: { + setExtremes: function (e) { + if (e.trigger == "navigator") { + var startTimeSnapped = + Math.round(e.min / that.vars.xAxisScaleInSeconds) * + that.vars.xAxisScaleInSeconds; + startTimeSnapped = Math.max(0, startTimeSnapped); + startTimeSnapped = Math.min( + that._getLatestPossibleWindowStartInSeconds(), + startTimeSnapped + ); + that._switchToWindow( + that.options.allRecordings, + startTimeSnapped, + that.vars.xAxisScaleInSeconds + ); + return false; + } }, - series: that._initSeries(data), - annotationsOptions: { - enabledButtons: false, + }, + }, + yAxis: { + tickInterval: 100, + minorTickInterval: 50, + min: -0.75 * that.options.graph.channelSpacing * 0.75, + max: (channels.length - 0.25) * that.options.graph.channelSpacing, + gridLineWidth: 0, + minorGridLineWidth: 0, + labels: { + enabled: that.options.showChannelNames, + step: 1, + useHTML: true, + formatter: function () { + if ( + this.value < 0 || + this.value > + channels.length * that.options.graph.channelSpacing || + this.value % that.options.graph.channelSpacing !== 0 + ) { + return null; + } + + var index = that._getChannelIndexFromY(this.value); + that.vars.allChannels = channels; + var channel = channels[index]; + + //that.vars.popUpActive = 1; + + var html = + '"; + + return html; }, + }, + title: { + text: null, + }, + scrollbar: { + enabled: true, + showFull: false, + }, + }, + scrollbar: { + liveRedraw: false, + }, + legend: { + enabled: false, + }, + series: that._initSeries(data) + //.push( + // { + // type : 'flags', + // data : [{ + // x : 0, // Point where the flag appears + // title : '', // Title of flag displayed on the chart + // text : '' // Text displayed when the flag are highlighted. + // }], + // onSeries : '', // Id of which series it should be placed on. If not defined + // // the flag series will be put on the X axis + // shape : 'flag' // Defines the shape of the flags. + // } + // ) + , + annotationsOptions: { + enabledButtons: false, + }, + })); + + if (that.options.features.examplesModeEnabled) { + that._displayAnnotations( + that._turnExamplesIntoAnnotations(that.options.features.examples) + ); + } + that._setupYAxisLinesAndLabels(); + }, + + /*_changeAmplitude: function (index, channels) { + var that = this; + + + var check; + var cid = "channel-" + index; // channel id + console.log("channel-index:" + cid) + var checker = that.vars.oldIndex; + + console.log("checker: " + checker) + console.log("that.vars.oldIndex:" +that.vars.oldIndex) + + if(checker > -1){ + $("#increase-"+(checker)).css("visibility", "hidden"); + $("#decrease-"+(checker)).css("visibility", "hidden"); + $("#default-"+(checker)).css("visibility", "hidden"); + $("#myPopup-"+(checker)).css("visibility", "hidden"); + that.vars.oldIndex = -1; + } + + var channel = channels[index]; + console.log("channel: " + channel); + + check = that.vars.popUpActive; + console.log("check: " + check) + + if(check == 1 ){ + $("#myPopup-"+(index)).css("visibility", "visible"); + $("#increase-"+(index)).css("visibility", "visible"); + $("#decrease-"+(index)).css("visibility", "visible"); + $("#default-"+(index)).css("visibility", "visible"); + } + console.log("check: " + check) + + + that.vars.popUpActive = 2; + that.vars.oldIndex =index; // checks if any channel has been clicked before + var increaser = $("#increase-"+(index)).on('click', (evt) => { + that.vars.valueOptions = 1; + that.vars.increaseOnce = 1; + that.vars.channelAmplitudeOnChange = { name: channel.name, dataId: channel.dataId }; + that.vars.reprint = 1; + that._switchToWindow(that.options.allRecordings, that.vars.currentWindowStart, that.vars.xAxisScaleInSeconds); + + }); - if (that.options.features.examplesModeEnabled) { - that._displayAnnotations(that._turnExamplesIntoAnnotations(that.options.features.examples)); - } - that._setupYAxisLinesAndLabels(); - that._setupAnnotationInteraction(); - }, + + var decreaser = $("#decrease-"+(index)).on('click', (evt) => { + that.vars.valueOptions = -1; + that.vars.channelAmplitudeOnChange = { name: channel.name, dataId: channel.dataId }; + that.vars.increaseOnce = 1; + that.vars.reprint = 1; + that._reloadCurrentWindow(); - _formatXAxisLabel: function() { - // Format x-axis at HH:MM:SS - var s = this.value; - var h = Math.floor(s / 3600); - s -= h * 3600; - var m = Math.floor(s / 60); - s -= m * 60; - return h + ":" + (m < 10 ? '0' + m : m) + ":" + (s < 10 ? '0' + s : s); //zero padding on minutes and seconds - }, + }); + var defaulter = $("#default-"+(index)).on('click', (evt) => { + that.vars.valueOptions = 0; + that.vars.channelAmplitudeOnChange = { name: channel.name, dataId: channel.dataId }; + that.vars.reprint = 1; + that._reloadCurrentWindow(); + + + }); - _blockGraphInteraction: function() { - var that = this; - var container = $(that.element); - var graph = $('#' + that.vars.graphID); - var blocker = $('
') - .addClass('blocker') - .css({ - position: 'absolute', - left: 0, - width: '100%', - top: graph.offset().top, - height: graph.height(), - backgroundColor: 'rgba(0, 0, 0, 0)', - }) - .appendTo(container); - }, + + },*/ + + _formatXAxisLabel: function () { + // Format x-axis at HH:MM:SS + var s = this.value; + var h = Math.floor(s / 3600); + s -= h * 3600; + var m = Math.floor(s / 60); + s -= m * 60; + return h + ":" + (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s); //zero padding on minutes and seconds + }, + + _renderAlignmentAlert: function () { + //create an alignment alert, alerting user to click psg first, red border + //disable it and enable when psg is clicked + + var that = this; + var alert = $("#alignment-alert"); + + if (!that._isInCrosshairSyncMode()) { + alert.hide(); + return; + } - _unblockGraphInteraction: function() { - var that = this; - $(that.element).find('> .blocker').remove(); - }, + let alertText; - _setupAnnotationInteraction: function() { - var that = this; - if (that.options.isReadOnly) return; - if (!that.options.features.order || !that.options.features.order.length) return; - var chart = that.vars.chart; - var container = chart.container; - - function drag(e) { - var annotation, - clickX = e.pageX - container.offsetLeft, - clickY = e.pageY - container.offsetTop; - if (!chart.isInsidePlot(clickX - chart.plotLeft, clickY - chart.plotTop)) { - return; - } - Highcharts.addEvent(document, 'mousemove', step); - Highcharts.addEvent(document, 'mouseup', drop); - var annotationId = undefined; - var clickXValue = that._convertPixelsToValue(clickX, 'x'); - var clickYValue = that._convertPixelsToValue(clickY, 'y'); - var channelIndexStart = that._getChannelIndexFromY(clickYValue); - var channelIndices = [ channelIndexStart ]; - var featureType = that.vars.activeFeatureType; - - annotation = that._addAnnotationBox(annotationId, clickXValue, channelIndices, featureType); - - function getAnnotationChannelIndices(e) { - var y = e.clientY - container.offsetTop, - dragYValue = that._convertPixelsToValue(y, 'y'), - channelIndices = that._getChannelsAnnotated(clickYValue, dragYValue); - return channelIndices; - } + if (that.vars.crosshairPosition.length === 0) { + alertText = "Please click on the PSG to align the graph."; + } else { + alertText = "Please click on the other montage to align the graph."; + } - function getAnnotationAttributes(e) { - var x = e.clientX - container.offsetLeft, - dx = x - clickX, - width = that._convertPixelsToValueLength(parseInt(dx, 10) + 1, 'x'), - channelIndices = getAnnotationChannelIndices(e), - { height, yValue } = that._getAnnotationBoxHeightAndYValueForChannelIndices(channelIndices); - if (dx >= 0) { - var xValue = that._convertPixelsToValue(clickX, 'x'); - } - else { - var xValue = that._convertPixelsToValue(x, 'x'); - } - return { - xValue: xValue, - yValue: yValue, - shape: { - params: { - width: width, - height: height, - } - } - }; - } + alert.show(); + alert.html(alertText); + }, + + _myfunction: function () { + var popup = document.getElementById("myPopup"); + popup.classList.toggle("show"); + }, + + _blockGraphInteraction: function () { + var that = this; + var container = $(that.element); + // //console.log(container); + var graph = $("#" + that.vars.graphID); + var blocker = $("
") + .addClass("blocker") + .css({ + position: "absolute", + left: 0, + width: "100%", + top: graph.offset().top, + height: graph.height(), + backgroundColor: "rgba(0, 0, 0, 0)", + }) + .appendTo(container); + }, + + _unblockGraphInteraction: function () { + var that = this; + $("> .blocker").remove(); + }, + + _setupAnnotationInteraction: function () { + var that = this; + if (!that.vars.setupOn) { + that.vars.setupOn = true; + } else { + // checks if the file is configured to be read only + if (that.options.isReadOnly) { + console.log("files is read only"); + return; + } + // //console.log(that.options.features.order); + if (!that.options.features.order || !that.options.features.order.length) + return; + var chart = that.vars.chart; + // //console.log(chart); + + // the container that the chart is in + var container = chart.container; + + // drag function for box annotations. + function drag(e) { + var annotation, + //gets the xy position of the mouse when you click + clickX = e.pageX - container.offsetLeft, + clickY = e.pageY - container.offsetTop; + if ( + !chart.isInsidePlot( + // if the moust is not inside the plot when you click, gets the leftmost position of the chart + clickX - chart.plotLeft, + clickY - chart.plotTop + ) + ) { + return; + } - function step(e) { - annotation.update(getAnnotationAttributes(e)); - annotation.metadata.channelIndices = getAnnotationChannelIndices(e); - } + //links the mousemove event with the step function defined later on + Highcharts.addEvent(document, "mousemove", step); - function drop(e) { - Highcharts.removeEvent(document, 'mousemove', step); - Highcharts.removeEvent(document, 'mouseup', drop); - var x = e.clientX - container.offsetLeft; - if (x == clickX) { - if (annotation && annotation.destroy) { - annotation.destroy(); - $('html').off('mousedown', annotation.outsideClickHandler); - } - that.vars.chart.selectedAnnotation = null; - return; - } - if (annotation) { - annotation.update(getAnnotationAttributes(e)); - } - annotation.outsideClickHandler = function() { - annotation.destroy(); - $('html').off('mousedown', annotation.outsideClickHandler); - that.vars.chart.selectedAnnotation = null; - } - $('html').on('mousedown', annotation.outsideClickHandler); - } - } + //links the mouseup event with the step function defined later on + Highcharts.addEvent(document, "mouseup", drop); + var annotationId = undefined; - Highcharts.addEvent(container, 'mousedown', drag); - }, + //gets the value of the mouse when you click on the graph + var clickXValue = that._convertPixelsToValue(clickX, "x"); + var clickYValue = that._convertPixelsToValue(clickY, "y"); + // //console.log(clickXValue); - _getAnnotationBoxHeightAndYValueForChannelIndices: function(channelIndices) { - var that = this; + //gets the index of the channel that is being hovered over at the moment + var channelIndexStart = that._getChannelIndexFromY(clickYValue); + var channelIndices = [channelIndexStart]; + var featureType = that.vars.activeFeatureType; - if (!Array.isArray(channelIndices)) { - channelIndices = [channelIndices]; - } - var channelIndexMin = Math.min(...channelIndices); - var channelIndexMax = Math.max(...channelIndices); - var height = (Math.abs(channelIndexMax - channelIndexMin) + 1) * that.options.graph.channelSpacing; - var yValue = that._getBorderTopForChannelIndex(channelIndexMin); - return { - height, - yValue, - }; - }, + // adds an annotation box + annotation = that._addAnnotationBox( + annotationId, + clickXValue, + channelIndices, + featureType + ); - _addAnnotationBox: function(annotationId, timeStart, channelIndices, featureType, timeEnd, confidence, comment, annotationData) { - var that = this; - var annotations = that.vars.chart.annotations.allItems; - if (!Array.isArray(channelIndices)) { - channelIndices = [channelIndices]; - } - if (annotations.some(a => - a.metadata.id == annotationId - && ( - a.metadata.channelIndices == channelIndices - || channelIndices.length == 1 && a.metadata.channelIndices.indexOf(channelIndices[0]) > -1 - ) - ) - ) { - return; - } - var timeEnd = timeEnd !== undefined ? timeEnd : false; - var annotationData = annotationData !== undefined ? annotationData : {}; - var preliminary = timeEnd === false; - var { height, yValue } = that._getAnnotationBoxHeightAndYValueForChannelIndices(channelIndices); - var shapeParams = { - height: height, - } - if (preliminary) { - shapeParams.width = 0; - shapeParams.fill = 'transparent'; - shapeParams.stroke = that._getFeatureColor(featureType, annotationData.is_answer); - shapeParams.strokeWidth = 10; - } - else { - shapeParams.width = timeEnd - timeStart; - shapeParams.fill = that._getFeatureColor(featureType, annotationData.is_answer, confidence); - shapeParams.stroke = 'transparent'; - shapeParams.strokeWidth = 0; - } - that.vars.chart.addAnnotation({ - xValue: timeStart, + // checks which channels that the annotation is over + function getAnnotationChannelIndices(e) { + var y = e.clientY - container.offsetTop, + dragYValue = that._convertPixelsToValue(y, "y"), + channelIndices = that._getChannelsAnnotated( + clickYValue, + dragYValue + ); + return channelIndices; + } + + // gets relevant annotation information about the annotation + function getAnnotationAttributes(e) { + var x = e.clientX - container.offsetLeft, + dx = x - clickX, + width = that._convertPixelsToValueLength(parseInt(dx, 10) + 1, "x"), + channelIndices = getAnnotationChannelIndices(e), + { height, yValue } = + that._getAnnotationBoxHeightAndYValueForChannelIndices( + channelIndices + ); + if (dx >= 0) { + var xValue = that._convertPixelsToValue(clickX, "x"); + } else { + var xValue = that._convertPixelsToValue(x, "x"); + } + return { + xValue: xValue, yValue: yValue, - allowDragX: preliminary, - allowDragY: false, - anchorX: 'left', - anchorY: 'top', shape: { - type: 'rect', - units: 'values', - params: shapeParams, + params: { + width: width, + height: height, + }, }, - events: { - mouseup: function(event) { - $(this.group.element).find('rect[shape-rendering="crispEdges"]').last().remove(); - }, - dblclick: function(event) { - if (that.options.isReadOnly) return; - if (annotationData.is_answer) return; - event.preventDefault(); - var xMinFixed = that._getAnnotationXMinFixed(this); - var xMaxFixed = that._getAnnotationXMaxFixed(this); - var annotationId = annotation.metadata.id; - var channelIndices = annotation.metadata.channelIndices; - var channelsDisplayed = that._getChannelsDisplayed(); - if (annotation.metadata.originalData) { - channelIndices = annotation.metadata.originalData.channels; - channelsDisplayed = annotation.metadata.originalData.channels_displayed; - } - that._deleteAnnotation(annotationId, that.vars.currentWindowRecording, xMinFixed, xMaxFixed, channelIndices, channelsDisplayed); - annotations.slice().reverse().filter(a => a.metadata.id == annotationId).forEach(a => { - a.destroy(); - that.vars.chart.selectedAnnotation = null; - }); - } - } - }); - var annotation = annotations[annotations.length - 1]; - if (!preliminary) { - var classString = $(annotation.group.element).attr('class'); - classString += ' saved'; - $(annotation.group.element).attr('class', classString); - } - $(annotation.group.element).on('mousedown', function(event) { - event.stopPropagation(); - }); - annotation.metadata = { - id: annotationId, - featureType: featureType, - channelIndices: channelIndices, - comment: '' - } - if (!preliminary) { - annotation.metadata.confidence = confidence; - annotation.metadata.comment = comment; - annotation.metadata.originalData = annotationData; + }; } - if (!that.options.isReadOnly && !annotationData.is_answer) { - that._addConfidenceLevelButtonsToAnnotationBox(annotation); - if (!preliminary) { - that._addCommentFormToAnnotationBox(annotation); - } - } - return annotation; - }, - _addConfidenceLevelButtonsToAnnotationBox: function(annotation) { - var that = this; - var annotations = that.vars.chart.annotations.allItems; - var annotationElement = $(annotation.group.element); - // To learn more about the foreignObject tag, see: - // https://developer.mozilla.org/en/docs/Web/SVG/Element/foreignObject - var htmlContext = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')); - htmlContext - .attr({ - width: 70, - height: 25, - x: 0, - y: 0, - zIndex: 2, - }) - .mousedown(function(event) { - event.stopPropagation(); - }) - .click(function(event) { - event.stopPropagation(); - }) - .dblclick(function(event) { - event.stopPropagation(); - }); - var body = $(document.createElement('body')) - .addClass('toolbar confidence-buttons') - .attr('xmlns', 'http://www.w3.org/1999/xhtml'); - var buttonGroup = $('
') - var buttonDefinitions = [{ - confidence: 0, - class: 'red', - }, { - confidence: 0.5, - class: 'yellow', - }, { - confidence: 1, - class: 'light-green', - }] - buttonDefinitions.forEach((buttonDefinition) => { - var button = $('
') - .addClass('btn') - .addClass(buttonDefinition.class) - .addClass(buttonDefinition.confidence == annotation.metadata.confidence ? 'active' : '') - .data('confidence', buttonDefinition.confidence) - .click(function(event) { - var confidence = $(this).data('confidence'); - var newColor = that._getFeatureColor(annotation.metadata.featureType, false, confidence); - annotations.filter(a => a.metadata.id == annotation.metadata.id).forEach(a => { - a.metadata.confidence = confidence; - $(a.group.element).find('.toolbar.confidence-buttons .btn').each(function() { - if ($(this).data('confidence') === confidence) { - $(this).addClass('active').siblings().removeClass('active'); - } - }); - a.update({ - allowDragX: false, - shape: { - params: { - strokeWidth: 0, - stroke: 'transparent', - fill: newColor, - } - } - }); - }); - that._saveFeatureAnnotation(annotation); - $('html').off('mousedown', annotation.outsideClickHandler); - var classString = $(annotation.group.element).attr('class'); - classString += ' saved'; - $(annotation.group.element).attr('class', classString); - that._addCommentFormToAnnotationBox(annotation); - }) - buttonGroup.append(button); - }); - body.append(buttonGroup); - htmlContext.append(body); - annotationElement.append(htmlContext); - }, - _addCommentFormToAnnotationBox: function(annotation) { - if (annotation.metadata.commentFormAdded) { - return; + // updates the annotation box with relevant channel indicies that it's selecting + function step(e) { + annotation.update(getAnnotationAttributes(e)); + annotation.metadata.channelIndices = getAnnotationChannelIndices(e); } - var that = this; - var annotations = that.vars.chart.annotations.allItems; - var annotationElement = $(annotation.group.element); - // To learn more about the foreignObject tag, see: - // https://developer.mozilla.org/en/docs/Web/SVG/Element/foreignObject - var htmlContext = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')); - htmlContext - .attr({ - width: 250, - height: 25, - x: 0, - y: 20, - zIndex: 2, - }) - .mousedown(function(event) { - event.stopPropagation(); - }) - .click(function(event) { - event.stopPropagation(); - }) - .dblclick(function(event) { - event.stopPropagation(); - }); - var body = $('') - .addClass('toolbar comment') - .attr('xmlns', 'http://www.w3.org/1999/xhtml'); - var form = $(''); - var toggleButton = $(''); - form.append(toggleButton); - var comment = annotation.metadata.comment; - var input = $('') - .hide() - .keydown(function(event) { - event.stopPropagation(); - }); - form.submit(function(event) { - event.preventDefault(); - var collapsed = toggleButton.hasClass('fa-comment'); - if (collapsed) { - toggleButton - .removeClass('fa-comment') - .addClass('fa-floppy-o') - input.show().focus(); - } - else { - toggleButton - .removeClass('fa-floppy-o') - .addClass('fa-comment') - input.hide(); - var comment = input.val(); - toggleButton.focus(); - annotations.filter(a => a.metadata.id == annotation.metadata.id).forEach(a => { - a.metadata.comment = comment; - $(a.group.element).find('.toolbar.comment input').val(comment); - }); - that._saveFeatureAnnotation(annotation); - } - }); - form.append(input); - body.append(form); - htmlContext.append(body); - annotationElement.append(htmlContext); - annotation.metadata.commentFormAdded = true; - }, - _saveFeatureAnnotation: function(annotation) { - var that = this; - var annotationId = annotation.metadata.id; - var type = annotation.metadata.featureType; - var time_start = that._getAnnotationXMinFixed(annotation); - var time_end = that._getAnnotationXMaxFixed(annotation); - var channel = annotation.metadata.channelIndices; - var confidence = annotation.metadata.confidence; - var comment = annotation.metadata.comment; - var rationale = undefined; - var metadata = {} - if (that._isHITModeEnabled()) { - metadata = { - visibleRegion: that.options.visibleRegion, - windowSizeInSeconds: that.options.windowSizeInSeconds, - isTrainingWindow: that._isCurrentWindowTrainingWindow(), - } - if (that.options.projectUUID) { - metadata.projectUUID = that.options.projectUUID; - } - } - that._saveAnnotation(annotationId, that.vars.currentWindowRecording, type, time_start, time_end, channel, confidence, comment, metadata, rationale, function(savedAnnotation, error) { - if (savedAnnotation) { - annotation.metadata.id = savedAnnotation.id; - annotationFormatted = savedAnnotation.value; - annotationFormatted.id = savedAnnotation.id; - annotationFormatted.arbitration = savedAnnotation.arbitration; - annotationFormatted.arbitrationRoundNumber = savedAnnotation.arbitrationRoundNumber; - annotationFormatted.rationale = savedAnnotation.rationale; - that._displayAnnotations([annotationFormatted]); + function drop(e) { + Highcharts.removeEvent(document, "mousemove", step); + Highcharts.removeEvent(document, "mouseup", drop); + var x = e.clientX - container.offsetLeft; + if (x == clickX) { + if (annotation && annotation.destroy) { + annotation.destroy(); + $("html").off("mousedown", annotation.outsideClickHandler); } - }); - }, + that.vars.chart.selectedAnnotation = null; + return; + } + if (annotation) { + annotation.update(getAnnotationAttributes(e)); + } + annotation.outsideClickHandler = function () { + // annotation.destroy(); + // $("html").off("mousedown", annotation.outsideClickHandler); + // that.vars.chart.selectedAnnotation = null; + }; + $("html").on("mousedown", annotation.outsideClickHandler); + } + } + + +///////////////////TODO: Change this to fix channel specific changepoint annotation + function click(e) { + // if (that.vars.annotationClicks.clickOne === null) { + // (clickXOne = e.pageX - container.offsetLeft), + // (clickYOne = e.pageY - container.offsetTop); + + // that.vars.annotationClicks.clickOne = { + // clickX: clickXOne, + // clickY: clickYOne, + // }; + // } else if (that.vars.annotationClicks.clickTwo === null) { + // (clickXTwo = e.pageX - container.offsetLeft), + // (clickYTwo = e.pageY - container.offsetTop); + + // that.vars.annotationClicks.clickTwo = { + // clickX: clickXTwo, + // clickY: clickYTwo, + // }; + + // var annotation = that._addAnnotationChangePoint(); + + // that.vars.annotationClicks.clickOne = null; + // that.vars.annotationClicks.clickTwo = null; + // } + clickX = e.pageX - container.offsetLeft; + clickY = e.pageY - container.offsetTop + var annotation = that._addAnnotationChangePoint(clickX, clickY); + + } + + + function clickAll(e) { + clickX = e.pageX - container.offsetLeft; + + var annotation = that._addAnnotationChangePointAll(clickX); + + } + + that.vars.annotationMode = that.options.features.annotationType; + // Define behaviour of different types of annotation. + if (that.options.features.annotationType == "box") { + Highcharts.removeEvent(container, "mousedown"); + Highcharts.addEvent(container, "mousedown", drag); + } else if (that.options.features.annotationType == "none") { + // console.log("here"); + Highcharts.removeEvent(container, "mousedown"); + } else if (that.options.features.annotationType == "cpoint") { + Highcharts.removeEvent(container, "mousedown"); + Highcharts.addEvent(container, "mousedown", click); + } else if (that.options.features.annotationType == "cpointall") { + Highcharts.removeEvent(container, "mousedown"); + Highcharts.addEvent(container, "mousedown", clickAll); + // } else if (that.options.features.annotationType == "sne") { + // // Highcharts.removeEvent(container, "click"); + // Highcharts.addEvent(container, "click", dropStartCrosshair); + } + } + }, - _saveFullWindowLabel: function(annotationCategory, label, rationale) { - var that = this; - var label = label; - var time_start = that.vars.currentWindowStart; - var time_end = time_start + that.options.windowSizeInSeconds; - var channel = undefined; - var confidence = 1; - var comment = ''; - var cacheKey = that._getAnnotationsCacheKey(that.vars.currentWindowRecording, time_start, time_end, false, annotationCategory); - var annotation = that.vars.annotationsCache[cacheKey] || {}; - that._saveAnnotation(annotation.id, that.vars.currentWindowRecording, label, time_start, time_end, channel, confidence, comment, {}, rationale, function(savedAnnotation, error) { - if (savedAnnotation) { - annotationFormatted = savedAnnotation.value; - annotationFormatted.id = savedAnnotation.id; - annotationFormatted.arbitration = savedAnnotation.arbitration; - annotationFormatted.arbitrationRoundNumber = savedAnnotation.arbitrationRoundNumber; - annotationFormatted.rationale = savedAnnotation.rationale; - that.vars.annotationsCache[cacheKey] = savedAnnotation; - } - that._refreshAnnotations(); + _addAnnotationType: function (annotation) { + var that = this; + // get the channels by name + const channelIndices = annotation.metadata.channelIndices; + let channelLabels = []; + + channelIndices.forEach((index) => { + const channelName = that.vars.allChannels[index].name; + + const currentLabel = that._getAnnotationLabel(channelName); + + if (currentLabel && currentLabel.length !== 0) { + currentLabel.forEach((label) => { + if (!channelLabels.includes(label)) { + channelLabels.push(label); + } }); - }, + } + // console.log(channelName); + // channelLabels = [...channelLabels, that._getAnnotationLabel(channelName)]; + }); + + return channelLabels; + }, + + _getAnnotationLabel: function (channelName) { + var that = this; + const currentMontage = that._getCurrentMontage(); + + if (that.options.features.annotationType === "box") { + switch (currentMontage) { + case "PSG": + switch (channelName) { + case "F4-A1": + case "C4-A1": + case "O2-A1": + return ["Aro"]; + + case "LOC-A2": + case "ROC-A1": + case "Chin1-Chin2": + case "ECG": + return []; + + case "Leg/L": + case "Leg/R": + return ["LM"]; + + case "Snore": + case "Airflow": + case "NasalPressure": + case "Thor": + case "Abdo": + return ["H1", "H2", "OA", "CA", "MA"]; + + case "SpO2": + return ["desat"]; + } + break; + + case "watchpath": + switch (channelName) { + case "VIEW_PAT": + case "DERIVED_PAT_AMP": + case "DERIVED_HR": + return ["Aro"]; + + case "SAO2_WRIST": + return ["desat"]; + + case "ACTIGRAPH": + case "WRIST_STAGES": + return []; + } + break; + + case "ANNE": + switch (channelName) { + case "ECG": + return ["Arrhythmia"]; + + case "AcclPtch": + case "AcclRoll": + case "RespEffort": + case "PAT(ms)": + case "PAT_resp": + case "Snore": + return ["Obs", "Cen", "Mix"]; + + case "SpO2(%)": + return ["desat"]; + + case "Pleth": + case "PAT_trend": + case "HR(bpm)": + return ["Aro"]; + + case "ChestTemp": + case "LimbTemp": + return []; + } + break; + + case "MUSE": + switch (channelName) { + case "eeg-ch1 - eeg-ch2": + case "eeg-ch4 - eeg-ch2": + case "eeg-ch1": + case "eeg-ch4": + case "eeg-ch1-eeg-ch4": + case "eeg-ch4-eeg-ch3": + case "eeg-ch2": + case "eeg-ch3": + case "acc-ch1": + case "acc-ch2": + case "acc-ch3": + case "ppg-ch2": + return []; + } + + break; + + case "Apnealink": + switch (channelName) { + case "Flow": + case "Effort": + case "Snoring": + return ["Hyp", "Obs", "Cen", "Mix"]; + case "Saturation": + return ["Desat"]; + case "Pulse": + return ["Aro"]; + } + break; + + case "GENEActiv": + switch (channelName) { + case "Temp": + case "light": + case "ENMO": + case "z-angle": + return ["Sleep Period", "Wake Period"]; + } + break; + + case "AX3": + switch (channelName) { + case "Temp": + case "ENMO": + case "z-angle": + return ["Sleep Period", "Wake Period"]; + } + break; +//TODO: FIX THIS + case "ANNE + PSG": + case "PSG + ANNE": + switch (channelName) { + case "ECG": + case "AcclPtch": + case "AcclRoll": + case "RespEffort": + case "Snore": + case "ECG": + case "Thor": + case "Abdo": + case "Snore": + case "Chin1-Chin2": + return []; + } + break; + + case "MUSE + PSG": + case "PSG + MUSE": + switch (channelName) { + case "eeg-ch1 - eeg-ch2": + case "eeg-ch4 - eeg-ch2": + case "eeg-ch1": + case "eeg-ch4": + case "eeg-ch1-eeg-ch4": + case "eeg-ch4-eeg-ch3": + case "acc-ch1": + case "acc-ch2": + case "acc-ch3": + case "F4-A1": + case "C4-A1": + case "O2-A1": + case "LOC-A2": + case "ROC-A1": + case "Chin1-Chin2": + return []; + } + break; + + case "GENEActiv + PSG": + case "PSG + GENEActiv": + switch (channelName) { + case "light": + case "ENMO": + case "z-angle": + case "Chin1-Chin2": + case "Leg/L": + case "Leg/R": + return []; + } + break; + + case "GENEActiv + Actical": + case "Actical + GENEActiv": + switch (channelName) { + case "ENMO": + case "z-angle": + case "Counts": + return []; + } + } + } else if (that.options.features.annotationType === "cpoint") { + console.log("here"); + console.log(currentMontage); + switch (currentMontage) { + case "PSG": + switch (channelName) { + case "F4-A1": + case "C4-A1": + case "O2-A1": + case "LOC-A2": + case "ROC-A1": + case "Chin1-Chin2": + case "ECG": + case "Leg/L": + case "Leg/R": + case "Snore": + case "Airflow": + case "NasalPressure": + case "Thor": + case "Abdo": + case "SpO2": + return ["ok", "artif"]; + } + break; + + case "watchpath": + switch (channelName) { + case "VIEW_PAT": + case "DERIVED_PAT_AMP": + case "DERIVED_HR": + case "SAO2_WRIST": + case "ACTIGRAPH": + return ["ok", "artif"]; + case "WRIST_STAGES": + return []; + } + break; + + case "ANNE": + switch (channelName) { + case "ECG": + case "AcclPtch": + case "AcclRoll": + case "RespEffort": + case "PAT(ms)": + case "PAT_resp": + case "Snore": + case "SpO2(%)": + case "Pleth": + case "PAT_trend": + case "HR(bpm)": + case "ChestTemp": + case "LimbTemp": + return ["ok", "artif"]; + } + break; + + case "MUSE": + switch (channelName) { + case "eeg-ch1 - eeg-ch2": + case "eeg-ch4 - eeg-ch2": + case "eeg-ch1": + case "eeg-ch4": + case "eeg-ch1-eeg-ch4": + case "eeg-ch4-eeg-ch3": + case "eeg-ch2": + case "eeg-ch3": + case "acc-ch1": + case "acc-ch2": + case "acc-ch3": + case "ppg-ch2": + return ["ok", "artif"]; + } + + break; + + case "Apnealink": + switch (channelName) { + case "Flow": + case "Effort": + case "Snoring": + case "Saturation": + case "Pulse": + return ["ok", "artif"]; + } + break; + + case "GENEActiv": + switch (channelName) { + case "Temp": + case "light": + case "ENMO": + case "z-angle": + return []; + } + break; + + case "AX3": + switch (channelName) { + case "Temp": + case "ENMO": + case "z-angle": + return []; + } + break; + + case "ANNE + PSG": + case "PSG + ANNE": + switch (channelName) { + case "ECG": + case "AcclPtch": + case "AcclRoll": + case "RespEffort": + case "Snore": + case "ECG": + case "Thor": + case "Abdo": + case "Snore": + case "Chin1-Chin2": + return []; + } + break; + + case "MUSE + PSG": + case "PSG + MUSE": + switch (channelName) { + case "eeg-ch1 - eeg-ch2": + case "eeg-ch4 - eeg-ch2": + case "eeg-ch1": + case "eeg-ch4": + case "eeg-ch1-eeg-ch4": + case "eeg-ch4-eeg-ch3": + case "acc-ch1": + case "acc-ch2": + case "acc-ch3": + case "F4-A1": + case "C4-A1": + case "O2-A1": + case "LOC-A2": + case "ROC-A1": + case "Chin1-Chin2": + return []; + } + break; + + case "GENEActiv + PSG": + case "PSG + GENEActiv": + switch (channelName) { + case "light": + case "ENMO": + case "z-angle": + case "Chin1-Chin2": + case "Leg/L": + case "Leg/R": + return []; + } + break; + + case "GENEActiv + Actical": + case "Actical + GENEActiv": + switch (channelName) { + case "ENMO": + case "z-angle": + case "Counts": + return []; + } + } + } else if (that.options.features.annotationType === "cpointall") { + console.log("here " + currentMontage); + switch (currentMontage) { + case "PSG": + switch (channelName) { + case "F4-A1": + case "C4-A1": + case "O2-A1": + case "LOC-A2": + case "ROC-A1": + case "Chin1-Chin2": + case "ECG": + case "Leg/L": + case "Leg/R": + case "Snore": + case "Airflow": + case "NasalPressure": + case "Thor": + case "Abdo": + case "SpO2": + return ["W", "N1", "N2", "N3", "artif"]; + } + break; + + case "watchpath": + switch (channelName) { + case "VIEW_PAT": + case "DERIVED_PAT_AMP": + case "DERIVED_HR": + case "SAO2_WRIST": + case "ACTIGRAPH": + case "WRIST_STAGES": + return ["Sleep", "Wake", "Artif"]; + } + break; + + case "ANNE": + switch (channelName) { + case "ECG": + case "AcclPtch": + case "AcclRoll": + case "RespEffort": + case "PAT(ms)": + case "PAT_resp": + case "Snore": + case "SpO2(%)": + case "Pleth": + case "PAT_trend": + case "HR(bpm)": + case "ChestTemp": + case "LimbTemp": + return ["Sleep", "Wake", "Artif"]; + } + break; + + case "MUSE": + switch (channelName) { + case "eeg-ch1 - eeg-ch2": + case "eeg-ch4 - eeg-ch2": + case "eeg-ch1": + case "eeg-ch4": + case "eeg-ch1-eeg-ch4": + case "eeg-ch4-eeg-ch3": + case "eeg-ch2": + case "eeg-ch3": + case "acc-ch1": + case "acc-ch2": + case "acc-ch3": + case "ppg-ch2": + return ["W", "N1", "N2", "N3", "artif"]; + } + + break; + + case "Apnealink": + switch (channelName) { + case "Flow": + case "Effort": + case "Snoring": + case "Saturation": + case "Pulse": + return []; + } + break; + + case "GENEActiv": + switch (channelName) { + case "Temp": + case "light": + case "ENMO": + case "z-angle": + return ["Wear", "Nonwear"]; + } + break; + + case "AX3": + switch (channelName) { + case "Temp": + case "ENMO": + case "z-angle": + return ["Wear", "Nonwear"]; + } + break; + + case "ANNE + PSG": + case "PSG + ANNE": + switch (channelName) { + case "ECG": + case "AcclPtch": + case "AcclRoll": + case "RespEffort": + case "Snore": + case "ECG": + case "Thor": + case "Abdo": + case "Snore": + case "Chin1-Chin2": + return []; + } + break; + + case "MUSE + PSG": + case "PSG + MUSE": + switch (channelName) { + case "eeg-ch1 - eeg-ch2": + case "eeg-ch4 - eeg-ch2": + case "eeg-ch1": + case "eeg-ch4": + case "eeg-ch1-eeg-ch4": + case "eeg-ch4-eeg-ch3": + case "acc-ch1": + case "acc-ch2": + case "acc-ch3": + case "F4-A1": + case "C4-A1": + case "O2-A1": + case "LOC-A2": + case "ROC-A1": + case "Chin1-Chin2": + return []; + } + break; + + case "GENEActiv + PSG": + case "PSG + GENEActiv": + switch (channelName) { + case "light": + case "ENMO": + case "z-angle": + case "Chin1-Chin2": + case "Leg/L": + case "Leg/R": + return []; + } + break; + + case "GENEActiv + Actical": + case "Actical + GENEActiv": + switch (channelName) { + case "ENMO": + case "z-angle": + case "Counts": + return []; + } + } + } + }, + + _getAnnotationLabelFromCreationType: function(annotation) { + if (annotation.creationType == 'ChangePointAll') { + return [undefined, "Awake", "N1", "N2", "SWS", "REM", "(unanalyzable)"]; + } else if (annotation.creationType == 'ChangePoint') { + return [undefined, "Obstructive Apnea", "Central Apnea", "Obstructive Hypoapnea", "Central Hypoapnea", "Flow Limitation", "Cortical Arousal", "Autonomic Arousal", "Desat. Event", "Mixed Apnea", "Mixed Hypoapnea", "(unanalyzable)", "(end previous state)"]; + } else { + return [undefined, "Obstructive Apnea", "Central Apnea", "Obstructive Hypoapnea", "Central Hypoapnea", "Flow Limitation", "Cortical Arousal", "Autonomic Arousal", "Desat. Event", "Mixed Apnea", "Mixed Hypoapnea", "(unanalyzable)"]; + } + }, - _refreshAnnotations: function() { - var that = this; - that._getAnnotations(that.vars.currentWindowRecording, that.vars.currentWindowStart, that.vars.currentWindowStart + that.options.windowSizeInSeconds); - }, + _getCurrentMontage: function () { + var that = this; - _saveArtifactAnnotation: function(type) { - var that = this; - that._saveFullWindowLabel('ARTIFACT', type); - }, + return that.vars.currentMontage; + }, - _isDisagreementCase: function() { - var that = this; - return $(that.element).find('.votes-info.has-disagreement').length > 0; - }, + _getAnnotationBoxHeightAndYValueForChannelIndices: function (channelIndices) { + var that = this; - _saveSleepStageAnnotation: function(type) { - var that = this; - if (that._isArbitrating()) { - if (!that._isDisagreementCase() && !confirm("You are trying to re-score a case for which all panel members already agree with you. While you're free to do this, it is not required to re-score agreement cases. We only ask to re-score those cases with any level of disagreement. The red arrow buttons in the bottom right-hand corner of the page let you jump to the next or previous disagreement case. There are also keyboard shortcuts for this: [D] to jump to the next disagreement case; and [A] to jump to the previous disagreement case. In addition, you may also press [SPACE BAR] to toggle a panel showing you how many disagreement cases you have already re-scored and how many are left. Press OK to re-score this agreement case anyways; or press CANCEL to stop here.")) { - that._refreshAnnotations(); - } - else { - const fullWindowLabelHumanReadable = that.vars.fullWindowLabelsToHumanReadable[type]; - var title = 'Why ' + fullWindowLabelHumanReadable + '?'; - const rationaleFormData = _.extend(that.options.context, { - task: that.options.context.task, - decisionField: 'value.label', - decision: type, - decisionHumanReadable: fullWindowLabelHumanReadable, - }); - let rationaleFormView; - swal({ - title: title, - confirmButtonText: 'Submit', - showCancelButton: true, - allowOutsideClick: false, - allowEscapeKey: false, - footer: 'Press SHIFT + ENTER to submit.', - animation: false, - focusConfirm: false, - backdrop: 'rgba(0, 0, 0, 0.4)', - onBeforeOpen() { - rationaleFormView = Blaze.renderWithData(Template.RationaleForm, rationaleFormData, swal.getContent(), swal.getContent().firstChild); - $(swal.getContent()).keypress((e) => { - if (e.which == 13 && e.shiftKey) { - e.stopImmediatePropagation(); - swal.clickConfirm(); - } - }); - $(swal.getContent()).find('input').keypress((e) => { - if (e.which == 13) { - e.stopImmediatePropagation(); - swal.clickConfirm(); - } - }); - }, - preConfirm() { - return Template.RationaleForm.__helpers.get('rationale').call(rationaleFormView._domrange.members[0].view.templateInstance(), swal.showValidationMessage); - } - }) - .then((result) => { - if (result.dismiss) { - that._refreshAnnotations(); - swal('Scoring decision not saved.', 'Please try again and provide a rationale.', 'warning'); - return; - } - const rationale = result.value; - that._saveFullWindowLabel('SLEEP_STAGE', type, rationale); - }); - } - } - else { - that._saveFullWindowLabel('SLEEP_STAGE', type); - } - }, + if (!Array.isArray(channelIndices)) { + channelIndices = [channelIndices]; + } - _getAnnotationXMinFixed: function(annotation) { - return parseFloat(annotation.options.xValue).toFixed(2); - }, + //gets the minimum and maximum channel indicies + var channelIndexMin = Math.min(...channelIndices); + var channelIndexMax = Math.max(...channelIndices); + + var height = + (Math.abs(channelIndexMax - channelIndexMin) + 1) * + that.options.graph.channelSpacing; + var yValue = that._getBorderTopForChannelIndex(channelIndexMin); + + return { + height, + yValue, + }; + }, + + //removes all box annotations from screen (they still exist in the backend) + _removeAnnotationBox: function () { + var that = this; + if (that.vars.chart) { + const annotations = that.vars.chart.annotations.allItems; + // //console.log(annotations); + for (let i = annotations.length - 1; i > -1; --i) { + annotations[i].destroy(); + } + } + }, + + _addAnnotationChangePoint: function (clickX, clickY) // annotationId, + // timeStart, + // channelIndices, + // featureType, + // timeEnd, + // confidence, + // comment, + // annotationData + { + var that = this; + + // const clickXOne = that.vars.annotationClicks.clickOne.clickX; + // const clickYOne = that.vars.annotationClicks.clickOne.clickY; + + // const clickXTwo = that.vars.annotationClicks.clickTwo.clickX; + // const clickYTwo = that.vars.annotationClicks.clickTwo.clickY; + + // const clickXOneValue = that._convertPixelsToValue(clickXOne, "x"); + // const clickYOneValue = that._convertPixelsToValue(clickYOne, "y"); + + // const clickXTwoValue = that._convertPixelsToValue(clickXTwo, "x"); + // const clickYTwoValue = that._convertPixelsToValue(clickYTwo, "y"); + + // console.log(clickXOneValue, clickYOneValue, clickXTwoValue, clickYTwoValue); + + const clickXValue = that._convertPixelsToValue(clickX, "x"); + const clickYValue = that._convertPixelsToValue(clickY, "y"); + + const channelIndex = that._getChannelIndexFromY(clickYValue); + console.log(that.vars.allChannels[channelIndex].name); + const featureType = that.vars.activeFeatureType; + + console.log("channel selected: " + channelIndex); + console.log("feature type: " + featureType); + + const annotationId = undefined; + + var annotation = that._addAnnotationBoxChangePoint( + annotationId, + clickXValue, + [channelIndex], + featureType, + ); + if (!that.options.isReadOnly) { + that._addCommentFormToAnnotationBox(annotation); + // if (!preliminary) { + // size = shapeParams; + // that._addCommentFormToAnnotationBoxChangePoint(annotation); + // } + } - _getAnnotationXMaxFixed: function(annotation) { - return parseFloat(annotation.options.xValue + annotation.options.shape.params.width).toFixed(2); - }, + // that._addCommentFormToAnnotationBox(annotation); - _getAxis: function(key) { - var that = this; - switch (key) { - case 'x': - case 'X': - var axis = that.vars.chart.xAxis[0]; - break; - default: - var axis = that.vars.chart.yAxis[0]; - break; - } - return axis; - }, + return annotation; + }, - _convertPixelsToValue: function(pixels, axisKey) { - var axis = this._getAxis(axisKey); - return axis.toValue(pixels); - }, + _addCommentFormToAnnotationBoxChangePoint: function (annotation) { + if (annotation.metadata.commentFormAdded) { + return; + } - _convertValueToPixels: function(value, axisKey) { - var axis = this._getAxis(axisKey); - return axis.toPixels(value); - }, + var that = this; + var annotations = that.vars.chart.annotations.allItems; + + // console.log(annotation); + var annotationElement = $(annotation.group.element); + + // annotationElement.attr({ + // "stroke-dasharray": "150,50", + // "stroke": "red", + // "stroke-width": "25", + // "stroke-dashoffset": "-500", + // }); + + // To learn more about the foreignObject tag, see: + // https://developer.mozilla.org/en/docs/Web/SVG/Element/foreignObject + var htmlContext = $( + document.createElementNS("http://www.w3.org/2000/svg", "foreignObject") + ); + + annotationElement.append(htmlContext); + + htmlContext + .attr({ + width: `${annotation.group.element.getBBox().width}`, + height: `${annotation.group.element.getBBox().height}`, + // x: 0, + // y: 20, + zIndex: 2, + }) + .mousedown(function (event) { + event.stopPropagation(); + }) + .click(function (event) { + event.stopPropagation(); + }) + // .dblclick(function (event) { + // event.stopPropagation(); + // event.preventDefault(); + // that._deleteAnnotation(annotation.id); + // }) + ; + + var body = $("") + .addClass("comment toolbar") + // .attr("xmlns", "http://www.w3.org/1999/xhtml"); + .attr({ + width: "100px", + height: "100px", + }); + + // dont make form disappear when not hovering over + body.mouseover(function (event) { + event.stopPropagation(); + }); + + var form = $(""); + form.css({ + position: "absolute", + // left: + display: "flex", + width: "100px", + height: "100%", + zIndex: 2, + }); + + //gets all the relevant labels based on annotation type + const channelLabels = that._addAnnotationType(annotation); + + //create a select element using Jquery + var annotationLabelSelector = $('') + .hide() + .keydown(function (event) { + event.stopPropagation(); + }) + .css({zIndex: 1}); + //add the options to the select element + + channelLabels.forEach((label) => { + annotationLabelSelector.append( + $('") + ).css({zIndex: 1}); + }); + + // //add margin top and bottom + annotationLabelSelector.css({ + width: "100%", + height: "25%", + padding: "1%", + zIndex: 1, + }); + + //TODO:Label is saving to annotation object, now need to handle labels in all other annotation related functions, specifically the save annotation one + form.append(toggleButton); + form.append(trashButton); + + + //add selector to form + form.append(annotationLabelSelector); + + var comment = annotation.metadata.comment; + + if (comment === undefined) { + comment = " "; + } - _getBorderTopForChannelIndex: function(index) { - var that = this; - var top = that._getOffsetForChannelIndexPostScale(index) + that.options.graph.channelSpacing / 2; - return top; - }, + //TODO: add annotation label handling + var annotationLabel = annotation.metadata.annotationLabel; - _getBorderBottomForChannelIndex: function(index) { - var that = this; - var bottom = that._getOffsetForChannelIndexPostScale(index) - that.options.graph.channelSpacing / 2; - return bottom; - }, + //make the selector show the current label if it exists + if (annotationLabel) { + annotationLabelSelector.val(annotationLabel); + } - _getArbitration: function() { - var that = this; - return that.options.context.assignment.arbitration; - }, + var input = $( + '' + ) + .hide() + .css({ + width: "100%", + height: "100%", + padding: "1%", + zIndex: 1, + }) + .keydown(function (event) { + event.stopPropagation(); + }); + + form.submit(function (event) { + event.preventDefault(); + var collapsed = toggleButton.hasClass("fa-pencil"); + if (collapsed) { + toggleButton.removeClass("fa-pencil").addClass("fa-floppy-o"); + input.show().focus(); + annotationLabelSelector.show(); + $(".changePointLabelRight").hide(); + $(".changePointLabelLeft").hide(); + that.vars.chart.tooltip.label.hide(); + + } else { + + + ////// + $(".changePointLabelRight").show(); + $(".changePointLabelLeft").show(); + + toggleButton.removeClass("fa-floppy-o").addClass("fa-pencil"); + input.hide(); + annotationLabelSelector.hide(); + var comment = input.val(); + var annotationLabel = annotationLabelSelector.val(); + toggleButton.focus(); + annotations + .filter((a) => a.metadata.id == annotation.metadata.id) + .forEach((a) => { + a.metadata.comment = comment; + a.metadata.annotationLabel = annotationLabel; + $(a.group.element).find(".toolbar.comment input").val(comment); + + //gets the label from the form selector + $(a.group.element).find(".form-control").val(annotationLabel); + }); + that._saveFeatureAnnotation(annotation); + that.vars.chart.tooltip.label.show(); + } + }); + + form.on('reset', function (event) { + event.preventDefault(); + if (that.options.isReadOnly) return; + + var index = that._getUniversalAnnotationIndexByXVal(that._getAnnotationXMinFixed(annotation)) + 1; + var nextAnnotation = that.vars.universalChangePointAnnotationsCache[index]; + + annotations + .slice() + .reverse() + .filter((a) => a.metadata.id == annotation.metadata.id) + .forEach((a) => { + a.destroy(); + that.vars.chart.selectedAnnotation = null; + }); + that._deleteAnnotation( + annotation.metadata.id, + ); + that._getNonTrivialUniversalAnnotations(); + if (nextAnnotation !== undefined) { + that._updateChangePointLabelLeft(nextAnnotation); + that._updateChangePointLabelRight(nextAnnotation); + } + }); + + form.append(input); + body.append(form); + htmlContext.append(body); + + annotation.metadata.commentFormAdded = true; + }, + + _getChangePointColor: function (changePoint) { + switch(changePoint) { + // Sleep stages + case "Awake": + return "lightgreen"; + case "N1": + return "aquamarine" + case "N2": + return "cyan"; + case "SWS": + return "mediumturquoise"; + case "REM": + return "deepskyblue"; + default: + // Apnea and Hypoapnea + if (changePoint) { + if (changePoint.includes("Apnea")) { + return "yellow"; + } else if (changePoint.includes("Hypoapnea")) { + return "orange"; + } else if (changePoint.includes("Arousal")) { + return "gold"; + } else if (changePoint == "Desat. Event" || changePoint == "Flow Limitation" ) { + return "lightsalmon"; + } else if (changePoint == "(end previous state)") { + return "white"; + } + } + return "red"; + } - _getArbitrationDoc: function() { - var that = this; - return that.options.context.assignment.arbitrationDoc(); - }, + }, - _getArbitrationRoundNumber: function() { - var that = this; - return that.options.context.assignment.arbitrationRoundNumber; - return roundNumber; - }, + _addChangePointLabelFixed: function () { + var that = this; + let chart = that.vars.chart; - _getArbitrationRoundNumberInt: function() { - var that = this; - var roundNumber = that._getArbitrationRoundNumber(); - roundNumber = !!roundNumber ? roundNumber : 0; - return roundNumber; - }, + var annotations = that._getNonTrivialUniversalAnnotations(); + // grab the previous annotation in sorted order + var index = that._getUniversalAnnotationIndexByXVal(that.vars.currentWindowStart); + var annotation = annotations[index]; + var label; + if (annotation !== undefined) { + label = annotation.metadata.annotationLabel; + } + label = label || 'undefined'; + + const x = 0; + const y = 0; + const height = 26; + const width = 200 + that._getTextWidth(label, 12); + var content = `
Latest Change Point Previous Page: ` + label + + '
'; + + chart.renderer.html(content, x+7.5, y+17) + .attr({ + zIndex: 5, + id: 'prevPageLatestLabel' + }) + .css({ + 'font-size': 12, + 'color': 'white' + }) + .add(); + + + chart.renderer.rect(x, y, width, height, 0) + .attr({ + 'stroke-width': 0.5, + stroke: 'black', + fill: '#26a69a', + zIndex: 4, + id: 'prevPageLatestBox' + }) + .add(); + }, + + _addChangePointLabelLeft: function (annotation) { + // Adds the left label tag denoting the pervious state to the bottom of a change point annotation. + + var that = this; + // var annotations = that.vars.chart.annotations.allItems; + + var annotationElement = $(annotation.group.element); + + var htmlContext = $( + document.createElementNS("http://www.w3.org/2000/svg", "foreignObject") + ); + // htmlContext.hide(); + + var textarea1 = $(`