diff --git a/LesionTracker/client/head.html b/LesionTracker/client/head.html index 5238847e4..ef408f13e 100644 --- a/LesionTracker/client/head.html +++ b/LesionTracker/client/head.html @@ -12,6 +12,9 @@ + + + diff --git a/OHIFViewer/client/components/toolbarSection/toolbarSection.js b/OHIFViewer/client/components/toolbarSection/toolbarSection.js index eb1a20861..1c6c80762 100644 --- a/OHIFViewer/client/components/toolbarSection/toolbarSection.js +++ b/OHIFViewer/client/components/toolbarSection/toolbarSection.js @@ -12,7 +12,7 @@ Template.toolbarSection.helpers({ svgLink: '/packages/viewerbase/assets/icons.svg#icon-studies', svgWidth: 15, svgHeight: 13, - bottomLabel: 'Studies' + bottomLabel: 'Series' }] }; }, diff --git a/Packages/design/app.styl b/Packages/design/app.styl index 094eef529..47040a535 100644 --- a/Packages/design/app.styl +++ b/Packages/design/app.styl @@ -1,3 +1,4 @@ +@import "{design}/styles/imports/animations" @import "{design}/styles/imports/mixins" @import "{design}/styles/imports/spacings" @import "{design}/styles/imports/variables" diff --git a/Packages/design/package.js b/Packages/design/package.js index b253d019c..41de0594b 100644 --- a/Packages/design/package.js +++ b/Packages/design/package.js @@ -26,6 +26,7 @@ Package.onUse(function(api) { // Common styles api.addFiles([ 'styles/common/webfonts.styl', + 'styles/common/keyframes.styl', 'styles/common/global.styl', 'styles/common/spacings.styl' ], 'client'); diff --git a/Packages/design/styles/common/keyframes.styl b/Packages/design/styles/common/keyframes.styl new file mode 100644 index 000000000..0c406e221 --- /dev/null +++ b/Packages/design/styles/common/keyframes.styl @@ -0,0 +1,19 @@ +@import "{design}/app.styl" + +@keyframes zoomIn + from + transform(scale(0)) + to + transform(scale(1)) + +@keyframes fadeIn + from + opacity: 0 + to + opacity: 1 + +@keyframes fadeOut + from + opacity: 1 + to + opacity: 0 diff --git a/Packages/design/styles/components/selectTree.styl b/Packages/design/styles/components/selectTree.styl index b365790d4..0dfe25a85 100644 --- a/Packages/design/styles/components/selectTree.styl +++ b/Packages/design/styles/components/selectTree.styl @@ -13,8 +13,8 @@ $gray6 = #303030 .tree-content background-color: white position: relative + transition(transform 0.3s ease\, background-color 0.3s ease\, border-color 0.3s ease\, border-radius 0.3s ease) - & .tree-search a.tree-back .tree-breadcrumb span @@ -146,6 +146,7 @@ $gray6 = #303030 &.active box-shadow: 0 0 0 200px white, inset 0 0 0 200px white + position: fixed transition(box-shadow 0.3s ease) z-index: 0 @@ -192,7 +193,6 @@ $gray6 = #303030 &>.tree-content transform(scale(0)) transform-origin(100% 50%) - transition(all 0.3s ease) .select-tree-common overflow: hidden diff --git a/Packages/design/styles/imports/animations.styl b/Packages/design/styles/imports/animations.styl new file mode 100644 index 000000000..5f8951ab1 --- /dev/null +++ b/Packages/design/styles/imports/animations.styl @@ -0,0 +1,17 @@ +animationDefaults() + animation-duration: 0.3s + animation-direction: alternate + animation-timing-function: ease-out + animation-fill-mode: forwards + +animateZoomIn() + animationDefaults() + animation-name: zoomIn + +animateFadeIn() + animationDefaults() + animation-name: fadeIn + +animateFadeOut() + animationDefaults() + animation-name: fadeOut diff --git a/Packages/dimseservice/server/DIMSE.js b/Packages/dimseservice/server/DIMSE.js index 298f63885..2c3f776f3 100755 --- a/Packages/dimseservice/server/DIMSE.js +++ b/Packages/dimseservice/server/DIMSE.js @@ -12,15 +12,18 @@ var getInstanceRetrievalParams = function(studyInstanceUID, seriesInstanceUID) { return { 0x0020000D: studyInstanceUID ? studyInstanceUID : '', 0x0020000E: (studyInstanceUID && seriesInstanceUID) ? seriesInstanceUID : '', - 0x00080005: '', - 0x00080020: '', - 0x00080030: '', - 0x00080090: '', - 0x00100010: '', - 0x00100020: '', - 0x00200010: '', - 0x0008103E: '', - 0x00200011: '', + 0x00080005: '', // specificCharacterSet + 0x00080020: '', // studyDate + 0x00080030: '', // studyDescription + 0x00080090: '', // referringPhysicianName + 0x00100010: '', // patientName + 0x00100020: '', // patientId + 0x00100030: '', // patientBirthDate + 0x00100040: '', // patientSex + 0x00200010: '', // studyId + 0x0008103E: '', // seriesDescription + 0x00200011: '', // seriesNumber + 0x00080080: '', // institutionName 0x00080016: '', // sopClassUid 0x00080018: '', // sopInstanceUid 0x00080060: '', // modality @@ -37,7 +40,9 @@ var getInstanceRetrievalParams = function(studyInstanceUID, seriesInstanceUID) { 0x00281052: '', // rescaleIntercept 0x00281053: '', // rescaleSlope 0x00280002: '', // samplesPerPixel + 0x00180050: '', // sliceThickness 0x00201041: '', // sliceLocation + 0x00189327: '', // tablePosition 0x00281050: '', // windowCenter 0x00281051: '', // windowWidth 0x00280030: '', // pixelSpacing @@ -47,9 +52,14 @@ var getInstanceRetrievalParams = function(studyInstanceUID, seriesInstanceUID) { 0x00200032: '', // imagePositionPatient 0x00200037: '', // imageOrientationPatient 0x00200052: '', // frameOfReferenceUID + 0x00282110: '', // lossyImageCompression + 0x00282112: '', // lossyImageCompressionRatio + 0x00282114: '', // lossyImageCompressionMethod, + 0x00180088: '' // spacingBetweenSlices // Orthanc has a bug here so we can't retrieve sequences at the moment // https://groups.google.com/forum/#!topic/orthanc-users/ghKJfvtnK8Y + //0x00282111: '', // derivationDescription //0x00082112: '' // sourceImageSequence }; }; diff --git a/Packages/lesiontracker/client/api/measurement.js b/Packages/lesiontracker/client/api/measurement.js new file mode 100644 index 000000000..f0e4336d9 --- /dev/null +++ b/Packages/lesiontracker/client/api/measurement.js @@ -0,0 +1,102 @@ +const hasValueAtTimepoint = timepointId => { + return measurement => { + if (measurement.timepoints[timepointId]) { + return true; + } + }; +}; + +const hasNoValueAtTimepoint = timepointId => { + return measurement => { + if (measurement.timepoints[timepointId] === undefined) { + return true; + } + }; +}; + +export const MeasurementApi = { + sortOptions: { + sort: { + lesionNumberAbsolute: 1 + } + }, + + // Return all Measurements + all(withPriors=false) { + let data = Measurements.find({}, this.sortOptions).fetch(); + + // If we don't have a prior for this Timepoint, + // this filter, we should just return all of the + // available Non-Targets measurements + if (this.priorTimepointId && withPriors === true) { + return data.filter(hasValueAtTimepoint(this.priorTimepointId)) + } + + return data; + }, + + unmarked() { + const withPriors = true; + return this.all(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));; + }, + + unmarkedTargets() { + const withPriors = true; + return this.targets(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));; + }, + + unmarkedNonTargets() { + const withPriors = true; + return this.nonTargets(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));; + }, + + // Return only Target Measurements + targets(withPriors=false) { + let data = Measurements.find({ + isTarget: true + }, this.sortOptions).fetch(); + + // If we don't have a prior for this Timepoint, + // this filter, we should just return all of the + // available Non-Targets measurements + if (this.priorTimepointId && withPriors === true) { + return data.filter(hasValueAtTimepoint(this.priorTimepointId)) + } + + return data; + }, + + // Return only Non-Target Measurements + nonTargets(withPriors=false) { + let data = Measurements.find({ + isTarget: false + }, this.sortOptions).fetch(); + + // If we don't have a prior for this Timepoint, + // this filter, we should just return all of the + // available Non-Targets measurements + if (this.priorTimepointId && withPriors === true) { + return data.filter(hasValueAtTimepoint(this.priorTimepointId)) + } + + return data; + }, + + // Return only New Lesions + newLesions() { + // If we are current editing a Baseline we won't have any priors, so newLesions + // should return an empty array. + if (!this.priorTimepointId) { + return []; + } + + // Find only lesions that have no value at the previous timepoint + return this.all().filter(hasNoValueAtTimepoint(this.priorTimepointId)); + }, + + firstLesion() { + return Measurements.findOne({ + target: true + }, this.sortOptions); + } +}; diff --git a/Packages/lesiontracker/lib/api/timepoint.js b/Packages/lesiontracker/client/api/timepoint.js similarity index 74% rename from Packages/lesiontracker/lib/api/timepoint.js rename to Packages/lesiontracker/client/api/timepoint.js index 21b5beb1f..5499da0ee 100644 --- a/Packages/lesiontracker/lib/api/timepoint.js +++ b/Packages/lesiontracker/client/api/timepoint.js @@ -19,12 +19,42 @@ class TimepointApi { return this.timepoints.find().fetch(); } - // Return only the current and prior timepoints - latest() { - const options = { - limit: 2 - }; - return this.timepoints.find({}, options).fetch(); + // Return only the current timepoint + current() { + return this.timepoints.findOne({ + timepointId: this.currentTimepointId + }); + } + + prior() { + const latestDate = this.current().latestDate; + return this.timepoints.findOne({ + latestDate: { + $lt: latestDate + } + }, { + sort: { + latestDate: -1 + }, + }); + } + + // Return only the current and prior Timepoints + currentAndPrior() { + let timepoints = [this.current()]; + const prior = this.prior(); + if (prior) { + timepoints.push(prior); + } + + return timepoints; + } + + // Return only the baseline timepoint + baseline() { + return this.timepoints.findOne({ + timepointType: 'baseline' + }); } // Return only the key timepoints (current, prior, nadir and baseline) diff --git a/Packages/lesiontracker/client/components/caseProgress/caseProgress.js b/Packages/lesiontracker/client/components/caseProgress/caseProgress.js index c5a630ea9..0f7303aa8 100644 --- a/Packages/lesiontracker/client/components/caseProgress/caseProgress.js +++ b/Packages/lesiontracker/client/components/caseProgress/caseProgress.js @@ -1,45 +1,37 @@ +import { MeasurementApi } from 'meteor/lesiontracker/client/api/measurement'; + Template.caseProgress.onCreated(() => { const instance = Template.instance(); instance.progressPercent = new ReactiveVar(); instance.progressText = new ReactiveVar(); instance.isLocked = new ReactiveVar(); - - if (!instance.data.currentTimepointId) { + + const current = instance.data.timepointApi.current(); + if (!current.timepointId) { console.warn('Case has no timepointId'); return; } - const currentTimepointId = instance.data.currentTimepointId; - const timepoint = Timepoints.findOne({ - timepointId: currentTimepointId - }); + instance.isLocked.set(current.isLocked); - const timepointType = timepoint.timepointType; + // Retrieve the initial number of targets left to measure at this + // follow-up. Note that this is done outside of the reactive function + // below so that new lesions don't change the initial target count. + const withPriors = true; + const totalTargets = MeasurementApi.targets(withPriors).length; - instance.isLocked.set(timepoint.isLocked); - - if (timepointType === 'baseline') { + // If we're currently reviewing a Baseline timepoint, don't do any + // progress measurement. + if (current.timepointType === 'baseline') { instance.progressPercent.set(100); } else { - // Retrieve the initial number of targets left to measure at this - // follow-up. Note that this is done outside of the reactive function - // below so that new lesions don't change the initial target count. - const totalTargets = Measurements.find({ - isTarget: true - }).count(); - // Setup a reactive function to update the progress whenever // a measurement is made instance.autorun(() => { // Obtain the number of Measurements for which the current Timepoint has // no Measurement data - let numRemainingMeasurements = 0; - Measurements.find().forEach(measurement => { - if (!measurement.timepoints[currentTimepointId]) { - numRemainingMeasurements++; - } - }); + const numRemainingMeasurements = MeasurementApi.unmarked().length; // Update the Case Progress text with the remaining measurement count instance.progressText.set(numRemainingMeasurements); @@ -67,7 +59,7 @@ Template.caseProgress.helpers({ }, progressComplete() { - let progressPercent = Template.instance().progressPercent.get(); + const progressPercent = Template.instance().progressPercent.get(); return progressPercent === 100; } }); diff --git a/Packages/lesiontracker/client/components/lesionTable/lesionTable.js b/Packages/lesiontracker/client/components/lesionTable/lesionTable.js index 1e8e1e210..cc4b6f417 100644 --- a/Packages/lesiontracker/client/components/lesionTable/lesionTable.js +++ b/Packages/lesiontracker/client/components/lesionTable/lesionTable.js @@ -1,3 +1,5 @@ +import { MeasurementApi } from 'meteor/lesiontracker/client/api/measurement'; + Template.lesionTable.onCreated(() => { const instance = Template.instance(); @@ -13,7 +15,7 @@ Template.lesionTable.onCreated(() => { if (tableLayout === 'key') { timepoints = instance.data.timepointApi.key(); } else { - timepoints = instance.data.timepointApi.latest(); + timepoints = instance.data.timepointApi.currentAndPrior(); } // Return key timepoints @@ -47,14 +49,10 @@ Session.setDefault('NewSeriesLoaded', false); Template.lesionTable.onRendered(() => { // Find the first measurement by Lesion Number - var firstLesion = Measurements.findOne({}, { - sort: { - lesionNumber: 1 - } - }); + const firstLesion = MeasurementApi.firstLesion(); // Create an object to store the ContentId inside - var templateData = { + const templateData = { contentId: Session.get('activeContentId') }; diff --git a/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js b/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js index 6fb869383..3a019ac97 100644 --- a/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js +++ b/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js @@ -1,7 +1,7 @@ Template.lesionTableHUD.onCreated(() => { const instance = Template.instance(); - instance.data.timepoints = new ReactiveVar(instance.data.timepointApi.latest()); + instance.data.timepoints = new ReactiveVar(instance.data.timepointApi.currentAndPrior()); }); Template.lesionTableHUD.onRendered(() => { diff --git a/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html b/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html index 055b6f2b3..bef8e048a 100644 --- a/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html +++ b/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html @@ -1,14 +1,16 @@