Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
sedghi committed Oct 1, 2024
1 parent 10e0d29 commit 7d27dba
Show file tree
Hide file tree
Showing 19 changed files with 409 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,17 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
newMeasurementSelected,
displaySetService
).then(({ referencedDisplaySet, referencedDisplaySetMetadata }) => {
if (!referencedDisplaySet || !referencedDisplaySetMetadata) {
return;
}

setMeasurementSelected(newMeasurementSelected);
setActiveImageDisplaySetData(referencedDisplaySet);
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);

if (
referencedDisplaySet.displaySetInstanceUID ===
activeImageDisplaySetData?.displaySetInstanceUID
activeImageDisplaySetData.displaySetInstanceUID
) {
const { measurements } = srDisplaySet;

Expand All @@ -151,6 +155,10 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
// new measurement
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);

if (!csViewport) {
return;
}

const imageIds = csViewport.getImageIds();

const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId);
Expand Down Expand Up @@ -395,6 +403,10 @@ async function _getViewportReferencedDisplaySetData(
displaySet.keyImageDisplaySet = createReferencedImageDisplaySet(displaySetService, displaySet);
}

if (!displaySetInstanceUID) {
return { referencedDisplaySetMetadata: null, referencedDisplaySet: null };
}

const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);

const image0 = referencedDisplaySet.images[0];
Expand Down
114 changes: 44 additions & 70 deletions extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { adaptersSR } from '@cornerstonejs/adapters';

import addSRAnnotation from './utils/addSRAnnotation';
import isRehydratable from './utils/isRehydratable';
import { SOPClassHandlerName, SOPClassHandlerId } from './id';
import {
SOPClassHandlerName,
SOPClassHandlerId,
SOPClassHandlerId3D,
SOPClassHandlerName3D,
} from './id';
import { CodeNameCodeSequenceValues, CodingSchemeDesignators } from './enums';

const { sopClassDictionary } = utils;
Expand All @@ -25,8 +30,6 @@ const sopClassUids = [
sopClassDictionary.BasicTextSR,
sopClassDictionary.EnhancedSR,
sopClassDictionary.ComprehensiveSR,
sopClassDictionary.Comprehensive3DSR,
// sopClassDictionary.MammographyCADSR,
];

const validateSameStudyUID = (uid: string, instances): void => {
Expand Down Expand Up @@ -92,6 +95,8 @@ function _getDisplaySetsFromSeries(
} = instance;
validateSameStudyUID(instance.StudyInstanceUID, instances);

const is3DSR = SOPClassUID === sopClassDictionary.Comprehensive3DSR;

const isImagingMeasurementReport =
ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurementReport;

Expand All @@ -104,7 +109,7 @@ function _getDisplaySetsFromSeries(
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SOPClassHandlerId,
SOPClassHandlerId: is3DSR ? SOPClassHandlerId3D : SOPClassHandlerId,
SOPClassUID,
instances,
referencedImages: null,
Expand All @@ -124,19 +129,19 @@ function _getDisplaySetsFromSeries(

/**
* Loads the display set with the given services and extension manager.
* @param displaySet - The display set to load.
* @param srDisplaySet - The display set to load.
* @param servicesManager - The services manager containing displaySetService and measurementService.
* @param extensionManager - The extension manager containing data sources.
*/
async function _load(
displaySet: Types.DisplaySet,
srDisplaySet: Types.DisplaySet,
servicesManager: AppTypes.ServicesManager,
extensionManager: AppTypes.ExtensionManager
) {
const { displaySetService, measurementService } = servicesManager.services;
const dataSources = extensionManager.getDataSources();
const dataSource = dataSources[0];
const { ContentSequence } = displaySet.instance;
const { ContentSequence } = srDisplaySet.instance;

async function retrieveBulkData(obj, parentObj = null, key = null) {
for (const prop in obj) {
Expand All @@ -147,9 +152,9 @@ async function _load(
} else if (prop === 'BulkDataURI') {
const value = await dataSource.retrieve.bulkDataURI({
BulkDataURI: obj[prop],
StudyInstanceUID: displaySet.instance.StudyInstanceUID,
SeriesInstanceUID: displaySet.instance.SeriesInstanceUID,
SOPInstanceUID: displaySet.instance.SOPInstanceUID,
StudyInstanceUID: srDisplaySet.instance.StudyInstanceUID,
SeriesInstanceUID: srDisplaySet.instance.SeriesInstanceUID,
SOPInstanceUID: srDisplaySet.instance.SOPInstanceUID,
});
if (parentObj && key) {
parentObj[key] = new Float32Array(value);
Expand All @@ -158,31 +163,31 @@ async function _load(
}
}

if (displaySet.isLoaded !== true) {
if (srDisplaySet.isLoaded !== true) {
await retrieveBulkData(ContentSequence);
}

if (displaySet.isImagingMeasurementReport) {
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
displaySet.measurements = _getMeasurements(ContentSequence);
if (srDisplaySet.isImagingMeasurementReport) {
srDisplaySet.referencedImages = _getReferencedImagesList(ContentSequence);
srDisplaySet.measurements = _getMeasurements(ContentSequence);
} else {
displaySet.referencedImages = [];
displaySet.measurements = [];
srDisplaySet.referencedImages = [];
srDisplaySet.measurements = [];
}

const mappings = measurementService.getSourceMappings(
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
);

displaySet.isHydrated = false;
displaySet.isRehydratable = isRehydratable(displaySet, mappings);
displaySet.isLoaded = true;
srDisplaySet.isHydrated = false;
srDisplaySet.isRehydratable = isRehydratable(srDisplaySet, mappings);
srDisplaySet.isLoaded = true;

/** Check currently added displaySets and add measurements if the sources exist */
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
_checkIfCanAddMeasurementsToDisplaySet(
displaySet,
srDisplaySet,
activeDisplaySet,
dataSource,
servicesManager
Expand All @@ -198,7 +203,7 @@ async function _load(
*/
displaySetsAdded.forEach(newDisplaySet => {
_checkIfCanAddMeasurementsToDisplaySet(
displaySet,
srDisplaySet,
newDisplaySet,
dataSource,
servicesManager
Expand All @@ -223,7 +228,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
) {
const { customizationService } = servicesManager.services;

let unloadedMeasurements = srDisplaySet.measurements.filter(
const unloadedMeasurements = srDisplaySet.measurements.filter(
measurement => measurement.loaded === false
);

Expand All @@ -235,9 +240,9 @@ function _checkIfCanAddMeasurementsToDisplaySet(
return;
}

const { sopClassUids } = newDisplaySet;
// const { sopClassUids } = newDisplaySet;
// Create a Set for faster lookups
const sopClassUidSet = new Set(sopClassUids);
// const sopClassUidSet = new Set(sopClassUids);

// Create a Map to efficiently look up ImageIds by SOPInstanceUID and frame number
const imageIdMap = new Map<string, string>();
Expand All @@ -249,54 +254,12 @@ function _checkIfCanAddMeasurementsToDisplaySet(
imageIdMap.set(key, imageId);
}

// Filter unloaded measurements based on SOPClassUID
unloadedMeasurements = unloadedMeasurements.filter(measurement =>
measurement.coords.some(coord => {
if (coord.ReferencedSOPSequence === undefined && coord.ReferencedFrameOfReferenceSequence) {
for (const [key, imageId] of imageIdMap) {
const imageMetadata = metadataProvider.get('instance', imageId);
if (imageMetadata.FrameOfReferenceUID !== coord.ReferencedFrameOfReferenceSequence) {
continue;
}

const sliceNormal = [0, 0, 0];
const orientation = imageMetadata.ImageOrientationPatient;
sliceNormal[0] = orientation[1] * orientation[5] - orientation[2] * orientation[4];
sliceNormal[1] = orientation[2] * orientation[3] - orientation[0] * orientation[5];
sliceNormal[2] = orientation[0] * orientation[4] - orientation[1] * orientation[3];

let distanceAlongNormal = 0;
for (let j = 0; j < 3; ++j) {
distanceAlongNormal += sliceNormal[j] * imageMetadata.ImagePositionPatient[j];
}

/** Assuming 2 mm tolerance */
if (Math.abs(distanceAlongNormal - coord.GraphicData[2]) > 2) {
continue;
}

const [SOPInstanceUID, frameNumber] = key.split(':');
coord.ReferencedSOPSequence = {
ReferencedSOPClassUID: imageMetadata.SOPClassUID,
ReferencedSOPInstanceUID: SOPInstanceUID,
ReferencedFrameNumber: frameNumber !== '1' ? parseInt(frameNumber, 10) : undefined,
};

break;
}
}

return (
coord.ReferencedSOPSequence &&
sopClassUidSet.has(coord.ReferencedSOPSequence.ReferencedSOPClassUID)
);
})
);

if (unloadedMeasurements.length === 0) {
if (!unloadedMeasurements?.length) {
return;
}

const is3DSR = srDisplaySet.SOPClassUID === sopClassDictionary.Comprehensive3DSR;

for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
let measurement = unloadedMeasurements[j];

Expand All @@ -312,6 +275,13 @@ function _checkIfCanAddMeasurementsToDisplaySet(
});
}

// if it is 3d SR we can just add the SR annotation
if (is3DSR) {
addSRAnnotation(measurement, null, null);
measurement.loaded = true;
continue;
}

const referencedSOPSequence = measurement.coords[0].ReferencedSOPSequence;
if (!referencedSOPSequence) {
continue;
Expand Down Expand Up @@ -392,6 +362,11 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) {
sopClassUids,
getDisplaySetsFromSeries,
},
{
name: SOPClassHandlerName3D,
sopClassUids: [sopClassDictionary.Comprehensive3DSR],
getDisplaySetsFromSeries,
},
];
}

Expand All @@ -417,7 +392,6 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {

const mergedContentSequencesByTrackingUniqueIdentifiers =
_getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);

const measurements = [];

Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
Expand Down
5 changes: 4 additions & 1 deletion extensions/cornerstone-dicom-sr/src/id.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ const id = packageJson.name;
const SOPClassHandlerName = 'dicom-sr';
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;

export { SOPClassHandlerName, SOPClassHandlerId, id };
const SOPClassHandlerName3D = 'dicom-sr-3d';
const SOPClassHandlerId3D = `${id}.sopClassHandlerModule.${SOPClassHandlerName3D}`;

export { SOPClassHandlerName, SOPClassHandlerId, SOPClassHandlerName3D, SOPClassHandlerId3D, id };
26 changes: 23 additions & 3 deletions extensions/cornerstone-dicom-sr/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ import {
LengthTool,
PlanarFreehandROITool,
RectangleROITool,
ProbeTool,
} from '@cornerstonejs/tools';
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';
import addToolInstance from './utils/addToolInstance';
import { Types } from '@ohif/core';
import toolNames from './tools/toolNames';

const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
/**
* @param {object} configuration
*/
export default function init({
configuration = {},
servicesManager,
}: Types.Extensions.ExtensionParams): void {
const { measurementService } = servicesManager.services;

addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
addToolInstance(toolNames.SRLength, LengthTool);
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
Expand All @@ -32,11 +37,26 @@ export default function init({
addToolInstance(toolNames.SRAngle, AngleTool);
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
addToolInstance(toolNames.SCOORD3DPoint, ProbeTool, { useViewReference: true });
addToolInstance(toolNames.SRSCOORD3DPoint, SCOORD3DPointTool);

// TODO - fix the SR display of Cobb Angle, as it joins the two lines
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);

const csTools3DVer1MeasurementSource = measurementService.getSource(
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
);

const { POINT } = measurementService.VALUE_TYPES;

measurementService.addMapping(
csTools3DVer1MeasurementSource,
'SRSCOORD3DPoint',
POINT,
SRSCOOR3DProbeMapper.toAnnotation,
SRSCOOR3DProbeMapper.toMeasurement
);

// Modify annotation tools to use dashed lines on SR
const dashedLine = {
lineDash: '4,4',
Expand Down
4 changes: 2 additions & 2 deletions extensions/cornerstone-dicom-sr/src/onModeEnter.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { SOPClassHandlerId } from './id';
import { SOPClassHandlerId, SOPClassHandlerId3D } from './id';

export default function onModeEnter({ servicesManager }) {
const { displaySetService } = servicesManager.services;
const displaySetCache = displaySetService.getDisplaySetCache();

const srDisplaySets = [...displaySetCache.values()].filter(
ds => ds.SOPClassHandlerId === SOPClassHandlerId
ds => ds.SOPClassHandlerId === SOPClassHandlerId || ds.SOPClassHandlerId === SOPClassHandlerId3D
);

srDisplaySets.forEach(ds => {
Expand Down
Loading

0 comments on commit 7d27dba

Please sign in to comment.