fix: Clicking on measurement should highlight the measurement row in the measurement panel OHIF-433 (#3111)
* fix: OHIF-433 Clicking on measurement should highlight the measurement row in the measurement panel Added selected flag to each OHIF measurement. Listening to CS3D ANNOTATION_SELECTION_CHANGE to keep the selected flag in sync. * fix: OHIF-433 Clicking on measurement should highlight the measurement row in the measurement panel Fixed broken unit tests. * PR feedback from Alireza: - use MEASUREMENT_UPDATED event for selection changed - added TODO to eventually have CS3D introduce the selected flag per annotation - added MeasurementService._publishEvent * PR feedback: now using _broadcastEvent
This commit is contained in:
parent
a997198587
commit
58ae8690a7
@ -121,11 +121,36 @@ const connectToolsToMeasurementService = (
|
|||||||
const { toolName } = metadata;
|
const { toolName } = metadata;
|
||||||
|
|
||||||
annotationModifiedEventDetail.uid = annotationUID;
|
annotationModifiedEventDetail.uid = annotationUID;
|
||||||
annotationToMeasurement(toolName, annotationModifiedEventDetail);
|
// Passing true to indicate this is an update and NOT a annotation (start) completion.
|
||||||
|
annotationToMeasurement(toolName, annotationModifiedEventDetail, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to update measurement:', error);
|
console.warn('Failed to update measurement:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function selectMeasurement(csToolsEvent) {
|
||||||
|
try {
|
||||||
|
const annotationSelectionEventDetail = csToolsEvent.detail;
|
||||||
|
|
||||||
|
const {
|
||||||
|
added: addedSelectedAnnotationUIDs,
|
||||||
|
removed: removedSelectedAnnotationUIDs,
|
||||||
|
} = annotationSelectionEventDetail;
|
||||||
|
|
||||||
|
if (removedSelectedAnnotationUIDs) {
|
||||||
|
removedSelectedAnnotationUIDs.forEach(annotationUID =>
|
||||||
|
MeasurementService.setMeasurementSelected(annotationUID, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedSelectedAnnotationUIDs) {
|
||||||
|
addedSelectedAnnotationUIDs.forEach(annotationUID =>
|
||||||
|
MeasurementService.setMeasurementSelected(annotationUID, true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to select and unselect measurements:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When csTools fires a removed event, remove the same measurement
|
* When csTools fires a removed event, remove the same measurement
|
||||||
@ -157,13 +182,17 @@ const connectToolsToMeasurementService = (
|
|||||||
|
|
||||||
// on display sets added, check if there are any measurements in measurement service that need to be
|
// on display sets added, check if there are any measurements in measurement service that need to be
|
||||||
// put into cornerstone tools
|
// put into cornerstone tools
|
||||||
|
const addedEvt = csToolsEvents.ANNOTATION_ADDED;
|
||||||
const completedEvt = csToolsEvents.ANNOTATION_COMPLETED;
|
const completedEvt = csToolsEvents.ANNOTATION_COMPLETED;
|
||||||
const updatedEvt = csToolsEvents.ANNOTATION_MODIFIED;
|
const updatedEvt = csToolsEvents.ANNOTATION_MODIFIED;
|
||||||
const removedEvt = csToolsEvents.ANNOTATION_REMOVED;
|
const removedEvt = csToolsEvents.ANNOTATION_REMOVED;
|
||||||
|
const selectionEvt = csToolsEvents.ANNOTATION_SELECTION_CHANGE;
|
||||||
|
|
||||||
|
eventTarget.addEventListener(addedEvt, addMeasurement);
|
||||||
eventTarget.addEventListener(completedEvt, addMeasurement);
|
eventTarget.addEventListener(completedEvt, addMeasurement);
|
||||||
eventTarget.addEventListener(updatedEvt, updateMeasurement);
|
eventTarget.addEventListener(updatedEvt, updateMeasurement);
|
||||||
eventTarget.addEventListener(removedEvt, removeMeasurement);
|
eventTarget.addEventListener(removedEvt, removeMeasurement);
|
||||||
|
eventTarget.addEventListener(selectionEvt, selectMeasurement);
|
||||||
|
|
||||||
return csTools3DVer1MeasurementSource;
|
return csTools3DVer1MeasurementSource;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -86,7 +86,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
const targets = Object.keys(cachedStats);
|
const targets = Object.keys(cachedStats);
|
||||||
|
|
||||||
if (!targets.length) {
|
if (!targets.length) {
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const annotations = [];
|
const annotations = [];
|
||||||
|
|||||||
@ -85,7 +85,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
const targets = Object.keys(cachedStats);
|
const targets = Object.keys(cachedStats);
|
||||||
|
|
||||||
if (!targets.length) {
|
if (!targets.length) {
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const annotations = [];
|
const annotations = [];
|
||||||
|
|||||||
@ -91,7 +91,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
const targets = Object.keys(cachedStats);
|
const targets = Object.keys(cachedStats);
|
||||||
|
|
||||||
if (!targets.length) {
|
if (!targets.length) {
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const annotations = [];
|
const annotations = [];
|
||||||
|
|||||||
@ -255,14 +255,13 @@ function _getMappedMeasurements(MeasurementService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _mapMeasurementToDisplay(measurement, index, types) {
|
function _mapMeasurementToDisplay(measurement, index, types) {
|
||||||
const { displayText, uid, label, type } = measurement;
|
const { displayText, uid, label, type, selected } = measurement;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid,
|
uid,
|
||||||
label: label || '(empty)',
|
label: label || '(empty)',
|
||||||
measurementType: type,
|
measurementType: type,
|
||||||
displayText: displayText || [],
|
displayText: displayText || [],
|
||||||
// TODO: handle one layer down
|
isActive: selected,
|
||||||
isActive: false, // activeMeasurementItem === i + 1,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -323,7 +323,7 @@ function _mapMeasurementToDisplay(measurement, types, DisplaySetService) {
|
|||||||
label: measurement.label || '(empty)',
|
label: measurement.label || '(empty)',
|
||||||
measurementType: measurement.type,
|
measurementType: measurement.type,
|
||||||
displayText: displayText || [],
|
displayText: displayText || [],
|
||||||
isActive: false, // activeMeasurementItem === i + 1,
|
isActive: measurement.selected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import pubSubServiceInterface from '../_shared/pubSubServiceInterface';
|
|||||||
* @property {number} area -
|
* @property {number} area -
|
||||||
* @property {Array} points -
|
* @property {Array} points -
|
||||||
* @property {MeasurementSource} source -
|
* @property {MeasurementSource} source -
|
||||||
|
* @property {boolean} selected -
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Measurement schema keys for object validation. */
|
/* Measurement schema keys for object validation. */
|
||||||
@ -56,6 +57,7 @@ const MEASUREMENT_SCHEMA_KEYS = [
|
|||||||
'shortestDiameter',
|
'shortestDiameter',
|
||||||
'longestDiameter',
|
'longestDiameter',
|
||||||
'cachedStats',
|
'cachedStats',
|
||||||
|
'selected',
|
||||||
];
|
];
|
||||||
|
|
||||||
const EVENTS = {
|
const EVENTS = {
|
||||||
@ -184,6 +186,20 @@ class MeasurementService {
|
|||||||
return measurement;
|
return measurement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMeasurementSelected(measurementUID, selected) {
|
||||||
|
const measurement = this.getMeasurement(measurementUID);
|
||||||
|
if (!measurement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
measurement.selected = selected;
|
||||||
|
|
||||||
|
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
|
||||||
|
source: measurement.source,
|
||||||
|
measurement,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new source.
|
* Create a new source.
|
||||||
*
|
*
|
||||||
@ -218,8 +234,17 @@ class MeasurementService {
|
|||||||
version,
|
version,
|
||||||
};
|
};
|
||||||
|
|
||||||
source.annotationToMeasurement = (annotationType, annotation) => {
|
source.annotationToMeasurement = (
|
||||||
return this.annotationToMeasurement(source, annotationType, annotation);
|
annotationType,
|
||||||
|
annotation,
|
||||||
|
isUpdate = false
|
||||||
|
) => {
|
||||||
|
return this.annotationToMeasurement(
|
||||||
|
source,
|
||||||
|
annotationType,
|
||||||
|
annotation,
|
||||||
|
isUpdate
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
source.remove = (measurementUID, eventDetails) => {
|
source.remove = (measurementUID, eventDetails) => {
|
||||||
@ -467,9 +492,15 @@ class MeasurementService {
|
|||||||
* @param {MeasurementSource} source The measurement source instance
|
* @param {MeasurementSource} source The measurement source instance
|
||||||
* @param {string} annotationType The source annotationType
|
* @param {string} annotationType The source annotationType
|
||||||
* @param {EventDetail} sourceAnnotationDetail for the annotation event
|
* @param {EventDetail} sourceAnnotationDetail for the annotation event
|
||||||
|
* @param {boolean} isUpdate is this an update or an add/completed instead?
|
||||||
* @return {string} A measurement uid
|
* @return {string} A measurement uid
|
||||||
*/
|
*/
|
||||||
annotationToMeasurement(source, annotationType, sourceAnnotationDetail) {
|
annotationToMeasurement(
|
||||||
|
source,
|
||||||
|
annotationType,
|
||||||
|
sourceAnnotationDetail,
|
||||||
|
isUpdate = false
|
||||||
|
) {
|
||||||
if (!this._isValidSource(source)) {
|
if (!this._isValidSource(source)) {
|
||||||
throw new Error('Invalid source.');
|
throw new Error('Invalid source.');
|
||||||
}
|
}
|
||||||
@ -524,19 +555,27 @@ class MeasurementService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.measurements[internalUID]) {
|
if (this.measurements[internalUID]) {
|
||||||
|
// TODO: Ultimately, each annotation should have a selected flag right from the soure.
|
||||||
|
// For now, it is just added in OHIF here and in setMeasurementSelected.
|
||||||
|
newMeasurement.selected = this.measurements[internalUID].selected;
|
||||||
this.measurements[internalUID] = newMeasurement;
|
this.measurements[internalUID] = newMeasurement;
|
||||||
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
|
if (isUpdate) {
|
||||||
source,
|
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
|
||||||
measurement: newMeasurement,
|
source,
|
||||||
notYetUpdatedAtSource: false,
|
measurement: newMeasurement,
|
||||||
});
|
notYetUpdatedAtSource: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.info('Measurement added.', newMeasurement);
|
||||||
|
this.measurements[internalUID] = newMeasurement;
|
||||||
|
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
|
||||||
|
source,
|
||||||
|
measurement: newMeasurement,
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info('Measurement added.', newMeasurement);
|
log.info('Measurement started.', newMeasurement);
|
||||||
this.measurements[internalUID] = newMeasurement;
|
this.measurements[internalUID] = newMeasurement;
|
||||||
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
|
|
||||||
source,
|
|
||||||
measurement: newMeasurement,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newMeasurement.uid;
|
return newMeasurement.uid;
|
||||||
@ -588,16 +627,10 @@ class MeasurementService {
|
|||||||
}
|
}
|
||||||
this._addJumpToMeasurement(viewportIndex, measurementUID);
|
this._addJumpToMeasurement(viewportIndex, measurementUID);
|
||||||
|
|
||||||
const eventName = this.EVENTS.JUMP_TO_MEASUREMENT;
|
this._broadcastEvent(this.EVENTS.JUMP_TO_MEASUREMENT, {
|
||||||
|
viewportIndex,
|
||||||
const hasListeners = Object.keys(this.listeners).length > 0;
|
measurement,
|
||||||
const hasCallbacks = Array.isArray(this.listeners[eventName]);
|
});
|
||||||
|
|
||||||
if (hasListeners && hasCallbacks) {
|
|
||||||
this.listeners[eventName].forEach(listener => {
|
|
||||||
listener.callback({ viewportIndex, measurement });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getJumpToMeasurement(viewportIndex) {
|
getJumpToMeasurement(viewportIndex) {
|
||||||
|
|||||||
@ -364,8 +364,9 @@ describe('MeasurementService.js', () => {
|
|||||||
() => (addCallbackWasCalled = true)
|
() => (addCallbackWasCalled = true)
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Add new measurement */
|
/* Add new measurement - two calls needed for the start and the other for the completed*/
|
||||||
source.annotationToMeasurement(annotationType, measurement);
|
const uid = source.annotationToMeasurement(annotationType, measurement);
|
||||||
|
source.annotationToMeasurement(annotationType, { uid, ...measurement });
|
||||||
|
|
||||||
expect(addCallbackWasCalled).toBe(true);
|
expect(addCallbackWasCalled).toBe(true);
|
||||||
});
|
});
|
||||||
@ -392,7 +393,11 @@ describe('MeasurementService.js', () => {
|
|||||||
const uid = source.annotationToMeasurement(annotationType, measurement);
|
const uid = source.annotationToMeasurement(annotationType, measurement);
|
||||||
|
|
||||||
/* Update measurement */
|
/* Update measurement */
|
||||||
source.annotationToMeasurement(annotationType, { uid, ...measurement });
|
source.annotationToMeasurement(
|
||||||
|
annotationType,
|
||||||
|
{ uid, ...measurement },
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
expect(updateCallbackWasCalled).toBe(true);
|
expect(updateCallbackWasCalled).toBe(true);
|
||||||
});
|
});
|
||||||
@ -418,8 +423,9 @@ describe('MeasurementService.js', () => {
|
|||||||
/* Unsubscribe */
|
/* Unsubscribe */
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
|
||||||
/* Create measurement */
|
/* Create measurement - two calls needed one to start and one to complete */
|
||||||
source.annotationToMeasurement(annotationType, measurement);
|
const uid = source.annotationToMeasurement(annotationType, measurement);
|
||||||
|
source.annotationToMeasurement(annotationType, { uid, ...measurement });
|
||||||
|
|
||||||
expect(updateCallbackWasCalled).toBe(false);
|
expect(updateCallbackWasCalled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user