feat(MeasurementService): add rendering of unmapped measurements (#5416)

This commit is contained in:
Igor Octaviano authored and GitHub committed 2025-10-06 08:50:17 -04:00
1 parent 639863a8ef
commit 851e74d7b8
13 files changed
+345 -92

No files matched your search

@@ -1,6 +1,7 @@
import log from '../../log';
import guid from '../../utils/guid';
import { PubSubService } from '../_shared/pubSubServiceInterface';
import { DicomMetadataStore } from '../DicomMetadataStore/DicomMetadataStore';
/**
* Measurement source schema
@@ -127,7 +128,6 @@ class MeasurementService extends PubSubService {
public readonly VALUE_TYPES = VALUE_TYPES;
private measurements = new Map();
private unmappedMeasurements = new Map();
private isMeasurementDeletedIndividually: boolean;
private sources = {};
@@ -495,6 +495,7 @@ class MeasurementService extends PubSubService {
);
if (!sourceMapping) {
console.log('No source mapping', source.uid, annotationType, source);
this.addUnmappedMeasurement(sourceAnnotationDetail, source);
return;
}
const { toMeasurementSchema } = sourceMapping;
@@ -507,16 +508,7 @@ class MeasurementService extends PubSubService {
measurement.source = source;
} catch (error) {
// Todo: handle other
this.unmappedMeasurements.set(sourceAnnotationDetail.uid, {
...sourceAnnotationDetail,
source: {
name: source.name,
version: source.version,
uid: source.uid,
},
});
this.addUnmappedMeasurement(sourceAnnotationDetail, source);
console.log('Failed to map', error);
throw new Error(
`Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}: ${error.message}`
@@ -577,14 +569,98 @@ class MeasurementService extends PubSubService {
return newMeasurement.uid;
}
/**
* Recursively searches for any attribute at any level in the object
* @param {any} obj The object to search
* @param {string} attributeName The name of the attribute to find
* @returns {any} The attribute value if found, undefined otherwise
*/
private findAttributeRecursively(obj: any, attributeName: string): any {
if (!obj || typeof obj !== 'object') {
return undefined;
}
if (obj[attributeName]) {
return obj[attributeName];
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const result = this.findAttributeRecursively(obj[key], attributeName);
if (result) {
return result;
}
}
}
return undefined;
}
/**
* Adds an unmapped measurement to the measurement service.
*
* @param {any} sourceAnnotationDetail The source annotation detail
* @param {any} source The source
*/
private addUnmappedMeasurement(sourceAnnotationDetail: any, source: any) {
if (sourceAnnotationDetail.annotation?.invalidated === true) {
console.log('Measurement is invalidated, skipping...', sourceAnnotationDetail);
return;
}
if (sourceAnnotationDetail.annotation?.isPreview === true) {
console.log('Measurement is preview, skipping...', sourceAnnotationDetail);
return;
}
const metadata = this.findAttributeRecursively(sourceAnnotationDetail, 'metadata');
const label = this.findAttributeRecursively(sourceAnnotationDetail, 'label');
const referencedImageId = this.findAttributeRecursively(
sourceAnnotationDetail,
'referencedImageId'
);
const displaySetInstanceUID = this.findAttributeRecursively(
sourceAnnotationDetail,
'displaySetInstanceUID'
);
const measurement = {
...sourceAnnotationDetail,
isUnmapped: true,
statusTooltip: 'This measurement is not compatible with this application',
source: {
name: source.name,
version: source.version,
uid: source.uid,
},
};
if (metadata) {
measurement.metadata = metadata;
}
if (label) {
measurement.label = label;
}
if (referencedImageId) {
measurement.referencedImageId = referencedImageId;
const instance = DicomMetadataStore.getInstanceByImageId(referencedImageId);
measurement.referenceStudyUID = instance.StudyInstanceUID;
measurement.referenceSeriesUID = instance.SeriesInstanceUID;
}
if (displaySetInstanceUID) {
measurement.displaySetInstanceUID = displaySetInstanceUID;
}
this.measurements.set(sourceAnnotationDetail.uid, measurement);
}
/**
* Removes a measurement and broadcasts the removed event.
*
* @param {string} measurementUID The measurement uid
*/
remove(measurementUID: string): void {
const measurement =
this.measurements.get(measurementUID) || this.unmappedMeasurements.get(measurementUID);
const measurement = this.measurements.get(measurementUID);
if (!measurementUID || !measurement) {
console.debug(`No uid provided, or unable to find measurement by uid.`);
@@ -593,7 +669,6 @@ class MeasurementService extends PubSubService {
const source = measurement.source;
this.unmappedMeasurements.delete(measurementUID);
this.measurements.delete(measurementUID);
this.isMeasurementDeletedIndividually = true;
this._broadcastEvent(this.EVENTS.MEASUREMENT_REMOVED, {
@@ -609,14 +684,13 @@ class MeasurementService extends PubSubService {
const measurements = [];
for (const measurementUID of measurementUIDs) {
const measurement =
this.measurements.get(measurementUID) || this.unmappedMeasurements.get(measurementUID);
this.measurements.get(measurementUID)
if (!measurementUID || !measurement) {
console.debug(`No uid provided, or unable to find measurement by uid.`);
continue;
}
this.unmappedMeasurements.delete(measurementUID);
this.measurements.delete(measurementUID);
measurements.push(measurement);
}
@@ -634,11 +708,7 @@ class MeasurementService extends PubSubService {
public clearMeasurements(filter?: MeasurementFilter) {
// Make a copy of the measurements
const toClear = this.getMeasurements(filter);
const unmappedClear = filter
? [...this.unmappedMeasurements.values()].filter(filter)
: this.unmappedMeasurements;
const measurements = [...toClear, ...unmappedClear];
unmappedClear.forEach(measurement => this.unmappedMeasurements.delete(measurement.uid));
const measurements = [...toClear];
toClear.forEach(measurement => this.measurements.delete(measurement.uid));
this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED, { measurements });
}
@@ -657,7 +727,8 @@ class MeasurementService extends PubSubService {
*/
public jumpToMeasurement(viewportId: string, measurementUID: string): void {
const measurement = this.measurements.get(measurementUID);
const measurement =
this.measurements.get(measurementUID)
if (!measurement) {
log.warn(`No measurement uid, or unable to find by uid.`);
@@ -779,7 +850,8 @@ class MeasurementService extends PubSubService {
};
public toggleLockMeasurement(measurementUID: string): void {
const measurement = this.measurements.get(measurementUID);
const measurement =
this.measurements.get(measurementUID)
if (!measurement) {
console.debug(`No measurement found for uid: ${measurementUID}`);
@@ -796,7 +868,8 @@ class MeasurementService extends PubSubService {
}
public toggleVisibilityMeasurement(measurementUID: string, visibility?: boolean): void {
const measurement = this.measurements.get(measurementUID);
const measurement =
this.measurements.get(measurementUID)
if (!measurement) {
console.debug(`No measurement found for uid: ${measurementUID}`);
@@ -820,7 +893,8 @@ class MeasurementService extends PubSubService {
}
public updateColorMeasurement(measurementUID: string, color: number[]): void {
const measurement = this.measurements.get(measurementUID);
const measurement =
this.measurements.get(measurementUID)
if (!measurement) {
console.debug(`No measurement found for uid: ${measurementUID}`);
@@ -10,6 +10,7 @@ export function filterMeasurementsBySeriesUID(selectedSeries: string[]) {
return measurement => selectedSeries.includes(measurement.referenceSeriesUID);
}
/** A filter that filters for measurements belonging to the study */
export function filterMeasurementsByStudyUID(studyUID) {
return measurement => measurement.referenceStudyUID == studyUID;
}