LT-115: Considering timepoint ID for measurement numbers
This commit is contained in:
parent
d6aeebe712
commit
c06043483d
@ -101,7 +101,7 @@ Template.viewer.onCreated(() => {
|
|||||||
Session.set('TimepointsReady', true);
|
Session.set('TimepointsReady', true);
|
||||||
|
|
||||||
const timepointIds = timepoints.map(t => t.timepointId);
|
const timepointIds = timepoints.map(t => t.timepointId);
|
||||||
instance.data.measurementApi = new OHIF.measurements.MeasurementApi(instance.data.currentTimepointId);
|
instance.data.measurementApi = new OHIF.measurements.MeasurementApi(instance.data.timepointApi);
|
||||||
const measurementsPromise = instance.data.measurementApi.retrieveMeasurements(patientId, timepointIds);
|
const measurementsPromise = instance.data.measurementApi.retrieveMeasurements(patientId, timepointIds);
|
||||||
measurementsPromise.then(() => {
|
measurementsPromise.then(() => {
|
||||||
Session.set('MeasurementsReady', true);
|
Session.set('MeasurementsReady', true);
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
||||||
|
|
||||||
|
export const ToolGroupBaseSchema = new SimpleSchema({
|
||||||
|
toolId: {
|
||||||
|
type: String,
|
||||||
|
label: 'Tool ID'
|
||||||
|
},
|
||||||
|
toolItemId: {
|
||||||
|
type: String,
|
||||||
|
label: 'Tool Item ID'
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
|
studyInstanceUid: {
|
||||||
|
type: String,
|
||||||
|
label: 'Study Instance UID'
|
||||||
|
},
|
||||||
|
timepointId: {
|
||||||
|
type: String,
|
||||||
|
label: 'Timepoint ID'
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,23 +1,9 @@
|
|||||||
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
import { ToolGroupBaseSchema } from './baseSchema';
|
||||||
import { nonTarget } from '../tools/nonTarget';
|
import { nonTarget } from '../tools/nonTarget';
|
||||||
|
|
||||||
const NonTargetSchema = new SimpleSchema({
|
|
||||||
toolId: {
|
|
||||||
type: String,
|
|
||||||
label: 'Tool ID'
|
|
||||||
},
|
|
||||||
toolItemId: {
|
|
||||||
type: String,
|
|
||||||
label: 'Tool Item ID'
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Date
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const nonTargets = {
|
export const nonTargets = {
|
||||||
id: 'nonTargets',
|
id: 'nonTargets',
|
||||||
name: 'Non-Targets',
|
name: 'Non-Targets',
|
||||||
childTools: [nonTarget],
|
childTools: [nonTarget],
|
||||||
schema: NonTargetSchema
|
schema: ToolGroupBaseSchema
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,25 +1,11 @@
|
|||||||
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
import { ToolGroupBaseSchema } from './baseSchema';
|
||||||
import { bidirectional } from '../tools/bidirectional';
|
import { bidirectional } from '../tools/bidirectional';
|
||||||
import { targetCR } from '../tools/targetCR';
|
import { targetCR } from '../tools/targetCR';
|
||||||
import { targetUN } from '../tools/targetUN';
|
import { targetUN } from '../tools/targetUN';
|
||||||
|
|
||||||
const TargetSchema = new SimpleSchema({
|
|
||||||
toolId: {
|
|
||||||
type: String,
|
|
||||||
label: 'Tool ID'
|
|
||||||
},
|
|
||||||
toolItemId: {
|
|
||||||
type: String,
|
|
||||||
label: 'Tool Item ID'
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Date
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const targets = {
|
export const targets = {
|
||||||
id: 'targets',
|
id: 'targets',
|
||||||
name: 'Targets',
|
name: 'Targets',
|
||||||
childTools: [bidirectional, targetCR, targetUN],
|
childTools: [bidirectional, targetCR, targetUN],
|
||||||
schema: TargetSchema
|
schema: ToolGroupBaseSchema
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,24 +1,10 @@
|
|||||||
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
|
import { ToolGroupBaseSchema } from './baseSchema';
|
||||||
import { length } from '../tools/length';
|
import { length } from '../tools/length';
|
||||||
import { ellipse } from '../tools/ellipse';
|
import { ellipse } from '../tools/ellipse';
|
||||||
|
|
||||||
const TempSchema = new SimpleSchema({
|
|
||||||
toolId: {
|
|
||||||
type: String,
|
|
||||||
label: 'Tool ID'
|
|
||||||
},
|
|
||||||
toolItemId: {
|
|
||||||
type: String,
|
|
||||||
label: 'Tool Item ID'
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: Date
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const temp = {
|
export const temp = {
|
||||||
id: 'temp',
|
id: 'temp',
|
||||||
name: 'Temporary',
|
name: 'Temporary',
|
||||||
childTools: [length, ellipse],
|
childTools: [length, ellipse],
|
||||||
schema: TempSchema
|
schema: ToolGroupBaseSchema
|
||||||
};
|
};
|
||||||
|
|||||||
@ -70,7 +70,7 @@ Template.measurementLocationDialog.onCreated(() => {
|
|||||||
setMeasurementNumberCallback: getSetMeasurementNumberCallbackFunction(measurementTypeId, measurementApi, timepointApi),
|
setMeasurementNumberCallback: getSetMeasurementNumberCallbackFunction(measurementTypeId, measurementApi, timepointApi),
|
||||||
// TODO: Check the position for these, the Add Label button position seems very awkward
|
// TODO: Check the position for these, the Add Label button position seems very awkward
|
||||||
getMeasurementLocationCallback: toggleLabel,
|
getMeasurementLocationCallback: toggleLabel,
|
||||||
changeMeasurementLocationCallback: toggleLabel
|
changeMeasurementLocationCallback: toggleLabel,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,9 @@ class MeasurementApi {
|
|||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(currentTimepointId) {
|
constructor(timepointApi) {
|
||||||
if (currentTimepointId) {
|
if (timepointApi) {
|
||||||
this.currentTimepointId = currentTimepointId;
|
this.timepointApi = timepointApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toolGroups = {};
|
this.toolGroups = {};
|
||||||
@ -35,14 +35,24 @@ class MeasurementApi {
|
|||||||
this.tools[tool.id] = collection;
|
this.tools[tool.id] = collection;
|
||||||
|
|
||||||
collection.find().observe({
|
collection.find().observe({
|
||||||
added(measurement) {
|
added: measurement => {
|
||||||
groupCollection.insert({
|
groupCollection.insert({
|
||||||
toolId: tool.id,
|
toolId: tool.id,
|
||||||
toolItemUid: measurement._id,
|
toolItemId: measurement._id,
|
||||||
|
timepointId: measurement.timepointId,
|
||||||
|
studyInstanceUid: measurement.studyInstanceUid,
|
||||||
createdAt: measurement.createdAt
|
createdAt: measurement.createdAt
|
||||||
});
|
});
|
||||||
|
|
||||||
const measurementCount = groupCollection.find().count();
|
const timepoint = this.timepointApi.timepoints.findOne({
|
||||||
|
timepointId: measurement.timepointId
|
||||||
|
});
|
||||||
|
const measurementCount = groupCollection.find({
|
||||||
|
studyInstanceUid: {
|
||||||
|
$in: timepoint.studyInstanceUids
|
||||||
|
}
|
||||||
|
}).count();
|
||||||
|
|
||||||
collection.update(measurement._id, {
|
collection.update(measurement._id, {
|
||||||
$set: {
|
$set: {
|
||||||
measurementNumber: measurementCount
|
measurementNumber: measurementCount
|
||||||
@ -50,13 +60,20 @@ class MeasurementApi {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
removedAt(measurement, atIndex) {
|
removedAt: (measurement, atIndex) => {
|
||||||
groupCollection.remove({
|
groupCollection.remove({
|
||||||
toolItemUid: measurement._id
|
toolItemId: measurement._id
|
||||||
|
});
|
||||||
|
|
||||||
|
const timepoint = this.timepointApi.timepoints.findOne({
|
||||||
|
timepointId: measurement.timepointId
|
||||||
});
|
});
|
||||||
|
|
||||||
toolGroup.childTools.forEach(childTool => {
|
toolGroup.childTools.forEach(childTool => {
|
||||||
this.tools[childTool.id].update({
|
this.tools[childTool.id].update({
|
||||||
|
studyInstanceUid: {
|
||||||
|
$in: timepoint.studyInstanceUids
|
||||||
|
},
|
||||||
measurementNumber: {
|
measurementNumber: {
|
||||||
$gt: atIndex
|
$gt: atIndex
|
||||||
}
|
}
|
||||||
@ -100,7 +117,7 @@ class MeasurementApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
storeMeasurements(timepoints) {
|
storeMeasurements() {
|
||||||
const storeFn = configuration.dataExchange.store;
|
const storeFn = configuration.dataExchange.store;
|
||||||
if (!_.isFunction(storeFn)) {
|
if (!_.isFunction(storeFn)) {
|
||||||
return;
|
return;
|
||||||
@ -117,6 +134,7 @@ class MeasurementApi {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const timepoints = this.timepointApi.all();
|
||||||
const timepointIds = timepoints.map(t => t.timepointId);
|
const timepointIds = timepoints.map(t => t.timepointId);
|
||||||
const patientId = timepoints[0].patientId;
|
const patientId = timepoints[0].patientId;
|
||||||
const filter = {
|
const filter = {
|
||||||
@ -126,6 +144,7 @@ class MeasurementApi {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
OHIF.log.info('Saving Measurements for timepoints:', timepoints);
|
||||||
storeFn(measurementData, filter).then(() => {
|
storeFn(measurementData, filter).then(() => {
|
||||||
OHIF.log.info('Measurement storage completed');
|
OHIF.log.info('Measurement storage completed');
|
||||||
});
|
});
|
||||||
@ -149,144 +168,20 @@ class MeasurementApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Create a better function to combine hasDataAtTimepoint and hasNoDataAtTimepoint
|
|
||||||
// because this doesn't seem very elegant...
|
|
||||||
hasDataAtTimepoint(collection, timepointId) {
|
|
||||||
// Retrieve all the data for this Measurement type (e.g. 'targets')
|
|
||||||
// which was recorded at baseline.
|
|
||||||
const dataAtTimepoint = collection.find({timepointId});
|
|
||||||
|
|
||||||
// Obtain a list of the Measurement Numbers from the
|
|
||||||
// measurements which have data at this timepoint
|
|
||||||
const numbers = dataAtTimepoint.map(m => m.measurementNumber);
|
|
||||||
|
|
||||||
// Retrieve all the data for this Measurement type which
|
|
||||||
// match the Measurement Numbers obtained above
|
|
||||||
const filter = {
|
|
||||||
measurementNumber: {
|
|
||||||
$in: numbers
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return collection.find(filter).fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
hasNoDataAtTimepoint(collection, timepointId) {
|
|
||||||
// Retrieve all the data for this Measurement type (e.g. 'targets')
|
|
||||||
// which was recorded at baseline.
|
|
||||||
const dataAtTimepoint = collection.find({timepointId});
|
|
||||||
|
|
||||||
// Obtain a list of the Measurement Numbers from the
|
|
||||||
// measurements which have data at this timepoint
|
|
||||||
const numbers = dataAtTimepoint.map(m => m.measurementNumber);
|
|
||||||
|
|
||||||
// Retrieve all the data for this Measurement type which
|
|
||||||
// match the Measurement Numbers obtained above
|
|
||||||
const filter = {
|
|
||||||
measurementNumber: {
|
|
||||||
$nin: numbers
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return collection.find(filter).fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
sortMeasurements(baselineTimepointId) {
|
sortMeasurements(baselineTimepointId) {
|
||||||
const tools = configuration.measurementTools;
|
const tools = configuration.measurementTools;
|
||||||
const hasDataAtTimepoint = this.hasDataAtTimepoint;
|
|
||||||
const hasNoDataAtTimepoint = this.hasNoDataAtTimepoint;
|
|
||||||
|
|
||||||
const includedTools = tools.filter(tool => {
|
const includedTools = tools.filter(tool => {
|
||||||
return (tool.options && tool.options.caseProgress && tool.options.caseProgress.include);
|
return (tool.options && tool.options.caseProgress && tool.options.caseProgress.include);
|
||||||
});
|
});
|
||||||
|
|
||||||
let overallMeasurementNumber = 1;
|
// Update Measurement the displayed Measurements
|
||||||
let specificToolMeasurementNumber = 1;
|
|
||||||
|
|
||||||
|
|
||||||
const updateMeasurementNumber = (collection, toolType) => {
|
|
||||||
return data => {
|
|
||||||
const filter = {
|
|
||||||
measurementNumber: data.measurementNumber,
|
|
||||||
toolType
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.update(filter, {
|
|
||||||
$set: {
|
|
||||||
measurementNumber: specificToolMeasurementNumber
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Increment the overall measurement number
|
|
||||||
specificToolMeasurementNumber += 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateMeasurementNumberOverall = (collection, toolType) => {
|
|
||||||
return data => {
|
|
||||||
const filter = {
|
|
||||||
measurementNumber: data.measurementNumber,
|
|
||||||
toolType
|
|
||||||
}
|
|
||||||
|
|
||||||
collection.update(filter, {
|
|
||||||
$set: {
|
|
||||||
measurementNumberOverall: overallMeasurementNumber
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Increment the overall measurement number
|
|
||||||
overallMeasurementNumber += 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const summarizeMeasurement = (groupObject, toolType) => {
|
|
||||||
return key => {
|
|
||||||
return {
|
|
||||||
measurementNumber: parseInt(key, 10),
|
|
||||||
entries: groupObject[key],
|
|
||||||
toolType
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// First, update Measurement Number and the displayed Measurements
|
|
||||||
includedTools.forEach(tool => {
|
includedTools.forEach(tool => {
|
||||||
const collection = this.tools[tool.id];
|
const collection = this.tools[tool.id];
|
||||||
const toolType = tool.cornerstoneToolType;
|
const measurements = collection.find().fetch();
|
||||||
const measurements = collection.find({toolType}).fetch();
|
|
||||||
const groupObject = _.groupBy(measurements, m => m.measurementNumber);
|
|
||||||
const sortedByMeasurementNumber = Object.keys(groupObject).map(summarizeMeasurement(groupObject, toolType));
|
|
||||||
sortedByMeasurementNumber.forEach(updateMeasurementNumber(collection, toolType))
|
|
||||||
|
|
||||||
measurements.forEach(measurement => {
|
measurements.forEach(measurement => {
|
||||||
OHIF.measurements.syncMeasurementAndToolData(measurement);
|
OHIF.measurements.syncMeasurementAndToolData(measurement);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset specificToolMeasurementNumber
|
|
||||||
specificToolMeasurementNumber = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Next, handle the overall measurement number.
|
|
||||||
// First, handle data that has a measurement at baseline
|
|
||||||
includedTools.forEach(tool => {
|
|
||||||
const collection = this.tools[tool.id];
|
|
||||||
const toolType = tool.cornerstoneToolType;
|
|
||||||
const measurements = hasDataAtTimepoint(collection, baselineTimepointId);
|
|
||||||
const groupObject = _.groupBy(measurements, m => m.measurementNumber);
|
|
||||||
const sortedByMeasurementNumber = Object.keys(groupObject).map(summarizeMeasurement(groupObject, toolType));
|
|
||||||
sortedByMeasurementNumber.forEach(updateMeasurementNumberOverall(collection, toolType))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Next, handle New Measurements (i.e. no baseline data)
|
|
||||||
// Note that this cannot be combined with the loop above due to the incrementing of the overallMeasurementNumber
|
|
||||||
includedTools.forEach(tool => {
|
|
||||||
const collection = this.tools[tool.id];
|
|
||||||
const toolType = tool.cornerstoneToolType;
|
|
||||||
const measurements = hasNoDataAtTimepoint(collection, baselineTimepointId);
|
|
||||||
const groupObject = _.groupBy(measurements, m => m.measurementNumber);
|
|
||||||
const sortedByMeasurementNumber = Object.keys(groupObject).map(summarizeMeasurement(groupObject, toolType));
|
|
||||||
sortedByMeasurementNumber.forEach(updateMeasurementNumberOverall(collection, toolType));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,19 +217,6 @@ class MeasurementApi {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the measurement numbers for the remaining measurements
|
|
||||||
const updateFilter = _.clone(filter);
|
|
||||||
updateFilter.measurementNumber = {
|
|
||||||
$gt: measurementNumber
|
|
||||||
};
|
|
||||||
collection.update(updateFilter, {
|
|
||||||
$inc: {
|
|
||||||
measurementNumber: -1
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
multi: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Synchronize the updated measurements with Cornerstone Tools
|
// Synchronize the updated measurements with Cornerstone Tools
|
||||||
// toolData to make sure the displayed measurements show 'Target X' correctly
|
// toolData to make sure the displayed measurements show 'Target X' correctly
|
||||||
const syncFilter = _.clone(updateFilter);
|
const syncFilter = _.clone(updateFilter);
|
||||||
|
|||||||
@ -11,16 +11,10 @@ Template.caseProgress.onCreated(() => {
|
|||||||
instance.isLocked = new ReactiveVar();
|
instance.isLocked = new ReactiveVar();
|
||||||
|
|
||||||
instance.saveData = () => {
|
instance.saveData = () => {
|
||||||
|
instance.data.measurementApi.storeMeasurements();
|
||||||
const timepointApi = instance.data.timepointApi;
|
|
||||||
const timepoints = timepointApi.all();
|
|
||||||
OHIF.log.info('Saving Measurements for timepoints:');
|
|
||||||
OHIF.log.info(timepoints);
|
|
||||||
instance.data.measurementApi.storeMeasurements(timepoints);
|
|
||||||
|
|
||||||
// Clear signaled unsaved changes...
|
// Clear signaled unsaved changes...
|
||||||
OHIF.ui.unsavedChanges.clear('viewer.studyViewer.measurements.*');
|
OHIF.ui.unsavedChanges.clear('viewer.studyViewer.measurements.*');
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.unsavedChangesHandler = () => {
|
instance.unsavedChangesHandler = () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user