Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: Save DICOM SR to the same series #3273

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions extensions/cornerstone-dicom-sr/src/commandsModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const _generateReport = (
if (typeof dataset.SpecificCharacterSet === 'undefined') {
dataset.SpecificCharacterSet = 'ISO_IR 192';
}
dataset.InstanceNumber = 1 + (options.InstanceNumber || 0);

return dataset;
};

Expand Down
14 changes: 7 additions & 7 deletions extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ function _getDisplaySetsFromSeries(
if (
!ConceptNameCodeSequence ||
ConceptNameCodeSequence.CodeValue !==
CodeNameCodeSequenceValues.ImagingMeasurementReport
CodeNameCodeSequenceValues.ImagingMeasurementReport
) {
servicesManager.services.uiNotificationService.show({
title: 'DICOM SR',
Expand Down Expand Up @@ -364,7 +364,7 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
trackingUniqueIdentifier => {
const mergedContentSequence =
mergedContentSequencesByTrackingUniqueIdentifiers[
trackingUniqueIdentifier
trackingUniqueIdentifier
];

const measurement = _processMeasurement(mergedContentSequence);
Expand Down Expand Up @@ -404,7 +404,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(

if (
mergedContentSequencesByTrackingUniqueIdentifiers[
trackingUniqueIdentifier
trackingUniqueIdentifier
] === undefined
) {
// Add the full ContentSequence
Expand Down Expand Up @@ -519,9 +519,9 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
const findingSites = mergedContentSequence.filter(
item =>
item.ConceptNameCodeSequence.CodingSchemeDesignator ===
CodingSchemeDesignators.SRT &&
CodingSchemeDesignators.SRT &&
item.ConceptNameCodeSequence.CodeValue ===
CodeNameCodeSequenceValues.FindingSite
CodeNameCodeSequenceValues.FindingSite
);

const measurement = {
Expand All @@ -538,7 +538,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
finding.ConceptCodeSequence.CodingSchemeDesignator
) &&
finding.ConceptCodeSequence.CodeValue ===
CodeNameCodeSequenceValues.CornerstoneFreeText
CodeNameCodeSequenceValues.CornerstoneFreeText
) {
measurement.labels.push({
label: CORNERSTONE_FREETEXT_CODE_VALUE,
Expand All @@ -554,7 +554,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
) &&
FindingSite.ConceptCodeSequence.CodeValue ===
CodeNameCodeSequenceValues.CornerstoneFreeText
CodeNameCodeSequenceValues.CornerstoneFreeText
);

if (cornerstoneFreeTextFindingSite) {
Expand Down
3 changes: 2 additions & 1 deletion extensions/cornerstone-dicom-sr/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { id } from './id.js';
import toolNames from './tools/toolNames';
import hydrateStructuredReport from './utils/hydrateStructuredReport';
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
import convertCode from './utils/convertCode';

const Component = React.lazy(() => {
return import(
Expand Down Expand Up @@ -74,4 +75,4 @@ const dicomSRExtension = {
export default dicomSRExtension;

// Put static exports here so they can be type checked
export { hydrateStructuredReport, createReferencedImageDisplaySet, srProtocol };
export { hydrateStructuredReport, createReferencedImageDisplaySet, srProtocol, convertCode };
24 changes: 24 additions & 0 deletions extensions/cornerstone-dicom-sr/src/utils/convertCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Converts a DICOM code value into an object with more information such
* as whether the code is a key value, any colouring etc.
*
* @param codingValues - map from string codes to code information
* @param code - the code we want more inofrmation about
* @returns An enhanced code value that includes the DICOM coding information
* as well as other information.
*/
const convertCode = (codingValues, code) => {
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return;
const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`;
const codeLookup = codingValues[ref];
const ret = {
ref,
...code,
...codeLookup,
CodeMeaning: codeLookup?.text || code.codeMeaning,
text: codeLookup?.text || code.CodeMeaning,
};
return ret;
};

export default convertCode;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { utilities, metaData } from '@cornerstonejs/core';
import OHIF, { DicomMetadataStore } from '@ohif/core';
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
import { adaptersSR } from '@cornerstonejs/adapters';
import convertCode from './convertCode';

const { guid } = OHIF.utils;
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
Expand All @@ -11,13 +12,10 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';

const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];

const convertCode = (codingValues, code) => {
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return;
const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`;
const ret = { ...codingValues[ref], ref, ...code, text: code.CodeMeaning };
return ret;
};

/**
* Takes a list of codes and runs them through convert, only adding the ones
* which are not CORNERSTONEJS issued to the result.
*/
const convertSites = (codingValues, sites) => {
if (!sites || !sites.length) return;
const ret = [];
Expand All @@ -32,7 +30,6 @@ const convertSites = (codingValues, sites) => {

/**
* Hydrates a structured report, for default viewports.
*
*/
export default function hydrateStructuredReport(
{ servicesManager, extensionManager },
Expand Down Expand Up @@ -287,7 +284,7 @@ function _mapLegacyDataSet(dataset) {
return dataset;
}

const toArray = function (x) {
const toArray = function(x) {
return Array.isArray(x) ? x : [x];
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import PropTypes from 'prop-types';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import OHIF, { utils, ServicesManager, ExtensionManager } from '@ohif/core';
import OHIF, {
utils,
ServicesManager,
ExtensionManager,
classes,
} from '@ohif/core';

import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule';

import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui';
import hydrateStructuredReport from '../utils/hydrateStructuredReport';

const { formatDate } = utils;
const { ImageSet } = classes;

const MEASUREMENT_TRACKING_EXTENSION_ID =
'@ohif/extension-measurement-tracking';
Expand Down Expand Up @@ -74,22 +80,26 @@ function OHIFCornerstoneSRViewport(props) {
// if no panels from measurement-tracking extension is used, this code will run
trackedMeasurements = null;
sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => {
measurementService.clearMeasurements();
const { StudyInstanceUID } = srDisplaySet;
measurementService.clearStudyMeasurements(StudyInstanceUID);
const { SeriesInstanceUIDs } = hydrateStructuredReport(
{ servicesManager, extensionManager },
displaySetInstanceUID
);
const displaySets = displaySetService.getDisplaySetsForSeries(
SeriesInstanceUIDs[0]
);
const displaySets =
(srDisplaySet.keyImageDisplaySet && [
srDisplaySet.keyImageDisplaySet,
]) ||
displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]);
if (displaySets.length) {
viewportGridService.setDisplaySetsForViewports([
{
viewportIndex: activeViewportIndex,
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
viewportIndex: activeViewportIndex,
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
},
]);
}
measurementService.setSeriesInformation(StudyInstanceUID, srDisplaySet);
};
}

Expand Down Expand Up @@ -424,19 +434,76 @@ OHIFCornerstoneSRViewport.defaultProps = {
customProps: {},
};

const findInstance = (measurement, displaySetService: DisplaySetService) => {
const {
displaySetInstanceUID,
ReferencedSOPInstanceUID: sopUid,
} = measurement;
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
if (!referencedDisplaySet.images) return;
return referencedDisplaySet.images.find(it => it.SOPInstanceUID === sopUid);
};

async function _getViewportReferencedDisplaySetData(
displaySet,
measurementSelected,
displaySetService
) {
const { measurements } = displaySet;
const measurement = measurements[measurementSelected];

const { displaySetInstanceUID } = measurement;
if (!displaySet.keyImageDisplaySet) {
const findReferences = ds => {
const instances = [];
const instanceByUrl = {};
for (const measurement of ds.measurements) {
const instance = findInstance(measurement, displaySetService);
if (!instance) {
console.log('Measurement', measurement, 'had no instances found');
continue;
}

const referencedDisplaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
// TODO - find actual URL
const { imageId } = measurement;
if (!imageId) continue;
if (instanceByUrl[imageId]) continue;
instanceByUrl[imageId] = instance;
instances.push(instance);
}
return instances;
};
const instances = findReferences(displaySet);
const updateInstances = function() {
this.images.splice(0, this.images.length, ...findReferences(displaySet));
this.numImageFrames = this.images.length;
};
const imageSet = new ImageSet(instances);
const instance = instances[0];
imageSet.setAttributes({
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
Fixed Show fixed Hide fixed
SeriesDate: instance.SeriesDate,
SeriesTime: instance.SeriesTime,
SeriesInstanceUID: imageSet.uid,
Fixed Show fixed Hide fixed
StudyInstanceUID: instance.StudyInstanceUID,
SeriesNumber: instance.SeriesNumber || 0,
SOPClassUID: instance.SOPClassUID,
SeriesDescription: `${displaySet.SeriesDescription} KO ${displaySet.instance.SeriesNumber}`,
Modality: 'KO',
isMultiFrame: false,
numImageFrames: instances.length,
SOPClassHandlerId: `@ohif/extension-default.sopClassHandlerModule.stack`,
isReconstructable: false,
madeInClient: true,
updateInstances,
});

displaySetService.addDisplaySets(imageSet);

displaySet.keyImageDisplaySet = imageSet;
}

const referencedDisplaySet = displaySet.keyImageDisplaySet;

const image0 = referencedDisplaySet.images[0];
const referencedDisplaySetMetadata = {
Expand Down
Loading