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:
Joe Boccanfuso 2023-01-13 13:57:18 -05:00 committed by GitHub
parent a997198587
commit 58ae8690a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 103 additions and 36 deletions

View File

@ -121,11 +121,36 @@ const connectToolsToMeasurementService = (
const { toolName } = metadata;
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) {
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
@ -157,13 +182,17 @@ const connectToolsToMeasurementService = (
// on display sets added, check if there are any measurements in measurement service that need to be
// put into cornerstone tools
const addedEvt = csToolsEvents.ANNOTATION_ADDED;
const completedEvt = csToolsEvents.ANNOTATION_COMPLETED;
const updatedEvt = csToolsEvents.ANNOTATION_MODIFIED;
const removedEvt = csToolsEvents.ANNOTATION_REMOVED;
const selectionEvt = csToolsEvents.ANNOTATION_SELECTION_CHANGE;
eventTarget.addEventListener(addedEvt, addMeasurement);
eventTarget.addEventListener(completedEvt, addMeasurement);
eventTarget.addEventListener(updatedEvt, updateMeasurement);
eventTarget.addEventListener(removedEvt, removeMeasurement);
eventTarget.addEventListener(selectionEvt, selectMeasurement);
return csTools3DVer1MeasurementSource;
};

View File

@ -86,7 +86,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
const targets = Object.keys(cachedStats);
if (!targets.length) {
return;
return [];
}
const annotations = [];

View File

@ -85,7 +85,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
const targets = Object.keys(cachedStats);
if (!targets.length) {
return;
return [];
}
const annotations = [];

View File

@ -91,7 +91,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
const targets = Object.keys(cachedStats);
if (!targets.length) {
return;
return [];
}
const annotations = [];

View File

@ -255,14 +255,13 @@ function _getMappedMeasurements(MeasurementService) {
}
function _mapMeasurementToDisplay(measurement, index, types) {
const { displayText, uid, label, type } = measurement;
const { displayText, uid, label, type, selected } = measurement;
return {
uid,
label: label || '(empty)',
measurementType: type,
displayText: displayText || [],
// TODO: handle one layer down
isActive: false, // activeMeasurementItem === i + 1,
isActive: selected,
};
}

View File

@ -323,7 +323,7 @@ function _mapMeasurementToDisplay(measurement, types, DisplaySetService) {
label: measurement.label || '(empty)',
measurementType: measurement.type,
displayText: displayText || [],
isActive: false, // activeMeasurementItem === i + 1,
isActive: measurement.selected,
};
}

View File

@ -26,6 +26,7 @@ import pubSubServiceInterface from '../_shared/pubSubServiceInterface';
* @property {number} area -
* @property {Array} points -
* @property {MeasurementSource} source -
* @property {boolean} selected -
*/
/* Measurement schema keys for object validation. */
@ -56,6 +57,7 @@ const MEASUREMENT_SCHEMA_KEYS = [
'shortestDiameter',
'longestDiameter',
'cachedStats',
'selected',
];
const EVENTS = {
@ -184,6 +186,20 @@ class MeasurementService {
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.
*
@ -218,8 +234,17 @@ class MeasurementService {
version,
};
source.annotationToMeasurement = (annotationType, annotation) => {
return this.annotationToMeasurement(source, annotationType, annotation);
source.annotationToMeasurement = (
annotationType,
annotation,
isUpdate = false
) => {
return this.annotationToMeasurement(
source,
annotationType,
annotation,
isUpdate
);
};
source.remove = (measurementUID, eventDetails) => {
@ -467,9 +492,15 @@ class MeasurementService {
* @param {MeasurementSource} source The measurement source instance
* @param {string} annotationType The source annotationType
* @param {EventDetail} sourceAnnotationDetail for the annotation event
* @param {boolean} isUpdate is this an update or an add/completed instead?
* @return {string} A measurement uid
*/
annotationToMeasurement(source, annotationType, sourceAnnotationDetail) {
annotationToMeasurement(
source,
annotationType,
sourceAnnotationDetail,
isUpdate = false
) {
if (!this._isValidSource(source)) {
throw new Error('Invalid source.');
}
@ -524,19 +555,27 @@ class MeasurementService {
};
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._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
source,
measurement: newMeasurement,
notYetUpdatedAtSource: false,
});
if (isUpdate) {
this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, {
source,
measurement: newMeasurement,
notYetUpdatedAtSource: false,
});
} else {
log.info('Measurement added.', newMeasurement);
this.measurements[internalUID] = newMeasurement;
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
source,
measurement: newMeasurement,
});
}
} else {
log.info('Measurement added.', newMeasurement);
log.info('Measurement started.', newMeasurement);
this.measurements[internalUID] = newMeasurement;
this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, {
source,
measurement: newMeasurement,
});
}
return newMeasurement.uid;
@ -588,16 +627,10 @@ class MeasurementService {
}
this._addJumpToMeasurement(viewportIndex, measurementUID);
const eventName = this.EVENTS.JUMP_TO_MEASUREMENT;
const hasListeners = Object.keys(this.listeners).length > 0;
const hasCallbacks = Array.isArray(this.listeners[eventName]);
if (hasListeners && hasCallbacks) {
this.listeners[eventName].forEach(listener => {
listener.callback({ viewportIndex, measurement });
});
}
this._broadcastEvent(this.EVENTS.JUMP_TO_MEASUREMENT, {
viewportIndex,
measurement,
});
}
getJumpToMeasurement(viewportIndex) {

View File

@ -364,8 +364,9 @@ describe('MeasurementService.js', () => {
() => (addCallbackWasCalled = true)
);
/* Add new measurement */
source.annotationToMeasurement(annotationType, measurement);
/* Add new measurement - two calls needed for the start and the other for the completed*/
const uid = source.annotationToMeasurement(annotationType, measurement);
source.annotationToMeasurement(annotationType, { uid, ...measurement });
expect(addCallbackWasCalled).toBe(true);
});
@ -392,7 +393,11 @@ describe('MeasurementService.js', () => {
const uid = source.annotationToMeasurement(annotationType, measurement);
/* Update measurement */
source.annotationToMeasurement(annotationType, { uid, ...measurement });
source.annotationToMeasurement(
annotationType,
{ uid, ...measurement },
true
);
expect(updateCallbackWasCalled).toBe(true);
});
@ -418,8 +423,9 @@ describe('MeasurementService.js', () => {
/* Unsubscribe */
unsubscribe();
/* Create measurement */
source.annotationToMeasurement(annotationType, measurement);
/* Create measurement - two calls needed one to start and one to complete */
const uid = source.annotationToMeasurement(annotationType, measurement);
source.annotationToMeasurement(annotationType, { uid, ...measurement });
expect(updateCallbackWasCalled).toBe(false);
});