feat(MeasurementService): add rendering of unmapped measurements (#5416)
This commit is contained in:
1 parent
639863a8ef
commit
851e74d7b8
13 files changed
+345
-92
No files matched your search
@@ -22,7 +22,7 @@ const {
|
||||
CodeScheme: Cornerstone3DCodeScheme,
|
||||
} = adaptersSR.Cornerstone3D;
|
||||
|
||||
type InstanceMetadata = Types.InstanceMetadata;
|
||||
type InstanceMetadata = OhifTypes.InstanceMetadata;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -69,9 +69,10 @@ function addInstances(instances: InstanceMetadata[], _displaySetService: Display
|
||||
* DICOM SR SOP Class Handler
|
||||
* For all referenced images in the TID 1500/300 sections, add an image to the
|
||||
* display.
|
||||
* @param instances is a set of instances all from the same series
|
||||
* @param servicesManager is the services that can be used for creating
|
||||
* @returns The list of display sets created for the given instances object
|
||||
* @param {InstanceMetadata[]} instances - A set of instances all from the same series
|
||||
* @param {AppTypes.ServicesManager} servicesManager - The services that can be used for creating
|
||||
* @param {AppTypes.ExtensionManager} extensionManager - The extension manager
|
||||
* @returns {Types.DisplaySet[]} The list of display sets created for the given instances object
|
||||
*/
|
||||
function _getDisplaySetsFromSeries(
|
||||
instances,
|
||||
@@ -143,7 +144,7 @@ function _getDisplaySetsFromSeries(
|
||||
* @param extensionManager - The extension manager containing data sources.
|
||||
*/
|
||||
async function _load(
|
||||
srDisplaySet: Types.DisplaySet,
|
||||
srDisplaySet: OhifTypes.DisplaySet,
|
||||
servicesManager: AppTypes.ServicesManager,
|
||||
extensionManager: AppTypes.ExtensionManager
|
||||
) {
|
||||
@@ -227,17 +228,9 @@ function _measurementBelongsToDisplaySet({ measurement, displaySet }) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if measurements can be added to a display set.
|
||||
*
|
||||
* @param srDisplaySet - The source display set containing measurements.
|
||||
* @param newDisplaySet - The new display set to check if measurements can be added.
|
||||
* @param dataSource - The data source used to retrieve image IDs.
|
||||
* @param servicesManager - The services manager.
|
||||
*/
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
srDisplaySet: OhifTypes.DisplaySet,
|
||||
newDisplaySet: OhifTypes.DisplaySet,
|
||||
dataSource,
|
||||
servicesManager: AppTypes.ServicesManager
|
||||
) {
|
||||
@@ -289,9 +282,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
is3DMeasurement &&
|
||||
_measurementBelongsToDisplaySet({ measurement, displaySet: newDisplaySet })
|
||||
) {
|
||||
_measurementBelongsToDisplaySet({ measurement, displaySet: newDisplaySet })
|
||||
|
||||
addSRAnnotation(measurement, null, null);
|
||||
addSRAnnotation({ measurement, displaySet: newDisplaySet });
|
||||
measurement.loaded = true;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
@@ -312,15 +303,12 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
imageId &&
|
||||
_measurementReferencesSOPInstanceUID(measurement, ReferencedSOPInstanceUID, frame)
|
||||
) {
|
||||
addSRAnnotation(measurement, imageId, frame);
|
||||
|
||||
// Update measurement properties
|
||||
addSRAnnotation({ measurement, imageId, frameNumber: frame, displaySet: newDisplaySet });
|
||||
measurement.loaded = true;
|
||||
measurement.imageId = imageId;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
measurement.ReferencedSOPInstanceUID = ReferencedSOPInstanceUID;
|
||||
measurement.frameNumber = frame;
|
||||
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
}
|
||||
}
|
||||
@@ -328,10 +316,10 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
|
||||
/**
|
||||
* Checks if a measurement references a specific SOP Instance UID.
|
||||
* @param measurement - The measurement object.
|
||||
* @param SOPInstanceUID - The SOP Instance UID to check against.
|
||||
* @param frameNumber - The frame number to check against (optional).
|
||||
* @returns True if the measurement references the specified SOP Instance UID, false otherwise.
|
||||
* @param {any} measurement - The measurement object.
|
||||
* @param {string} sopInstanceUID - The SOP Instance UID to check against.
|
||||
* @param {number} frameNumber - The frame number to check against (optional).
|
||||
* @returns {boolean} True if the measurement references the specified SOP Instance UID, false otherwise.
|
||||
*/
|
||||
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
||||
const { coords } = measurement;
|
||||
@@ -363,10 +351,8 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
||||
/**
|
||||
* Retrieves the SOP class handler module.
|
||||
*
|
||||
* @param {Object} options - The options for retrieving the SOP class handler module.
|
||||
* @param {Object} options.servicesManager - The services manager.
|
||||
* @param {Object} options.extensionManager - The extension manager.
|
||||
* @returns {Array} An array containing the SOP class handler module.
|
||||
* @param {OhifTypes.Extensions.ExtensionParams} params - The extension parameters.
|
||||
* @returns {Array} An array containing the SOP class handler modules.
|
||||
*/
|
||||
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
|
||||
const { servicesManager, extensionManager } = params;
|
||||
@@ -390,8 +376,8 @@ function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams)
|
||||
/**
|
||||
* Retrieves the measurements from the ImagingMeasurementReportContentSequence.
|
||||
*
|
||||
* @param {Array} ImagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
|
||||
* @returns {Array} - The array of measurements.
|
||||
* @param {any[]} imagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
|
||||
* @returns {any[]} The array of measurements.
|
||||
*/
|
||||
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||
@@ -429,8 +415,8 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
/**
|
||||
* Retrieves merged content sequences by tracking unique identifiers.
|
||||
*
|
||||
* @param {Array} MeasurementGroups - The measurement groups.
|
||||
* @returns {Object} - The merged content sequences by tracking unique identifiers.
|
||||
* @param {any[]} measurementGroups - The measurement groups.
|
||||
* @returns {Object} The merged content sequences by tracking unique identifiers.
|
||||
*/
|
||||
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
||||
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
||||
@@ -477,8 +463,8 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
||||
* it calls the _processTID1410Measurement function.
|
||||
* Otherwise, it calls the _processNonGeometricallyDefinedMeasurement function.
|
||||
*
|
||||
* @param {Array<Object>} mergedContentSequence - The merged content sequence to process.
|
||||
* @returns {any} - The processed measurement result.
|
||||
* @param {any[]} mergedContentSequence - The merged content sequence to process.
|
||||
* @returns {any} The processed measurement result.
|
||||
*/
|
||||
function _processMeasurement(mergedContentSequence) {
|
||||
if (mergedContentSequence.some(group => isScoordOr3d(group) && !isTextPosition(group))) {
|
||||
@@ -493,8 +479,8 @@ function _processMeasurement(mergedContentSequence) {
|
||||
* TID 1410 style measurements have a SCOORD or SCOORD3D at the top level,
|
||||
* and non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D.
|
||||
*
|
||||
* @param mergedContentSequence - The merged content sequence containing the measurements.
|
||||
* @returns The measurement object containing the loaded status, labels, coordinates, tracking unique identifier, and tracking identifier.
|
||||
* @param {any[]} mergedContentSequence - The merged content sequence containing the measurements.
|
||||
* @returns {any} The measurement object containing the loaded status, labels, coordinates, tracking unique identifier, and tracking identifier.
|
||||
*/
|
||||
function _processTID1410Measurement(mergedContentSequence) {
|
||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||
@@ -567,8 +553,8 @@ function _processTID1410Measurement(mergedContentSequence) {
|
||||
/**
|
||||
* Processes the non-geometrically defined measurement from the merged content sequence.
|
||||
*
|
||||
* @param mergedContentSequence The merged content sequence containing the measurement data.
|
||||
* @returns The processed measurement object.
|
||||
* @param {any[]} mergedContentSequence The merged content sequence containing the measurement data.
|
||||
* @returns {any} The processed measurement object.
|
||||
*/
|
||||
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
||||
@@ -668,8 +654,8 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
|
||||
/**
|
||||
* Extracts coordinates from a graphic item of type SCOORD or SCOORD3D.
|
||||
* @param {object} graphicItem - The graphic item containing the coordinates.
|
||||
* @returns {object} - The extracted coordinates.
|
||||
* @param {any} graphicItem - The graphic item containing the coordinates.
|
||||
* @returns {any} The extracted coordinates.
|
||||
*/
|
||||
const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
||||
const { ValueType, GraphicType, GraphicData } = graphicItem;
|
||||
@@ -683,9 +669,9 @@ const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
||||
|
||||
/**
|
||||
* Retrieves the label and value from the provided ConceptNameCodeSequence and MeasuredValueSequence.
|
||||
* @param {Object} ConceptNameCodeSequence - The ConceptNameCodeSequence object.
|
||||
* @param {Object} MeasuredValueSequence - The MeasuredValueSequence object.
|
||||
* @returns {Object} - An object containing the label and value.
|
||||
* @param {any} conceptNameCodeSequence - The ConceptNameCodeSequence object.
|
||||
* @param {any} measuredValueSequence - The MeasuredValueSequence object.
|
||||
* @returns {Object} An object containing the label and value.
|
||||
* The label represents the CodeMeaning from the ConceptNameCodeSequence.
|
||||
* The value represents the formatted NumericValue and CodeValue from the MeasuredValueSequence.
|
||||
* Example: { label: 'Long Axis', value: '31.00 mm' }
|
||||
@@ -704,8 +690,8 @@ function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredVal
|
||||
/**
|
||||
* Retrieves a list of referenced images from the Imaging Measurement Report Content Sequence.
|
||||
*
|
||||
* @param {Array} ImagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
|
||||
* @returns {Array} - The list of referenced images.
|
||||
* @param {any[]} imagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
|
||||
* @returns {any[]} The list of referenced images.
|
||||
*/
|
||||
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
||||
@@ -752,7 +738,7 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
* Otherwise, the sequence is wrapped in an array and returned.
|
||||
*
|
||||
* @param {any} sequence - The DICOM sequence to convert.
|
||||
* @returns {any[]} - The converted array.
|
||||
* @returns {any[]} The converted array.
|
||||
*/
|
||||
function _getSequenceAsArray(sequence) {
|
||||
if (!sequence) {
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
LengthTool,
|
||||
PlanarFreehandROITool,
|
||||
RectangleROITool,
|
||||
utilities as csToolsUtils,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { Types, MeasurementService } from '@ohif/core';
|
||||
import { Types } from '@ohif/core';
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
|
||||
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
|
||||
import SCOORD3DPointTool from './tools/SCOORD3DPointTool';
|
||||
import SRSCOOR3DProbeMapper from './utils/SRSCOOR3DProbeMapper';
|
||||
@@ -28,7 +28,7 @@ export default function init({
|
||||
configuration = {},
|
||||
servicesManager,
|
||||
}: Types.Extensions.ExtensionParams): void {
|
||||
const { measurementService, cornerstoneViewportService } = servicesManager.services;
|
||||
const { measurementService } = servicesManager.services;
|
||||
|
||||
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
|
||||
addToolInstance(toolNames.SRLength, LengthTool);
|
||||
|
||||
@@ -7,8 +7,53 @@ import toolNames from '../tools/toolNames';
|
||||
|
||||
const { MeasurementReport } = adaptersSR.Cornerstone3D;
|
||||
|
||||
export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
/**
|
||||
* Adds a DICOM SR (Structured Report) annotation to the annotation manager.
|
||||
* This function processes measurement data from DICOM SR and converts it into
|
||||
* a format suitable for display in the Cornerstone3D viewer.
|
||||
*
|
||||
* @param {Object} params - The parameters object
|
||||
* @param {Object} params.measurement - The DICOM SR measurement data containing coordinates, labels, and metadata
|
||||
* @param {Array} params.measurement.coords - Array of coordinate objects with GraphicType, ValueType, and other properties
|
||||
* @param {string} params.measurement.TrackingUniqueIdentifier - Unique identifier for the measurement
|
||||
* @param {string} params.measurement.TrackingIdentifier - Tracking identifier for adapter lookup
|
||||
* @param {Array} [params.measurement.labels] - Optional array of label objects
|
||||
* @param {string} [params.measurement.displayText] - Optional display text for the annotation
|
||||
* @param {Object} [params.measurement.textBox] - Optional text box configuration
|
||||
* @param {string|null} [params.imageId] - Optional image ID for the referenced image (defaults to null)
|
||||
* @param {number|null} [params.frameNumber] - Optional frame number for multi-frame images (defaults to null)
|
||||
* @param {Object} params.displaySet - The display set containing the image
|
||||
* @param {string} params.displaySet.displaySetInstanceUID - Unique identifier for the display set
|
||||
* @returns {void}
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* addSRAnnotation({
|
||||
* measurement: {
|
||||
* TrackingUniqueIdentifier: '1.2.3.4.5',
|
||||
* TrackingIdentifier: 'POINT',
|
||||
* coords: [{
|
||||
* GraphicType: 'POINT',
|
||||
* ValueType: 'SCOORD',
|
||||
* // ... other coordinate properties
|
||||
* }],
|
||||
* labels: [{ value: 'Measurement Point' }],
|
||||
* displayText: 'Point measurement'
|
||||
* },
|
||||
* imageId: 'wadouri:file://path/to/image.dcm', // Optional
|
||||
* frameNumber: 0, // Optional
|
||||
* displaySet: { displaySetInstanceUID: '1.2.3.4' }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export default function addSRAnnotation({ measurement, imageId = null, frameNumber = null, displaySet }) {
|
||||
/** @type {string} The tool name to use for the annotation, defaults to DICOMSRDisplay */
|
||||
let toolName = toolNames.DICOMSRDisplay;
|
||||
|
||||
/**
|
||||
* @type {Object} Renderable data organized by graphic type
|
||||
* Groups coordinate data by GraphicType for efficient rendering
|
||||
*/
|
||||
const renderableData = measurement.coords.reduce((acc, coordProps) => {
|
||||
acc[coordProps.GraphicType] = acc[coordProps.GraphicType] || [];
|
||||
acc[coordProps.GraphicType].push(getRenderableData({ ...coordProps, imageId }));
|
||||
@@ -52,10 +97,15 @@ export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
referencedImageId: imageId,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Types.Annotation} The annotation object to be added to the annotation manager
|
||||
* Contains all necessary metadata and data for rendering the DICOM SR measurement
|
||||
*/
|
||||
const SRAnnotation: Types.Annotation = {
|
||||
annotationUID: TrackingUniqueIdentifier,
|
||||
highlighted: false,
|
||||
isLocked: false,
|
||||
isPreview: toolName === toolNames.DICOMSRDisplay,
|
||||
invalidated: false,
|
||||
metadata: {
|
||||
toolName,
|
||||
@@ -64,6 +114,7 @@ export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
graphicType,
|
||||
FrameOfReferenceUID: frameOfReferenceUID,
|
||||
referencedImageId: imageId,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
},
|
||||
data: {
|
||||
label: measurement.labels?.[0]?.value || undefined,
|
||||
@@ -81,8 +132,11 @@ export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
};
|
||||
|
||||
/**
|
||||
* const annotationManager = annotation.annotationState.getAnnotationManager();
|
||||
* was not triggering annotation_added events.
|
||||
* Add the annotation to the annotation state manager.
|
||||
* Note: Using annotation.state.addAnnotation() instead of annotationManager.addAnnotation()
|
||||
* because the latter was not triggering annotation_added events properly.
|
||||
*
|
||||
* @param {Types.Annotation} SRAnnotation - The annotation to add
|
||||
*/
|
||||
annotation.state.addAnnotation(SRAnnotation);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ const DEFAULT_TYPES = [GroupAccordion, Content, Trigger];
|
||||
* measurements panel for a practical, working example.
|
||||
*/
|
||||
export function AccordionGroup(props) {
|
||||
const { grouping, items, children, sourceChildren, type } = props;
|
||||
const { grouping, items, children, sourceChildren } = props;
|
||||
const childProps = useSystem();
|
||||
let defaultValue = props.defaultValue;
|
||||
const groups = grouping.groupingFunction(items, grouping, childProps);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useActiveViewportDisplaySets, useSystem, utils } from '@ohif/core';
|
||||
// import { AccordionContent, AccordionItem, AccordionTrigger } from '@ohif/ui-next';
|
||||
import { useActiveViewportDisplaySets, utils } from '@ohif/core';
|
||||
|
||||
import { AccordionGroup } from './AccordionGroup';
|
||||
import MeasurementsOrAdditionalFindings from './MeasurementsOrAdditionalFindings';
|
||||
@@ -63,8 +62,7 @@ export const groupByStudy = (items, grouping, childProps) => {
|
||||
export function StudyMeasurements(props): React.ReactNode {
|
||||
const { items, grouping = {}, children } = props;
|
||||
|
||||
const system = useSystem();
|
||||
const activeDisplaySets = useActiveViewportDisplaySets(system);
|
||||
const activeDisplaySets = useActiveViewportDisplaySets();
|
||||
const activeStudyUID = activeDisplaySets?.[0]?.StudyInstanceUID;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
function mapMeasurementToDisplay(measurement, displaySetService) {
|
||||
const { referenceSeriesUID } = measurement;
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ const connectMeasurementServiceToTools = ({
|
||||
if (measurement?.metadata?.referencedImageId) {
|
||||
imageId = measurement.metadata.referencedImageId;
|
||||
frameNumber = getSOPInstanceAttributes(measurement.metadata.referencedImageId).frameNumber;
|
||||
} else {
|
||||
} else if (instance) {
|
||||
imageId = dataSource.getImageIdsForInstance({ instance });
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ export default function PanelMeasurement(props): React.ReactNode {
|
||||
);
|
||||
return cloned;
|
||||
}
|
||||
|
||||
// Need to merge defaults on the content props to ensure they get passed to children
|
||||
return <StudyMeasurements items={displayMeasurements} />;
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
import { useTrackedMeasurements } from '../getContextModule';
|
||||
import { UntrackSeriesModal } from './PanelStudyBrowserTracking/untrackSeriesModal';
|
||||
|
||||
const { filterMeasurementsBySeriesUID, filterAny } = utils.MeasurementFilters;
|
||||
const { filterMeasurementsBySeriesUID, filterAny } =
|
||||
utils.MeasurementFilters;
|
||||
|
||||
function PanelMeasurementTableTracking(props) {
|
||||
const [viewportGrid] = useViewportGrid();
|
||||
@@ -22,7 +23,9 @@ function PanelMeasurementTableTracking(props) {
|
||||
|
||||
const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements();
|
||||
const { trackedStudy, trackedSeries } = trackedMeasurements.context;
|
||||
const measurementFilter = trackedStudy ? filterMeasurementsBySeriesUID(trackedSeries) : filterAny;
|
||||
const measurementFilter = trackedStudy
|
||||
? filterMeasurementsBySeriesUID(trackedSeries)
|
||||
: filterAny;
|
||||
|
||||
const onUntrackConfirm = () => {
|
||||
sendTrackedMeasurementsEvent('UNTRACK_ALL', {});
|
||||
|
||||
Reference in new issue
Block a user