feat(SR): SCOORD3D point annotations support for stack viewports (#4315)
This commit is contained in:
parent
e0fca8c88c
commit
ac1cad25af
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.77.6",
|
||||
"@cornerstonejs/core": "^1.77.6",
|
||||
"@cornerstonejs/adapters": "^1.86.0",
|
||||
"@cornerstonejs/core": "^1.86.0",
|
||||
"@kitware/vtk.js": "30.4.1",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.81.6",
|
||||
"@cornerstonejs/core": "^1.81.6",
|
||||
"@cornerstonejs/adapters": "^1.86.0",
|
||||
"@cornerstonejs/core": "^1.86.0",
|
||||
"@kitware/vtk.js": "30.4.1",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.82.4",
|
||||
"@cornerstonejs/core": "^1.82.4",
|
||||
"@cornerstonejs/tools": "^1.82.4",
|
||||
"@cornerstonejs/adapters": "^1.86.0",
|
||||
"@cornerstonejs/core": "^1.86.0",
|
||||
"@cornerstonejs/tools": "^1.86.0",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import CodeNameCodeSequenceValues from '../constants/CodeNameCodeSequenceValues';
|
||||
import { CodeNameCodeSequenceValues } from '../enums';
|
||||
import formatContentItemValue from '../utils/formatContentItem';
|
||||
|
||||
const EMPTY_TAG_VALUE = '[empty]';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
@ -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];
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
const CodeNameCodeSequenceValues = {
|
||||
ImagingMeasurementReport: '126000',
|
||||
ImageLibrary: '111028',
|
||||
ImagingMeasurements: '126010',
|
||||
MeasurementGroup: '125007',
|
||||
ImageLibraryGroup: '126200',
|
||||
TrackingUniqueIdentifier: '112040',
|
||||
TrackingIdentifier: '112039',
|
||||
Finding: '121071',
|
||||
FindingSite: 'G-C0E3', // SRT
|
||||
CornerstoneFreeText: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
||||
};
|
||||
|
||||
export { CodeNameCodeSequenceValues as default };
|
||||
@ -1,7 +0,0 @@
|
||||
export default {
|
||||
POINT: 'POINT',
|
||||
MULTIPOINT: 'MULTIPOINT',
|
||||
POLYLINE: 'POLYLINE',
|
||||
CIRCLE: 'CIRCLE',
|
||||
ELLIPSE: 'ELLIPSE',
|
||||
};
|
||||
44
extensions/cornerstone-dicom-sr/src/enums.ts
Normal file
44
extensions/cornerstone-dicom-sr/src/enums.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
export const SCOORDTypes = {
|
||||
POINT: 'POINT',
|
||||
MULTIPOINT: 'MULTIPOINT',
|
||||
POLYLINE: 'POLYLINE',
|
||||
CIRCLE: 'CIRCLE',
|
||||
ELLIPSE: 'ELLIPSE',
|
||||
};
|
||||
|
||||
export const CodeNameCodeSequenceValues = {
|
||||
ImagingMeasurementReport: '126000',
|
||||
ImageLibrary: '111028',
|
||||
ImagingMeasurements: '126010',
|
||||
MeasurementGroup: '125007',
|
||||
ImageLibraryGroup: '126200',
|
||||
TrackingUniqueIdentifier: '112040',
|
||||
TrackingIdentifier: '112039',
|
||||
Finding: '121071',
|
||||
FindingSite: 'G-C0E3', // SRT
|
||||
FindingSiteSCT: '363698007', // SCT
|
||||
};
|
||||
|
||||
export const CodingSchemeDesignators = {
|
||||
SRT: 'SRT',
|
||||
SCT: 'SCT',
|
||||
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'],
|
||||
};
|
||||
|
||||
export const RelationshipType = {
|
||||
INFERRED_FROM: 'INFERRED FROM',
|
||||
CONTAINS: 'CONTAINS',
|
||||
};
|
||||
|
||||
const enums = {
|
||||
CodeNameCodeSequenceValues,
|
||||
CodingSchemeDesignators,
|
||||
RelationshipType,
|
||||
SCOORDTypes,
|
||||
};
|
||||
|
||||
export default enums;
|
||||
@ -1,32 +1,37 @@
|
||||
import { SOPClassHandlerName, SOPClassHandlerId } from './id';
|
||||
import { utils, classes, DisplaySetService, Types } from '@ohif/core';
|
||||
import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation';
|
||||
import isRehydratable from './utils/isRehydratable';
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
import CodeNameCodeSequenceValues from './constants/CodeNameCodeSequenceValues';
|
||||
|
||||
import addSRAnnotation from './utils/addSRAnnotation';
|
||||
import isRehydratable from './utils/isRehydratable';
|
||||
import {
|
||||
SOPClassHandlerName,
|
||||
SOPClassHandlerId,
|
||||
SOPClassHandlerId3D,
|
||||
SOPClassHandlerName3D,
|
||||
} from './id';
|
||||
import { CodeNameCodeSequenceValues, CodingSchemeDesignators } from './enums';
|
||||
|
||||
const { sopClassDictionary } = utils;
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
const { ImageSet, MetadataProvider: metadataProvider } = classes;
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
type InstanceMetadata = Types.InstanceMetadata;
|
||||
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
const { ImageSet, MetadataProvider: metadataProvider } = classes;
|
||||
|
||||
// TODO ->
|
||||
// Add SR thumbnail
|
||||
// Make viewport
|
||||
// Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport.
|
||||
/**
|
||||
* TODO
|
||||
* - [ ] Add SR thumbnail
|
||||
* - [ ] Make viewport
|
||||
* - [ ] Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport
|
||||
*/
|
||||
|
||||
const sopClassUids = [
|
||||
'1.2.840.10008.5.1.4.1.1.88.11', // BASIC TEXT SR
|
||||
'1.2.840.10008.5.1.4.1.1.88.22', // ENHANCED SR
|
||||
'1.2.840.10008.5.1.4.1.1.88.33', // COMPREHENSIVE SR
|
||||
'1.2.840.10008.5.1.4.1.1.88.34', // Comprehensive 3D SR
|
||||
// '1.2.840.10008.5.1.4.1.1.88.50', // Mammography CAD SR
|
||||
sopClassDictionary.BasicTextSR,
|
||||
sopClassDictionary.EnhancedSR,
|
||||
sopClassDictionary.ComprehensiveSR,
|
||||
];
|
||||
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const validateSameStudyUID = (uid: string, instances): void => {
|
||||
instances.forEach(it => {
|
||||
if (it.StudyInstanceUID !== uid) {
|
||||
@ -36,18 +41,6 @@ const validateSameStudyUID = (uid: string, instances): void => {
|
||||
});
|
||||
};
|
||||
|
||||
const CodingSchemeDesignators = {
|
||||
SRT: 'SRT',
|
||||
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'],
|
||||
};
|
||||
|
||||
const RELATIONSHIP_TYPE = {
|
||||
INFERRED_FROM: 'INFERRED FROM',
|
||||
CONTAINS: 'CONTAINS',
|
||||
};
|
||||
|
||||
const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
|
||||
|
||||
/**
|
||||
* Adds instances to the DICOM SR series, rather than creating a new
|
||||
* series, so that as SR's are saved, they append to the series, and the
|
||||
@ -102,6 +95,8 @@ function _getDisplaySetsFromSeries(
|
||||
} = instance;
|
||||
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
||||
|
||||
const is3DSR = SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
||||
|
||||
const isImagingMeasurementReport =
|
||||
ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurementReport;
|
||||
|
||||
@ -114,7 +109,7 @@ function _getDisplaySetsFromSeries(
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId,
|
||||
SOPClassHandlerId: is3DSR ? SOPClassHandlerId3D : SOPClassHandlerId,
|
||||
SOPClassUID,
|
||||
instances,
|
||||
referencedImages: null,
|
||||
@ -132,11 +127,21 @@ function _getDisplaySetsFromSeries(
|
||||
return [displaySet];
|
||||
}
|
||||
|
||||
async function _load(displaySet, servicesManager: AppTypes.ServicesManager, extensionManager) {
|
||||
/**
|
||||
* Loads the display set with the given services and extension manager.
|
||||
* @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(
|
||||
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) {
|
||||
@ -147,9 +152,9 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
||||
} 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);
|
||||
@ -158,16 +163,16 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
@ -175,28 +180,30 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
||||
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.
|
||||
/** Check currently added displaySets and add measurements if the sources exist */
|
||||
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
srDisplaySet,
|
||||
activeDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
);
|
||||
});
|
||||
|
||||
// Subscribe to new displaySets as the source may come in after.
|
||||
/** Subscribe to new displaySets as the source may come in after */
|
||||
displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => {
|
||||
const { displaySetsAdded } = data;
|
||||
// If there are still some measurements that have not yet been loaded into cornerstone,
|
||||
// See if we can load them onto any of the new displaySets.
|
||||
/**
|
||||
* If there are still some measurements that have not yet been loaded into cornerstone,
|
||||
* See if we can load them onto any of the new displaySets.
|
||||
*/
|
||||
displaySetsAdded.forEach(newDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
@ -205,6 +212,14 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@ -212,108 +227,103 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
servicesManager: AppTypes.ServicesManager
|
||||
) {
|
||||
const { customizationService } = servicesManager.services;
|
||||
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||
|
||||
const unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||
measurement => measurement.loaded === false
|
||||
);
|
||||
|
||||
if (unloadedMeasurements.length === 0) {
|
||||
// All already loaded!
|
||||
if (
|
||||
unloadedMeasurements.length === 0 ||
|
||||
!(newDisplaySet instanceof ImageSet) ||
|
||||
newDisplaySet.unsupported
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!newDisplaySet) instanceof ImageSet) {
|
||||
// This also filters out _this_ displaySet, as it is not an ImageSet.
|
||||
// const { sopClassUids } = newDisplaySet;
|
||||
// Create a Set for faster lookups
|
||||
// const sopClassUidSet = new Set(sopClassUids);
|
||||
|
||||
// Create a Map to efficiently look up ImageIds by SOPInstanceUID and frame number
|
||||
const imageIdMap = new Map<string, string>();
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
||||
|
||||
for (const imageId of imageIds) {
|
||||
const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId);
|
||||
const key = `${SOPInstanceUID}:${frameNumber || 1}`;
|
||||
imageIdMap.set(key, imageId);
|
||||
}
|
||||
|
||||
if (!unloadedMeasurements?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newDisplaySet.unsupported) {
|
||||
return;
|
||||
}
|
||||
const is3DSR = srDisplaySet.SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
||||
|
||||
const { sopClassUids } = newDisplaySet;
|
||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||
let measurement = unloadedMeasurements[j];
|
||||
|
||||
// Check if any have the newDisplaySet is the correct SOPClass.
|
||||
unloadedMeasurements = unloadedMeasurements.filter(measurement =>
|
||||
measurement.coords.some(coord =>
|
||||
sopClassUids.includes(coord.ReferencedSOPSequence.ReferencedSOPClassUID)
|
||||
)
|
||||
);
|
||||
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
||||
'onBeforeSRAddMeasurement'
|
||||
)?.value;
|
||||
|
||||
if (unloadedMeasurements.length === 0) {
|
||||
// New displaySet isn't the correct SOPClass, so can't contain the referenced images.
|
||||
return;
|
||||
}
|
||||
|
||||
const SOPInstanceUIDs = [];
|
||||
|
||||
unloadedMeasurements.forEach(measurement => {
|
||||
const { coords } = measurement;
|
||||
|
||||
coords.forEach(coord => {
|
||||
const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||
|
||||
if (!SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
SOPInstanceUIDs.push(SOPInstanceUID);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
||||
|
||||
for (const imageId of imageIdsForDisplaySet) {
|
||||
if (!unloadedMeasurements.length) {
|
||||
// All measurements loaded.
|
||||
return;
|
||||
if (typeof onBeforeSRAddMeasurement === 'function') {
|
||||
measurement = onBeforeSRAddMeasurement({
|
||||
measurement,
|
||||
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
||||
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
||||
});
|
||||
}
|
||||
|
||||
const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId);
|
||||
// if it is 3d SR we can just add the SR annotation
|
||||
if (is3DSR) {
|
||||
addSRAnnotation(measurement, null, null);
|
||||
measurement.loaded = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||
let measurement = unloadedMeasurements[j];
|
||||
const referencedSOPSequence = measurement.coords[0].ReferencedSOPSequence;
|
||||
if (!referencedSOPSequence) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
||||
'onBeforeSRAddMeasurement'
|
||||
)?.value;
|
||||
const { ReferencedSOPInstanceUID } = referencedSOPSequence;
|
||||
const frame = referencedSOPSequence.ReferencedFrameNumber || 1;
|
||||
const key = `${ReferencedSOPInstanceUID}:${frame}`;
|
||||
const imageId = imageIdMap.get(key);
|
||||
|
||||
if (typeof onBeforeSRAddMeasurement === 'function') {
|
||||
measurement = onBeforeSRAddMeasurement({
|
||||
measurement,
|
||||
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
||||
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
||||
});
|
||||
}
|
||||
if (
|
||||
imageId &&
|
||||
_measurementReferencesSOPInstanceUID(measurement, ReferencedSOPInstanceUID, frame)
|
||||
) {
|
||||
addSRAnnotation(measurement, imageId, frame);
|
||||
|
||||
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
||||
const frame =
|
||||
(measurement.coords[0].ReferencedSOPSequence &&
|
||||
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
||||
1;
|
||||
// Update measurement properties
|
||||
measurement.loaded = true;
|
||||
measurement.imageId = imageId;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
measurement.ReferencedSOPInstanceUID = ReferencedSOPInstanceUID;
|
||||
measurement.frameNumber = frame;
|
||||
|
||||
/** Add DICOMSRDisplay annotation for the SR viewport (only) */
|
||||
addDICOMSRDisplayAnnotation(measurement, imageId, frame);
|
||||
|
||||
/** Update measurement properties */
|
||||
measurement.loaded = true;
|
||||
measurement.imageId = imageId;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
measurement.ReferencedSOPInstanceUID =
|
||||
measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||
measurement.frameNumber = frame;
|
||||
delete measurement.coords;
|
||||
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
}
|
||||
}
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
||||
const { coords } = measurement;
|
||||
|
||||
// NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
||||
// Standard. But for now, we will support only one ReferenceFrameNumber.
|
||||
/**
|
||||
* NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
||||
* Standard. But for now, we will support only one ReferenceFrameNumber.
|
||||
*/
|
||||
const ReferencedFrameNumber =
|
||||
(measurement.coords[0].ReferencedSOPSequence &&
|
||||
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
||||
@ -326,27 +336,46 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
||||
for (let j = 0; j < coords.length; j++) {
|
||||
const coord = coords[j];
|
||||
const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence;
|
||||
|
||||
if (ReferencedSOPInstanceUID === SOPInstanceUID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: SOPClassHandlerName,
|
||||
sopClassUids,
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
{
|
||||
name: SOPClassHandlerName3D,
|
||||
sopClassUids: [sopClassDictionary.Comprehensive3DSR],
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the measurements from the ImagingMeasurementReportContentSequence.
|
||||
*
|
||||
* @param {Array} ImagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
|
||||
* @returns {Array} - The array of measurements.
|
||||
*/
|
||||
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||
item =>
|
||||
@ -363,7 +392,6 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
|
||||
const mergedContentSequencesByTrackingUniqueIdentifiers =
|
||||
_getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);
|
||||
|
||||
const measurements = [];
|
||||
|
||||
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
||||
@ -372,7 +400,6 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier];
|
||||
|
||||
const measurement = _processMeasurement(mergedContentSequence);
|
||||
|
||||
if (measurement) {
|
||||
measurements.push(measurement);
|
||||
}
|
||||
@ -382,6 +409,12 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
return measurements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves merged content sequences by tracking unique identifiers.
|
||||
*
|
||||
* @param {Array} MeasurementGroups - The measurement groups.
|
||||
* @returns {Object} - The merged content sequences by tracking unique identifiers.
|
||||
*/
|
||||
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
||||
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
||||
|
||||
@ -393,7 +426,6 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
||||
item.ConceptNameCodeSequence.CodeValue ===
|
||||
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
||||
);
|
||||
|
||||
if (!TrackingUniqueIdentifierItem) {
|
||||
console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.');
|
||||
}
|
||||
@ -422,6 +454,15 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
||||
return mergedContentSequencesByTrackingUniqueIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the measurement based on the merged content sequence.
|
||||
* If the merged content sequence contains SCOORD or SCOORD3D value types,
|
||||
* 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.
|
||||
*/
|
||||
function _processMeasurement(mergedContentSequence) {
|
||||
if (
|
||||
mergedContentSequence.some(
|
||||
@ -434,11 +475,21 @@ function _processMeasurement(mergedContentSequence) {
|
||||
return _processNonGeometricallyDefinedMeasurement(mergedContentSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes TID 1410 style measurements from the 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.
|
||||
*/
|
||||
function _processTID1410Measurement(mergedContentSequence) {
|
||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
||||
|
||||
const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD');
|
||||
const graphicItem = mergedContentSequence.find(
|
||||
group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D'
|
||||
);
|
||||
|
||||
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||
|
||||
@ -465,7 +516,6 @@ function _processTID1410Measurement(mergedContentSequence) {
|
||||
|
||||
NUMContentItems.forEach(item => {
|
||||
const { ConceptNameCodeSequence, MeasuredValueSequence } = item;
|
||||
|
||||
if (MeasuredValueSequence) {
|
||||
measurement.labels.push(
|
||||
_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence)
|
||||
@ -473,12 +523,29 @@ function _processTID1410Measurement(mergedContentSequence) {
|
||||
}
|
||||
});
|
||||
|
||||
const findingSites = mergedContentSequence.filter(
|
||||
item =>
|
||||
item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SCT &&
|
||||
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSiteSCT
|
||||
);
|
||||
if (findingSites.length) {
|
||||
measurement.labels.push({
|
||||
label: CodeNameCodeSequenceValues.FindingSiteSCT,
|
||||
value: findingSites[0].ConceptCodeSequence.CodeMeaning,
|
||||
});
|
||||
}
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
||||
|
||||
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||
|
||||
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
||||
@ -508,10 +575,10 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||
finding.ConceptCodeSequence.CodingSchemeDesignator
|
||||
) &&
|
||||
finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||
finding.ConceptCodeSequence.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||
) {
|
||||
measurement.labels.push({
|
||||
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
||||
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
||||
value: finding.ConceptCodeSequence.CodeMeaning,
|
||||
});
|
||||
}
|
||||
@ -523,12 +590,13 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
|
||||
) &&
|
||||
FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||
FindingSite.ConceptCodeSequence.CodeValue ===
|
||||
Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||
);
|
||||
|
||||
if (cornerstoneFreeTextFindingSite) {
|
||||
measurement.labels.push({
|
||||
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
||||
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
||||
value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning,
|
||||
});
|
||||
}
|
||||
@ -538,15 +606,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item;
|
||||
|
||||
const { ValueType } = ContentSequence;
|
||||
|
||||
if (!ValueType === 'SCOORD') {
|
||||
console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
|
||||
|
||||
if (coords) {
|
||||
measurement.coords.push(coords);
|
||||
}
|
||||
@ -561,51 +626,47 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
return measurement;
|
||||
}
|
||||
|
||||
function _getCoordsFromSCOORDOrSCOORD3D(item) {
|
||||
const { ValueType, RelationshipType, GraphicType, GraphicData } = item;
|
||||
|
||||
if (
|
||||
!(
|
||||
RelationshipType == RELATIONSHIP_TYPE.INFERRED_FROM ||
|
||||
RelationshipType == RELATIONSHIP_TYPE.CONTAINS
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
`Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM" or "CONTAINS"`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
||||
const { ValueType, GraphicType, GraphicData } = graphicItem;
|
||||
const coords = { ValueType, GraphicType, GraphicData };
|
||||
|
||||
// ContentSequence has length of 1 as RelationshipType === 'INFERRED FROM'
|
||||
if (ValueType === 'SCOORD') {
|
||||
const { ReferencedSOPSequence } = item.ContentSequence;
|
||||
|
||||
coords.ReferencedSOPSequence = ReferencedSOPSequence;
|
||||
} else if (ValueType === 'SCOORD3D') {
|
||||
const { ReferencedFrameOfReferenceSequence } = item.ContentSequence;
|
||||
|
||||
coords.ReferencedFrameOfReferenceSequence = ReferencedFrameOfReferenceSequence;
|
||||
}
|
||||
|
||||
coords.ReferencedSOPSequence = graphicItem.ContentSequence?.ReferencedSOPSequence;
|
||||
coords.ReferencedFrameOfReferenceSequence =
|
||||
graphicItem.ReferencedFrameOfReferenceUID ||
|
||||
graphicItem.ContentSequence?.ReferencedFrameOfReferenceSequence;
|
||||
return coords;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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' }
|
||||
*/
|
||||
function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) {
|
||||
const { CodeMeaning } = ConceptNameCodeSequence;
|
||||
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
||||
const { CodeValue } = MeasurementUnitsCodeSequence;
|
||||
|
||||
const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : '';
|
||||
|
||||
return {
|
||||
label: CodeMeaning,
|
||||
value: `${formatedNumericValue} ${CodeValue}`,
|
||||
}; // E.g. Long Axis: 31.0 mm
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary
|
||||
@ -618,6 +679,9 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(
|
||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup
|
||||
);
|
||||
if (!ImageLibraryGroup) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const referencedImages = [];
|
||||
|
||||
@ -641,6 +705,15 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
return referencedImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a DICOM sequence to an array.
|
||||
* If the sequence is null or undefined, an empty array is returned.
|
||||
* If the sequence is already an array, it is returned as is.
|
||||
* Otherwise, the sequence is wrapped in an array and returned.
|
||||
*
|
||||
* @param {any} sequence - The DICOM sequence to convert.
|
||||
* @returns {any[]} - The converted array.
|
||||
*/
|
||||
function _getSequenceAsArray(sequence) {
|
||||
if (!sequence) {
|
||||
return [];
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -8,6 +8,7 @@ import { id } from './id.js';
|
||||
import toolNames from './tools/toolNames';
|
||||
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
||||
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
||||
import Enums from './enums';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport');
|
||||
@ -70,4 +71,4 @@ const dicomSRExtension = {
|
||||
export default dicomSRExtension;
|
||||
|
||||
// Put static exports here so they can be type checked
|
||||
export { hydrateStructuredReport, createReferencedImageDisplaySet, srProtocol };
|
||||
export { hydrateStructuredReport, createReferencedImageDisplaySet, srProtocol, Enums, toolNames };
|
||||
|
||||
@ -9,16 +9,28 @@ import {
|
||||
LengthTool,
|
||||
PlanarFreehandROITool,
|
||||
RectangleROITool,
|
||||
utilities as csToolsUtils,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { Types, MeasurementService } from '@ohif/core';
|
||||
import { StackViewport } from '@cornerstonejs/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 = {} }: Types.Extensions.ExtensionParams): void {
|
||||
export default function init({
|
||||
configuration = {},
|
||||
servicesManager,
|
||||
}: Types.Extensions.ExtensionParams): void {
|
||||
const { measurementService, cornerstoneViewportService } = servicesManager.services;
|
||||
|
||||
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
|
||||
addToolInstance(toolNames.SRLength, LengthTool);
|
||||
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
||||
@ -28,10 +40,26 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP
|
||||
addToolInstance(toolNames.SRAngle, AngleTool);
|
||||
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
|
||||
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
|
||||
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',
|
||||
@ -49,4 +77,28 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP
|
||||
SRRectangleROI: dashedLine,
|
||||
global: {},
|
||||
});
|
||||
|
||||
measurementService.subscribe(
|
||||
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
|
||||
({ viewportId, measurement, isConsumed }) => {
|
||||
if (isConsumed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const currentViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
const { viewPlaneNormal } = currentViewport.getCamera();
|
||||
const referencedImageId = csToolsUtils.getClosestImageIdForStackViewport(
|
||||
currentViewport as StackViewport,
|
||||
measurement.points[0],
|
||||
viewPlaneNormal
|
||||
);
|
||||
const imageIndex = (currentViewport as StackViewport)
|
||||
.getImageIds()
|
||||
.indexOf(referencedImageId);
|
||||
csToolsUtils.jumpToSlice(currentViewport.element, { imageIndex });
|
||||
} catch (error) {
|
||||
console.warn('Unable to jump to image based on measurement coordinate', error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -7,10 +7,11 @@ import {
|
||||
Types as cs3DToolsTypes,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { getTrackingUniqueIdentifiersForElement } from './modules/dicomSRModule';
|
||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||
import { SCOORDTypes } from '../enums';
|
||||
import toolNames from './toolNames';
|
||||
|
||||
export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
static toolName = 'DICOMSRDisplay';
|
||||
static toolName = toolNames.DICOMSRDisplay;
|
||||
|
||||
constructor(
|
||||
toolProps = {},
|
||||
@ -112,19 +113,19 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
let canvasCoordinatesAdapter;
|
||||
|
||||
switch (GraphicType) {
|
||||
case SCOORD_TYPES.POINT:
|
||||
case SCOORDTypes.POINT:
|
||||
renderMethod = this.renderPoint;
|
||||
break;
|
||||
case SCOORD_TYPES.MULTIPOINT:
|
||||
case SCOORDTypes.MULTIPOINT:
|
||||
renderMethod = this.renderMultipoint;
|
||||
break;
|
||||
case SCOORD_TYPES.POLYLINE:
|
||||
case SCOORDTypes.POLYLINE:
|
||||
renderMethod = this.renderPolyLine;
|
||||
break;
|
||||
case SCOORD_TYPES.CIRCLE:
|
||||
case SCOORDTypes.CIRCLE:
|
||||
renderMethod = this.renderEllipse;
|
||||
break;
|
||||
case SCOORD_TYPES.ELLIPSE:
|
||||
case SCOORDTypes.ELLIPSE:
|
||||
renderMethod = this.renderEllipse;
|
||||
canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners;
|
||||
break;
|
||||
@ -335,7 +336,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
}
|
||||
|
||||
const { annotationUID, data = {} } = annotation;
|
||||
const { label } = data;
|
||||
const { labels } = data;
|
||||
const { color } = options;
|
||||
|
||||
let adaptedCanvasCoordinates = canvasCoordinates;
|
||||
@ -343,7 +344,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
if (typeof canvasCoordinatesAdapter === 'function') {
|
||||
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
||||
}
|
||||
const textLines = this._getTextBoxLinesFromLabels(label);
|
||||
const textLines = this._getTextBoxLinesFromLabels(labels);
|
||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||
|
||||
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
||||
|
||||
203
extensions/cornerstone-dicom-sr/src/tools/SCOORD3DPointTool.ts
Normal file
203
extensions/cornerstone-dicom-sr/src/tools/SCOORD3DPointTool.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { Types, metaData, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import {
|
||||
annotation,
|
||||
drawing,
|
||||
utilities,
|
||||
Types as cs3DToolsTypes,
|
||||
AnnotationDisplayTool,
|
||||
} from '@cornerstonejs/tools';
|
||||
import toolNames from './toolNames';
|
||||
import { Annotation } from '@cornerstonejs/tools/dist/types/types';
|
||||
|
||||
export default class SCOORD3DPointTool extends AnnotationDisplayTool {
|
||||
static toolName = toolNames.SRSCOORD3DPoint;
|
||||
|
||||
constructor(
|
||||
toolProps = {},
|
||||
defaultToolProps = {
|
||||
configuration: {},
|
||||
}
|
||||
) {
|
||||
super(toolProps, defaultToolProps);
|
||||
}
|
||||
|
||||
_getTextBoxLinesFromLabels(labels) {
|
||||
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!
|
||||
|
||||
const labelLength = Math.min(labels.length, 5);
|
||||
const lines = [];
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
// This tool should not inherit from AnnotationTool and we should not need
|
||||
// to add the following lines.
|
||||
isPointNearTool = () => null;
|
||||
getHandleNearImagePoint = () => null;
|
||||
|
||||
renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => {
|
||||
const { viewport } = enabledElement;
|
||||
const { element } = viewport;
|
||||
|
||||
const annotations = annotation.state.getAnnotations(this.getToolName(), element);
|
||||
|
||||
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
||||
if (!annotations?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter toolData to only render the data for the active SR.
|
||||
const filteredAnnotations = annotations;
|
||||
if (!viewport._actors?.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
const styleSpecifier: cs3DToolsTypes.AnnotationStyle.StyleSpecifier = {
|
||||
toolGroupId: this.toolGroupId,
|
||||
toolName: this.getToolName(),
|
||||
viewportId: enabledElement.viewport.id,
|
||||
};
|
||||
|
||||
for (let i = 0; i < filteredAnnotations.length; i++) {
|
||||
const annotation = filteredAnnotations[i];
|
||||
|
||||
const annotationUID = annotation.annotationUID;
|
||||
const { renderableData } = annotation.data;
|
||||
const { POINT: points } = renderableData;
|
||||
|
||||
styleSpecifier.annotationUID = annotationUID;
|
||||
|
||||
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
||||
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
||||
const color = this.getStyle('color', styleSpecifier, annotation);
|
||||
|
||||
const options = {
|
||||
color,
|
||||
lineDash,
|
||||
lineWidth,
|
||||
};
|
||||
|
||||
const point = points[0][0];
|
||||
|
||||
// check if viewport can render it
|
||||
const viewable = viewport.isReferenceViewable(
|
||||
{ FrameOfReferenceUID: annotation.metadata.FrameOfReferenceUID, cameraFocalPoint: point },
|
||||
{ asNearbyProjection: true }
|
||||
);
|
||||
|
||||
if (!viewable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// render the point
|
||||
const arrowPointCanvas = viewport.worldToCanvas(point);
|
||||
// Todo: configure this
|
||||
const arrowEndCanvas = [arrowPointCanvas[0] + 20, arrowPointCanvas[1] + 20];
|
||||
const canvasCoordinates = [arrowPointCanvas, arrowEndCanvas];
|
||||
|
||||
drawing.drawArrow(
|
||||
svgDrawingHelper,
|
||||
annotationUID,
|
||||
'1',
|
||||
canvasCoordinates[1],
|
||||
canvasCoordinates[0],
|
||||
{
|
||||
color: options.color,
|
||||
width: options.lineWidth,
|
||||
}
|
||||
);
|
||||
|
||||
this.renderTextBox(
|
||||
svgDrawingHelper,
|
||||
viewport,
|
||||
canvasCoordinates,
|
||||
annotation,
|
||||
styleSpecifier,
|
||||
options
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderTextBox(
|
||||
svgDrawingHelper,
|
||||
viewport,
|
||||
canvasCoordinates,
|
||||
annotation,
|
||||
styleSpecifier,
|
||||
options = {}
|
||||
) {
|
||||
if (!canvasCoordinates || !annotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { annotationUID, data = {} } = annotation;
|
||||
const { labels } = data;
|
||||
|
||||
const textLines = [];
|
||||
|
||||
for (const label of labels) {
|
||||
// make this generic
|
||||
// fix this
|
||||
if (label.label === '363698007') {
|
||||
textLines.push(`Finding Site: ${label.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
const { color } = options;
|
||||
|
||||
const adaptedCanvasCoordinates = canvasCoordinates;
|
||||
// adapt coordinates if there is an adapter
|
||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||
|
||||
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
||||
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
||||
}
|
||||
|
||||
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
||||
|
||||
const textBoxUID = '1';
|
||||
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
||||
|
||||
const boundingBox = drawing.drawLinkedTextBox(
|
||||
svgDrawingHelper,
|
||||
annotationUID,
|
||||
textBoxUID,
|
||||
textLines,
|
||||
textBoxPosition,
|
||||
canvasCoordinates,
|
||||
{},
|
||||
{
|
||||
...textBoxOptions,
|
||||
color,
|
||||
}
|
||||
);
|
||||
|
||||
const { x: left, y: top, width, height } = boundingBox;
|
||||
|
||||
annotation.data.handles.textBox.worldBoundingBox = {
|
||||
topLeft: viewport.canvasToWorld([left, top]),
|
||||
topRight: viewport.canvasToWorld([left + width, top]),
|
||||
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
||||
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
||||
};
|
||||
}
|
||||
|
||||
public getLinkedTextBoxStyle(
|
||||
specifications: cs3DToolsTypes.AnnotationStyle.StyleSpecifier,
|
||||
annotation?: Annotation
|
||||
): Record<string, unknown> {
|
||||
// Todo: this function can be used to set different styles for different toolMode
|
||||
// for the textBox.
|
||||
|
||||
return {
|
||||
visibility: this.getStyle('textBoxVisibility', specifications, annotation),
|
||||
fontFamily: this.getStyle('textBoxFontFamily', specifications, annotation),
|
||||
fontSize: this.getStyle('textBoxFontSize', specifications, annotation),
|
||||
color: this.getStyle('textBoxColor', specifications, annotation),
|
||||
shadow: this.getStyle('textBoxShadow', specifications, annotation),
|
||||
background: this.getStyle('textBoxBackground', specifications, annotation),
|
||||
lineWidth: this.getStyle('textBoxLinkLineWidth', specifications, annotation),
|
||||
lineDash: this.getStyle('textBoxLinkLineDash', specifications, annotation),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,5 @@
|
||||
import DICOMSRDisplayTool from './DICOMSRDisplayTool';
|
||||
|
||||
const toolNames = {
|
||||
DICOMSRDisplay: DICOMSRDisplayTool.toolName,
|
||||
DICOMSRDisplay: 'DICOMSRDisplay',
|
||||
SRLength: 'SRLength',
|
||||
SRBidirectional: 'SRBidirectional',
|
||||
SREllipticalROI: 'SREllipticalROI',
|
||||
@ -11,6 +9,7 @@ const toolNames = {
|
||||
SRCobbAngle: 'SRCobbAngle',
|
||||
SRRectangleROI: 'SRRectangleROI',
|
||||
SRPlanarFreehandROI: 'SRPlanarFreehandROI',
|
||||
SRSCOORD3DPoint: 'SRSCOORD3DPoint',
|
||||
};
|
||||
|
||||
export default toolNames;
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
const SRSCOOR3DProbe = {
|
||||
toAnnotation: measurement => {},
|
||||
|
||||
/**
|
||||
* Maps cornerstone annotation event data to measurement service format.
|
||||
*
|
||||
* @param {Object} cornerstone Cornerstone event data
|
||||
* @return {Measurement} Measurement instance
|
||||
*/
|
||||
toMeasurement: (
|
||||
csToolsEventDetail,
|
||||
displaySetService,
|
||||
CornerstoneViewportService,
|
||||
getValueTypeFromToolType,
|
||||
customizationService
|
||||
) => {
|
||||
const { annotation } = csToolsEventDetail;
|
||||
const { metadata, data, annotationUID } = annotation;
|
||||
|
||||
if (!metadata || !data) {
|
||||
console.warn('Probe tool: Missing metadata or data');
|
||||
return null;
|
||||
}
|
||||
|
||||
const { toolName } = metadata;
|
||||
const { points } = data.handles;
|
||||
|
||||
const displayText = getDisplayText(annotation);
|
||||
return {
|
||||
uid: annotationUID,
|
||||
points,
|
||||
metadata,
|
||||
toolName: metadata.toolName,
|
||||
label: data.label,
|
||||
displayText: displayText,
|
||||
data: data.cachedStats,
|
||||
type: getValueTypeFromToolType?.(toolName) ?? null,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function getDisplayText(annotation) {
|
||||
const { data } = annotation;
|
||||
|
||||
if (!data) {
|
||||
return [''];
|
||||
}
|
||||
const { labels } = data;
|
||||
|
||||
const displayText = [];
|
||||
|
||||
for (const label of labels) {
|
||||
// make this generic
|
||||
if (label.label === '33636980076') {
|
||||
displayText.push(`Finding Site: ${label.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return displayText;
|
||||
}
|
||||
|
||||
export default SRSCOOR3DProbe;
|
||||
67
extensions/cornerstone-dicom-sr/src/utils/addSRAnnotation.ts
Normal file
67
extensions/cornerstone-dicom-sr/src/utils/addSRAnnotation.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { Types, annotation } from '@cornerstonejs/tools';
|
||||
import { metaData } from '@cornerstonejs/core';
|
||||
|
||||
import getRenderableData from './getRenderableData';
|
||||
import toolNames from '../tools/toolNames';
|
||||
|
||||
export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
let toolName = toolNames.DICOMSRDisplay;
|
||||
const renderableData = measurement.coords.reduce((acc, coordProps) => {
|
||||
acc[coordProps.GraphicType] = acc[coordProps.GraphicType] || [];
|
||||
acc[coordProps.GraphicType].push(getRenderableData({ ...coordProps, imageId }));
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const { TrackingUniqueIdentifier } = measurement;
|
||||
const { ValueType: valueType, GraphicType: graphicType } = measurement.coords[0];
|
||||
const graphicTypePoints = renderableData[graphicType];
|
||||
|
||||
/** TODO: Read the tool name from the DICOM SR identification type in the future. */
|
||||
let frameOfReferenceUID = null;
|
||||
|
||||
if (imageId) {
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||
frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID;
|
||||
}
|
||||
|
||||
if (valueType === 'SCOORD3D') {
|
||||
toolName = toolNames.SRSCOORD3DPoint;
|
||||
|
||||
// get the ReferencedFrameOfReferenceUID from the measurement
|
||||
frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence;
|
||||
}
|
||||
|
||||
const SRAnnotation: Types.Annotation = {
|
||||
annotationUID: TrackingUniqueIdentifier,
|
||||
highlighted: false,
|
||||
isLocked: false,
|
||||
invalidated: false,
|
||||
metadata: {
|
||||
toolName,
|
||||
valueType,
|
||||
graphicType,
|
||||
FrameOfReferenceUID: frameOfReferenceUID,
|
||||
referencedImageId: imageId,
|
||||
},
|
||||
data: {
|
||||
label: measurement.labels?.[0]?.value || undefined,
|
||||
displayText: measurement.displayText || undefined,
|
||||
handles: {
|
||||
textBox: measurement.textBox ?? {},
|
||||
points: graphicTypePoints[0],
|
||||
},
|
||||
cachedStats: {},
|
||||
frameNumber,
|
||||
renderableData,
|
||||
TrackingUniqueIdentifier,
|
||||
labels: measurement.labels,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* const annotationManager = annotation.annotationState.getAnnotationManager();
|
||||
* was not triggering annotation_added events.
|
||||
*/
|
||||
annotation.state.addAnnotation(SRAnnotation);
|
||||
console.debug('Adding SR annotation:', SRAnnotation);
|
||||
}
|
||||
@ -1,8 +1,14 @@
|
||||
import { addTool } from '@cornerstonejs/tools';
|
||||
|
||||
export default function addToolInstance(name: string, toolClass, configuration?): void {
|
||||
export default function addToolInstance(name: string, toolClass, configuration = {}): void {
|
||||
class InstanceClass extends toolClass {
|
||||
static toolName = name;
|
||||
constructor(toolProps, defaultToolProps) {
|
||||
toolProps.configuration = toolProps.configuration
|
||||
? { ...toolProps.configuration, ...configuration }
|
||||
: configuration;
|
||||
super(toolProps, defaultToolProps);
|
||||
}
|
||||
}
|
||||
addTool(InstanceClass);
|
||||
}
|
||||
|
||||
@ -61,6 +61,11 @@ const createReferencedImageDisplaySet = (displaySetService, displaySet) => {
|
||||
|
||||
const imageSet = new ImageSet(instances);
|
||||
const instance = instances[0];
|
||||
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
||||
SeriesDate: instance.SeriesDate,
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
/**
|
||||
* Extracts the label from the toolData imported from dcmjs. We need to do this
|
||||
* as dcmjs does not depeend on OHIF/the measurementService, it just produces data for cornestoneTools.
|
||||
@ -9,13 +13,15 @@
|
||||
export default function getLabelFromDCMJSImportedToolData(toolData) {
|
||||
const { findingSites = [], finding } = toolData;
|
||||
|
||||
let freeTextLabel = findingSites.find(fs => fs.CodeValue === 'CORNERSTONEFREETEXT');
|
||||
let freeTextLabel = findingSites.find(
|
||||
fs => fs.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||
);
|
||||
|
||||
if (freeTextLabel) {
|
||||
return freeTextLabel.CodeMeaning;
|
||||
}
|
||||
|
||||
if (finding && finding.CodeValue === 'CORNERSTONEFREETEXT') {
|
||||
if (finding && finding.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT) {
|
||||
return finding.CodeMeaning;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,106 +1,49 @@
|
||||
import { vec3 } from 'gl-matrix';
|
||||
import { Types, annotation } from '@cornerstonejs/tools';
|
||||
import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core';
|
||||
|
||||
import toolNames from '../tools/toolNames';
|
||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||
import { SCOORDTypes } from '../enums';
|
||||
|
||||
const EPSILON = 1e-4;
|
||||
|
||||
export default function addDICOMSRDisplayAnnotation(measurement, imageId, frameNumber) {
|
||||
const toolName = toolNames.DICOMSRDisplay;
|
||||
|
||||
const measurementData = {
|
||||
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
||||
renderableData: {},
|
||||
labels: measurement.labels,
|
||||
imageId,
|
||||
};
|
||||
|
||||
measurement.coords.forEach(coord => {
|
||||
const { GraphicType, GraphicData } = coord;
|
||||
|
||||
if (measurementData.renderableData[GraphicType] === undefined) {
|
||||
measurementData.renderableData[GraphicType] = [];
|
||||
const getRenderableCoords = ({ GraphicData, ValueType, imageId }) => {
|
||||
const renderableData = [];
|
||||
if (ValueType === 'SCOORD3D') {
|
||||
for (let i = 0; i < GraphicData.length; i += 3) {
|
||||
renderableData.push([GraphicData[i], GraphicData[i + 1], GraphicData[i + 2]]);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
||||
renderableData.push(worldPos);
|
||||
}
|
||||
}
|
||||
return renderableData;
|
||||
};
|
||||
|
||||
measurementData.renderableData[GraphicType].push(
|
||||
_getRenderableData(GraphicType, GraphicData, imageId)
|
||||
);
|
||||
});
|
||||
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||
|
||||
/**
|
||||
* This annotation (DICOMSRDisplay) is only used by the SR viewport.
|
||||
* This is used before the annotation is hydrated. If hydrated the measurement will be added
|
||||
* to the measurement service and will be available for the other viewports.
|
||||
*/
|
||||
const SRAnnotation: Types.Annotation = {
|
||||
annotationUID: measurement.TrackingUniqueIdentifier,
|
||||
highlighted: false,
|
||||
isLocked: false,
|
||||
invalidated: false,
|
||||
metadata: {
|
||||
toolName: toolName,
|
||||
FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID,
|
||||
referencedImageId: imageId,
|
||||
},
|
||||
data: {
|
||||
label: measurement.labels,
|
||||
handles: {
|
||||
textBox: measurement.textBox ?? {},
|
||||
},
|
||||
cachedStats: {},
|
||||
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
||||
renderableData: measurementData.renderableData,
|
||||
frameNumber,
|
||||
},
|
||||
};
|
||||
const annotationManager = annotation.state.getAnnotationManager();
|
||||
annotationManager.addAnnotation(SRAnnotation);
|
||||
}
|
||||
|
||||
function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
let renderableData: csTypes.Point3[];
|
||||
function getRenderableData({ GraphicType, GraphicData, ValueType, imageId }) {
|
||||
let renderableData = [];
|
||||
|
||||
switch (GraphicType) {
|
||||
case SCOORD_TYPES.POINT:
|
||||
case SCOORD_TYPES.MULTIPOINT:
|
||||
case SCOORD_TYPES.POLYLINE:
|
||||
renderableData = [];
|
||||
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
||||
GraphicData[i],
|
||||
GraphicData[i + 1],
|
||||
]);
|
||||
|
||||
renderableData.push(worldPos);
|
||||
}
|
||||
|
||||
case SCOORDTypes.POINT:
|
||||
case SCOORDTypes.MULTIPOINT:
|
||||
case SCOORDTypes.POLYLINE: {
|
||||
renderableData = getRenderableCoords({ GraphicData, ValueType, imageId });
|
||||
break;
|
||||
case SCOORD_TYPES.CIRCLE: {
|
||||
const pointsWorld = [];
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
||||
GraphicData[i],
|
||||
GraphicData[i + 1],
|
||||
]);
|
||||
|
||||
pointsWorld.push(worldPos);
|
||||
}
|
||||
|
||||
}
|
||||
case SCOORDTypes.CIRCLE: {
|
||||
const pointsWorld: csTypes.Point3[] = getRenderableCoords({
|
||||
GraphicData,
|
||||
ValueType,
|
||||
imageId,
|
||||
});
|
||||
// We do not have an explicit draw circle svg helper in Cornerstone3D at
|
||||
// this time, but we can use the ellipse svg helper to draw a circle, so
|
||||
// here we reshape the data for that purpose.
|
||||
const center = pointsWorld[0];
|
||||
const onPerimeter = pointsWorld[1];
|
||||
|
||||
const radius = vec3.distance(center, onPerimeter);
|
||||
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||
|
||||
if (!imagePlaneModule) {
|
||||
throw new Error('No imagePlaneModule found');
|
||||
}
|
||||
@ -115,14 +58,12 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
|
||||
// we need to get major/minor axis (which are both the same size major = minor)
|
||||
|
||||
// first axisStart
|
||||
const firstAxisStart = vec3.create();
|
||||
vec3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
|
||||
|
||||
const firstAxisEnd = vec3.create();
|
||||
vec3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
|
||||
|
||||
// second axisStart
|
||||
const secondAxisStart = vec3.create();
|
||||
vec3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
|
||||
|
||||
@ -138,21 +79,16 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
|
||||
break;
|
||||
}
|
||||
case SCOORD_TYPES.ELLIPSE: {
|
||||
case SCOORDTypes.ELLIPSE: {
|
||||
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
||||
// But Cornerstone3D points are ordered as top, bottom, left, right for the
|
||||
// ellipse so we need to identify if the majorAxis is horizontal or vertical
|
||||
// and then choose the correct points to use for the ellipse.
|
||||
|
||||
const pointsWorld: csTypes.Point3[] = [];
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
||||
GraphicData[i],
|
||||
GraphicData[i + 1],
|
||||
]);
|
||||
|
||||
pointsWorld.push(worldPos);
|
||||
}
|
||||
const pointsWorld: csTypes.Point3[] = getRenderableCoords({
|
||||
GraphicData,
|
||||
ValueType,
|
||||
imageId,
|
||||
});
|
||||
|
||||
const majorAxisStart = vec3.fromValues(...pointsWorld[0]);
|
||||
const majorAxisEnd = vec3.fromValues(...pointsWorld[1]);
|
||||
@ -202,3 +138,5 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
|
||||
return renderableData;
|
||||
}
|
||||
|
||||
export default getRenderableData;
|
||||
@ -3,14 +3,12 @@ import OHIF, { DicomMetadataStore } from '@ohif/core';
|
||||
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
import { annotation as CsAnnotation } from '@cornerstonejs/tools';
|
||||
const { locking } = CsAnnotation;
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
|
||||
const { locking } = CsAnnotation;
|
||||
const { guid } = OHIF.utils;
|
||||
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
|
||||
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||
|
||||
const convertCode = (codingValues, code) => {
|
||||
|
||||
@ -4,7 +4,7 @@ const cornerstoneAdapters =
|
||||
adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
||||
|
||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||
const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG;
|
||||
const CORNERSTONE_3D_TAG = adaptersSR.Cornerstone3D.CORNERSTONE_3D_TAG;
|
||||
|
||||
/**
|
||||
* Checks if the given `displaySet`can be rehydrated into the `measurementService`.
|
||||
|
||||
@ -42,9 +42,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^1.82.4",
|
||||
"@cornerstonejs/core": "^1.86.0",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^1.82.4",
|
||||
"@cornerstonejs/tools": "^1.82.4",
|
||||
"@cornerstonejs/tools": "^1.86.0",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,9 +36,9 @@
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.2",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.82.4",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.86.0",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@ohif/core": "3.9.0-beta.98",
|
||||
"@ohif/ui": "3.9.0-beta.98",
|
||||
@ -55,10 +55,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.82.4",
|
||||
"@cornerstonejs/core": "^1.82.4",
|
||||
"@cornerstonejs/adapters": "^1.86.0",
|
||||
"@cornerstonejs/core": "^1.86.0",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^1.82.4",
|
||||
"@cornerstonejs/tools": "^1.82.4",
|
||||
"@cornerstonejs/tools": "^1.86.0",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@kitware/vtk.js": "30.4.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@ -610,5 +610,4 @@ OHIFCornerstoneViewport.propTypes = {
|
||||
// to set the initial state of the viewport's first image to render
|
||||
initialImageIdOrIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
export default OHIFCornerstoneViewport;
|
||||
|
||||
9
extensions/cornerstone/src/enums.ts
Normal file
9
extensions/cornerstone/src/enums.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
export const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const Enums = {
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION,
|
||||
};
|
||||
|
||||
export default Enums;
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import * as csStreamingImageVolumeLoader from '@cornerstonejs/streaming-image-volume-loader';
|
||||
import { Enums as cs3DToolsEnums } from '@cornerstonejs/tools';
|
||||
import { Types } from '@ohif/core';
|
||||
import Enums from './enums';
|
||||
|
||||
import init from './init';
|
||||
import getCustomizationModule from './getCustomizationModule';
|
||||
@ -234,5 +235,6 @@ export {
|
||||
ImageOverlayViewerTool,
|
||||
getSOPInstanceAttributes,
|
||||
dicomLoaderService,
|
||||
Enums,
|
||||
};
|
||||
export default cornerstoneExtension;
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
import { eventTarget } from '@cornerstonejs/core';
|
||||
import { Enums, annotation } from '@cornerstonejs/tools';
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
|
||||
import * as CSExtensionEnums from './enums';
|
||||
import { toolNames } from './initCornerstoneTools';
|
||||
import { onCompletedCalibrationLine } from './tools/CalibrationLineTool';
|
||||
|
||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
const { removeAnnotation } = annotation.state;
|
||||
|
||||
const csToolsEvents = Enums.Events;
|
||||
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const initMeasurementService = (
|
||||
measurementService,
|
||||
displaySetService,
|
||||
@ -189,11 +187,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
cornerstoneViewportService,
|
||||
customizationService
|
||||
);
|
||||
connectMeasurementServiceToTools(
|
||||
measurementService,
|
||||
cornerstoneViewportService,
|
||||
csTools3DVer1MeasurementSource
|
||||
);
|
||||
connectMeasurementServiceToTools(measurementService, cornerstoneViewportService);
|
||||
const { annotationToMeasurement, remove } = csTools3DVer1MeasurementSource;
|
||||
|
||||
//
|
||||
@ -210,7 +204,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
onCompletedCalibrationLine(servicesManager, csToolsEvent)
|
||||
.then(
|
||||
() => {
|
||||
console.log('calibration applied');
|
||||
console.log('Calibration applied.');
|
||||
},
|
||||
() => true
|
||||
)
|
||||
@ -230,7 +224,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
annotationToMeasurement(toolName, annotationAddedEventDetail);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to update measurement:', error);
|
||||
console.warn('Failed to add measurement:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,6 +251,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
console.warn('Failed to update measurement:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function selectMeasurement(csToolsEvent) {
|
||||
try {
|
||||
const annotationSelectionEventDetail = csToolsEvent.detail;
|
||||
@ -276,7 +271,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to select and unselect measurements:', error);
|
||||
console.warn('Failed to select/unselect measurements:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,20 +283,13 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
*/
|
||||
function removeMeasurement(csToolsEvent) {
|
||||
try {
|
||||
try {
|
||||
const annotationRemovedEventDetail = csToolsEvent.detail;
|
||||
const {
|
||||
annotation: { annotationUID },
|
||||
} = annotationRemovedEventDetail;
|
||||
|
||||
const measurement = measurementService.getMeasurement(annotationUID);
|
||||
|
||||
if (measurement) {
|
||||
console.log('~~ removeEvt', csToolsEvent);
|
||||
remove(annotationUID, annotationRemovedEventDetail);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to update measurement:', error);
|
||||
const annotationRemovedEventDetail = csToolsEvent.detail;
|
||||
const {
|
||||
annotation: { annotationUID },
|
||||
} = annotationRemovedEventDetail;
|
||||
const measurement = measurementService.getMeasurement(annotationUID);
|
||||
if (measurement) {
|
||||
remove(annotationUID, annotationRemovedEventDetail);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to remove measurement:', error);
|
||||
@ -325,19 +313,10 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
return csTools3DVer1MeasurementSource;
|
||||
};
|
||||
|
||||
const connectMeasurementServiceToTools = (
|
||||
measurementService,
|
||||
cornerstoneViewportService,
|
||||
measurementSource
|
||||
) => {
|
||||
const connectMeasurementServiceToTools = (measurementService, cornerstoneViewportService) => {
|
||||
const { MEASUREMENT_REMOVED, MEASUREMENTS_CLEARED, MEASUREMENT_UPDATED, RAW_MEASUREMENT_ADDED } =
|
||||
measurementService.EVENTS;
|
||||
|
||||
const csTools3DVer1MeasurementSource = measurementService.getSource(
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||
);
|
||||
|
||||
measurementService.subscribe(MEASUREMENTS_CLEARED, ({ measurements }) => {
|
||||
if (!Object.keys(measurements).length) {
|
||||
return;
|
||||
@ -348,7 +327,6 @@ const connectMeasurementServiceToTools = (
|
||||
if (source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) {
|
||||
continue;
|
||||
}
|
||||
|
||||
removeAnnotation(uid);
|
||||
}
|
||||
});
|
||||
|
||||
@ -188,6 +188,12 @@ class CornerstoneCacheService {
|
||||
|
||||
const displaySet = nonOverlayDisplaySets[0];
|
||||
|
||||
if (displaySet.load && displaySet.load instanceof Function) {
|
||||
const { userAuthenticationService } = this.servicesManager.services;
|
||||
const headers = userAuthenticationService.getAuthorizationHeader();
|
||||
await displaySet.load({ headers });
|
||||
}
|
||||
|
||||
let stackImageIds = this.stackImageIds.get(displaySet.displaySetInstanceUID);
|
||||
|
||||
if (!stackImageIds) {
|
||||
|
||||
@ -404,6 +404,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
): void {
|
||||
const renderingEngine = this.getRenderingEngine();
|
||||
|
||||
// if not valid viewportData then return early
|
||||
if (viewportData.viewportType === csEnums.ViewportType.STACK) {
|
||||
// check if imageIds is valid
|
||||
if (!viewportData.data[0].imageIds?.length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the old viewportInfo, which may have old options but we might be
|
||||
// using its viewport (same viewportId as the new viewportInfo)
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
@ -641,6 +649,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
|
||||
this._handleOverlays(viewport);
|
||||
|
||||
if (!imageIds?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
||||
viewport.setProperties({ ...properties });
|
||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||
|
||||
@ -19,11 +19,11 @@ const Probe = {
|
||||
getValueTypeFromToolType,
|
||||
customizationService
|
||||
) => {
|
||||
const { annotation, viewportId } = csToolsEventDetail;
|
||||
const { annotation } = csToolsEventDetail;
|
||||
const { metadata, data, annotationUID } = annotation;
|
||||
|
||||
if (!metadata || !data) {
|
||||
console.warn('Length tool: Missing metadata or data');
|
||||
console.warn('Probe tool: Missing metadata or data');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export default [
|
||||
const supportedTools = [
|
||||
'Length',
|
||||
'EllipticalROI',
|
||||
'CircleROI',
|
||||
@ -11,6 +11,8 @@ export default [
|
||||
'PlanarFreehandROI',
|
||||
'SplineROI',
|
||||
'LivewireContour',
|
||||
'Probe',
|
||||
'UltrasoundDirectionalTool',
|
||||
'SCOORD3DPoint',
|
||||
];
|
||||
|
||||
export default supportedTools;
|
||||
|
||||
@ -94,7 +94,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
EllipticalROI: {
|
||||
toAnnotation: EllipticalROI.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -111,7 +110,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
CircleROI: {
|
||||
toAnnotation: CircleROI.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -128,7 +126,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
RectangleROI: {
|
||||
toAnnotation: RectangleROI.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -145,7 +142,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
PlanarFreehandROI: {
|
||||
toAnnotation: PlanarFreehandROI.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -162,7 +158,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
SplineROI: {
|
||||
toAnnotation: SplineROI.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -179,7 +174,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
LivewireContour: {
|
||||
toAnnotation: LivewireContour.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -196,7 +190,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
ArrowAnnotate: {
|
||||
toAnnotation: ArrowAnnotate.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -214,7 +207,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Probe: {
|
||||
toAnnotation: Probe.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -232,7 +224,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
CobbAngle: {
|
||||
toAnnotation: CobbAngle.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
@ -249,7 +240,6 @@ const measurementServiceMappingsFactory = (
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Angle: {
|
||||
toAnnotation: Angle.toAnnotation,
|
||||
toMeasurement: csToolsAnnotation =>
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"colormap": "^2.3",
|
||||
"mathjs": "^12.4.2"
|
||||
}
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^1.82.0",
|
||||
"@cornerstonejs/tools": "^1.82.0",
|
||||
"@cornerstonejs/core": "^1.86.0",
|
||||
"@cornerstonejs/tools": "^1.86.0",
|
||||
"@ohif/core": "3.9.0-beta.98",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.9.0-beta.98",
|
||||
"@ohif/ui": "3.9.0-beta.98",
|
||||
|
||||
@ -4,11 +4,12 @@ import { StudySummary, MeasurementTable, useViewportGrid, ActionButtons } from '
|
||||
import { DicomMetadataStore, utils } from '@ohif/core';
|
||||
import { useDebounce } from '@hooks';
|
||||
import { useAppConfig } from '@state';
|
||||
import { useTrackedMeasurements } from '../../getContextModule';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Separator } from '@ohif/ui-next';
|
||||
|
||||
import { useTrackedMeasurements } from '../../getContextModule';
|
||||
|
||||
const { downloadCSVReport } = utils;
|
||||
const { formatDate } = utils;
|
||||
|
||||
@ -43,49 +44,50 @@ function PanelMeasurementTableTracking({
|
||||
|
||||
useEffect(() => {
|
||||
const measurements = measurementService.getMeasurements();
|
||||
const filteredMeasurements = measurements.filter(
|
||||
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
|
||||
);
|
||||
|
||||
const mappedMeasurements = filteredMeasurements.map(m =>
|
||||
_mapMeasurementToDisplay(m, measurementService.VALUE_TYPES, displaySetService)
|
||||
const mappedMeasurements = measurements.map(m =>
|
||||
_mapMeasurementToDisplay(m, displaySetService)
|
||||
);
|
||||
setDisplayMeasurements(mappedMeasurements);
|
||||
// eslint-ignore-next-line
|
||||
}, [measurementService, trackedStudy, trackedSeries, debouncedMeasurementChangeTimestamp]);
|
||||
|
||||
const updateDisplayStudySummary = async () => {
|
||||
if (trackedMeasurements.matches('tracking')) {
|
||||
const StudyInstanceUID = trackedStudy;
|
||||
const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
const instanceMeta = studyMeta.series[0].instances[0];
|
||||
const { StudyDate, StudyDescription } = instanceMeta;
|
||||
|
||||
const modalities = new Set();
|
||||
studyMeta.series.forEach(series => {
|
||||
if (trackedSeries.includes(series.SeriesInstanceUID)) {
|
||||
modalities.add(series.instances[0].Modality);
|
||||
}
|
||||
});
|
||||
const modality = Array.from(modalities).join('/');
|
||||
|
||||
if (displayStudySummary.key !== StudyInstanceUID) {
|
||||
setDisplayStudySummary({
|
||||
key: StudyInstanceUID,
|
||||
date: StudyDate, // TODO: Format: '07-Sep-2010'
|
||||
modality,
|
||||
description: StudyDescription,
|
||||
});
|
||||
}
|
||||
} else if (trackedStudy === '' || trackedStudy === undefined) {
|
||||
setDisplayStudySummary(DISPLAY_STUDY_SUMMARY_INITIAL_VALUE);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
measurementService,
|
||||
displaySetService,
|
||||
trackedStudy,
|
||||
trackedSeries,
|
||||
debouncedMeasurementChangeTimestamp,
|
||||
]);
|
||||
|
||||
// ~~ DisplayStudySummary
|
||||
useEffect(() => {
|
||||
const updateDisplayStudySummary = async () => {
|
||||
if (trackedMeasurements.matches('tracking')) {
|
||||
const StudyInstanceUID = trackedStudy;
|
||||
const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
const instanceMeta = studyMeta.series[0].instances[0];
|
||||
const { StudyDate, StudyDescription } = instanceMeta;
|
||||
|
||||
const modalities = new Set();
|
||||
studyMeta.series.forEach(series => {
|
||||
if (trackedSeries.includes(series.SeriesInstanceUID)) {
|
||||
modalities.add(series.instances[0].Modality);
|
||||
}
|
||||
});
|
||||
const modality = Array.from(modalities).join('/');
|
||||
|
||||
if (displayStudySummary.key !== StudyInstanceUID) {
|
||||
setDisplayStudySummary({
|
||||
key: StudyInstanceUID,
|
||||
date: StudyDate, // TODO: Format: '07-Sep-2010'
|
||||
modality,
|
||||
description: StudyDescription,
|
||||
});
|
||||
}
|
||||
} else if (trackedStudy === '' || trackedStudy === undefined) {
|
||||
setDisplayStudySummary(DISPLAY_STUDY_SUMMARY_INITIAL_VALUE);
|
||||
}
|
||||
};
|
||||
updateDisplayStudySummary();
|
||||
}, [displayStudySummary.key, trackedMeasurements, trackedStudy, updateDisplayStudySummary]);
|
||||
}, [displayStudySummary.key, trackedSeries, trackedMeasurements, trackedStudy]);
|
||||
|
||||
// TODO: Better way to consolidated, debounce, check on change?
|
||||
// Are we exposing the right API for measurementService?
|
||||
@ -125,13 +127,11 @@ function PanelMeasurementTableTracking({
|
||||
const trackedMeasurements = measurements.filter(
|
||||
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
|
||||
);
|
||||
|
||||
downloadCSVReport(trackedMeasurements, measurementService);
|
||||
downloadCSVReport(trackedMeasurements);
|
||||
}
|
||||
|
||||
const jumpToImage = ({ uid, isActive }) => {
|
||||
measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid);
|
||||
|
||||
onMeasurementItemClickHandler({ uid, isActive });
|
||||
};
|
||||
|
||||
@ -160,7 +160,6 @@ function PanelMeasurementTableTracking({
|
||||
if (!isActive) {
|
||||
const measurements = [...displayMeasurements];
|
||||
const measurement = measurements.find(m => m.uid === uid);
|
||||
|
||||
measurements.forEach(m => (m.isActive = m.uid !== uid ? false : true));
|
||||
measurement.isActive = true;
|
||||
setDisplayMeasurements(measurements);
|
||||
@ -168,14 +167,25 @@ function PanelMeasurementTableTracking({
|
||||
};
|
||||
|
||||
const displayMeasurementsWithoutFindings = displayMeasurements.filter(
|
||||
dm => dm.measurementType !== measurementService.VALUE_TYPES.POINT && dm.referencedImageId
|
||||
dm =>
|
||||
dm.measurementType !== measurementService.VALUE_TYPES.POINT &&
|
||||
dm.referencedImageId &&
|
||||
trackedStudy === dm.referenceStudyUID &&
|
||||
trackedSeries.includes(dm.referenceSeriesUID)
|
||||
);
|
||||
const additionalFindings = displayMeasurements.filter(
|
||||
dm => dm.measurementType === measurementService.VALUE_TYPES.POINT && dm.referencedImageId
|
||||
dm =>
|
||||
dm.measurementType === measurementService.VALUE_TYPES.POINT &&
|
||||
dm.referencedImageId &&
|
||||
trackedStudy === dm.referenceStudyUID &&
|
||||
trackedSeries.includes(dm.referenceSeriesUID)
|
||||
);
|
||||
const nonAcquisitionMeasurements = displayMeasurements.filter(
|
||||
dm =>
|
||||
dm.referencedImageId == null ||
|
||||
trackedStudy !== dm.referenceStudyUID ||
|
||||
trackedSeries.includes(dm.referenceSeriesUID)
|
||||
);
|
||||
|
||||
const nonAcquisitionMeasurements = displayMeasurements.filter(dm => dm.referencedImageId == null);
|
||||
|
||||
const disabled =
|
||||
additionalFindings.length === 0 &&
|
||||
displayMeasurementsWithoutFindings.length === 0 &&
|
||||
@ -278,23 +288,11 @@ PanelMeasurementTableTracking.propTypes = {
|
||||
};
|
||||
|
||||
// TODO: This could be a measurementService mapper
|
||||
function _mapMeasurementToDisplay(measurement, types, displaySetService) {
|
||||
const { referenceStudyUID, referenceSeriesUID, SOPInstanceUID } = measurement;
|
||||
function _mapMeasurementToDisplay(measurement, displaySetService) {
|
||||
const { referenceSeriesUID } = measurement;
|
||||
|
||||
// TODO: We don't deal with multiframe well yet, would need to update
|
||||
// This in OHIF-312 when we add FrameIndex to measurements.
|
||||
|
||||
const instance = DicomMetadataStore.getInstance(
|
||||
referenceStudyUID,
|
||||
referenceSeriesUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
|
||||
const displaySets = displaySetService.getDisplaySetsForSeries(referenceSeriesUID);
|
||||
|
||||
if (!displaySets[0]?.instances) {
|
||||
throw new Error('The tracked measurements panel should only be tracking "stack" displaySets.');
|
||||
}
|
||||
// This in OHIF-312 when we add FrameIndex to measurements
|
||||
|
||||
const {
|
||||
displayText: baseDisplayText,
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import { addTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool } from '@cornerstonejs/tools';
|
||||
import {
|
||||
addTool,
|
||||
RectangleROIStartEndThresholdTool,
|
||||
CircleROIStartEndThresholdTool,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
|
||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} servicesManager
|
||||
@ -17,11 +22,12 @@ export default function init({ servicesManager }) {
|
||||
addTool(RectangleROIStartEndThresholdTool);
|
||||
addTool(CircleROIStartEndThresholdTool);
|
||||
|
||||
const { RectangleROIStartEndThreshold, CircleROIStartEndThreshold } = measurementServiceMappingsFactory(
|
||||
measurementService,
|
||||
displaySetService,
|
||||
cornerstoneViewportService
|
||||
);
|
||||
const { RectangleROIStartEndThreshold, CircleROIStartEndThreshold } =
|
||||
measurementServiceMappingsFactory(
|
||||
measurementService,
|
||||
displaySetService,
|
||||
cornerstoneViewportService
|
||||
);
|
||||
|
||||
const csTools3DVer1MeasurementSource = measurementService.getSource(
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||
import { getSOPInstanceAttributes } from '@ohif/extension-cornerstone';
|
||||
|
||||
const CircleROIStartEndThreshold = {
|
||||
toAnnotation: (measurement, definition) => { },
|
||||
toAnnotation: (measurement, definition) => {},
|
||||
|
||||
/**
|
||||
* Maps cornerstone annotation event data to measurement service format.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||
import { getSOPInstanceAttributes } from '@ohif/extension-cornerstone';
|
||||
|
||||
const RectangleROIStartEndThreshold = {
|
||||
toAnnotation: (measurement, definition) => {},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold';
|
||||
import CircleROIStartEndThreshold from './CircleROIStartEndThreshold'
|
||||
import CircleROIStartEndThreshold from './CircleROIStartEndThreshold';
|
||||
|
||||
const measurementServiceMappingsFactory = (
|
||||
measurementService,
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { metaData } from '@cornerstonejs/core';
|
||||
|
||||
export default function getSOPInstanceAttributes(imageId) {
|
||||
if (imageId) {
|
||||
return _getUIDFromImageID(imageId);
|
||||
}
|
||||
}
|
||||
|
||||
function _getUIDFromImageID(imageId) {
|
||||
const instance = metaData.get('instance', imageId);
|
||||
|
||||
return {
|
||||
SOPInstanceUID: instance.SOPInstanceUID,
|
||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||
StudyInstanceUID: instance.StudyInstanceUID,
|
||||
frameNumber: instance.frameNumber || 1,
|
||||
};
|
||||
}
|
||||
@ -162,10 +162,10 @@ function modeFactory() {
|
||||
namespace: dicomsr.viewport,
|
||||
displaySetsToDisplay: [dicomsr.sopClassHandler],
|
||||
},
|
||||
// {
|
||||
// namespace: dicomvideo.viewport,
|
||||
// displaySetsToDisplay: [dicomvideo.sopClassHandler],
|
||||
// },
|
||||
{
|
||||
namespace: dicomvideo.viewport,
|
||||
displaySetsToDisplay: [dicomvideo.sopClassHandler],
|
||||
},
|
||||
{
|
||||
namespace: dicompdf.viewport,
|
||||
displaySetsToDisplay: [dicompdf.sopClassHandler],
|
||||
|
||||
@ -26,6 +26,7 @@ const tracked = {
|
||||
|
||||
const dicomsr = {
|
||||
sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr',
|
||||
sopClassHandler3D: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr-3d',
|
||||
viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr',
|
||||
};
|
||||
|
||||
@ -199,6 +200,7 @@ function modeFactory({ modeConfiguration }) {
|
||||
displaySetsToDisplay: [
|
||||
ohif.sopClassHandler,
|
||||
dicomvideo.sopClassHandler,
|
||||
dicomsr.sopClassHandler3D,
|
||||
ohif.wsiSopClassHandler,
|
||||
],
|
||||
},
|
||||
@ -206,10 +208,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
namespace: dicomsr.viewport,
|
||||
displaySetsToDisplay: [dicomsr.sopClassHandler],
|
||||
},
|
||||
// {
|
||||
// namespace: dicomvideo.viewport,
|
||||
// displaySetsToDisplay: [dicomvideo.sopClassHandler],
|
||||
// },
|
||||
{
|
||||
namespace: dicompdf.viewport,
|
||||
displaySetsToDisplay: [dicompdf.sopClassHandler],
|
||||
@ -246,6 +244,7 @@ function modeFactory({ modeConfiguration }) {
|
||||
ohif.sopClassHandler,
|
||||
ohif.wsiSopClassHandler,
|
||||
dicompdf.sopClassHandler,
|
||||
dicomsr.sopClassHandler3D,
|
||||
dicomsr.sopClassHandler,
|
||||
dicomRT.sopClassHandler,
|
||||
],
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { toolNames as SRToolNames } from '@ohif/extension-cornerstone-dicom-sr';
|
||||
|
||||
const colours = {
|
||||
'viewport-0': 'rgb(200, 0, 0)',
|
||||
'viewport-1': 'rgb(200, 200, 0)',
|
||||
@ -89,7 +91,13 @@ function initDefaultToolGroup(
|
||||
{ toolName: toolNames.LivewireContour },
|
||||
{ toolName: toolNames.WindowLevelRegion },
|
||||
],
|
||||
enabled: [{ toolName: toolNames.ImageOverlayViewer }, { toolName: toolNames.ReferenceLines }],
|
||||
enabled: [
|
||||
{ toolName: toolNames.ImageOverlayViewer },
|
||||
{ toolName: toolNames.ReferenceLines },
|
||||
{
|
||||
toolName: SRToolNames.SRSCOORD3DPoint,
|
||||
},
|
||||
],
|
||||
disabled: [
|
||||
{
|
||||
toolName: toolNames.AdvancedMagnify,
|
||||
@ -165,7 +173,6 @@ function initSRToolGroup(extensionManager, toolGroupService) {
|
||||
enabled: [
|
||||
{
|
||||
toolName: SRToolNames.DICOMSRDisplay,
|
||||
bindings: [],
|
||||
},
|
||||
],
|
||||
// disabled
|
||||
|
||||
@ -51,9 +51,9 @@
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.77.6",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.86.0",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@ohif/core": "3.9.0-beta.98",
|
||||
"@ohif/extension-cornerstone": "3.9.0-beta.98",
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useCallback, useRef, useState } from 'react';
|
||||
import React, { useEffect, useCallback, useRef } from 'react';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Types, MeasurementService } from '@ohif/core';
|
||||
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
||||
import EmptyViewport from './EmptyViewport';
|
||||
@ -24,8 +23,13 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
});
|
||||
const layoutHash = useRef(null);
|
||||
|
||||
const { displaySetService, measurementService, hangingProtocolService, uiNotificationService } =
|
||||
servicesManager.services;
|
||||
const {
|
||||
displaySetService,
|
||||
measurementService,
|
||||
hangingProtocolService,
|
||||
cornerstoneViewportService,
|
||||
uiNotificationService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const generateLayoutHash = () => `${numCols}-${numRows}`;
|
||||
|
||||
@ -108,6 +112,10 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
|
||||
const _getUpdatedViewports = useCallback(
|
||||
(viewportId, displaySetInstanceUID) => {
|
||||
if (!displaySetInstanceUID) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let updatedViewports = [];
|
||||
try {
|
||||
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
@ -175,6 +183,7 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Arbitrarily assign the viewport to element 0
|
||||
// TODO - this should perform a search to find the most suitable viewport.
|
||||
updatedViewports[0] = { ...updatedViewports[0] };
|
||||
|
||||
@ -35,9 +35,9 @@
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.3",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.82.0",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.86.0",
|
||||
"@ohif/ui": "3.9.0-beta.98",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21"
|
||||
|
||||
@ -466,7 +466,6 @@ class MeasurementService extends PubSubService {
|
||||
}
|
||||
|
||||
const sourceInfo = this._getSourceToString(source);
|
||||
|
||||
if (!this._sourceHasMappings(source)) {
|
||||
throw new Error(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`);
|
||||
}
|
||||
|
||||
128
yarn.lock
128
yarn.lock
@ -2428,39 +2428,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||
|
||||
"@cornerstonejs/adapters@^1.77.6":
|
||||
version "1.83.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.83.0.tgz#934c462d98b7857ef9670702547505d112fa13f8"
|
||||
integrity sha512-PKNdzwI/i79Gu9ZxM4SemttCKJpSnMUSQSBu27mXzFzm5ENyOGnus3sLV5ciqZBRaHiuyteaHosJhxOavxaX1g==
|
||||
"@cornerstonejs/adapters@^1.86.0":
|
||||
version "1.86.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.86.0.tgz#389938f25b4b3f784c13dda46c3d2523a1db38cf"
|
||||
integrity sha512-Ln8hC2W6d30q97Mhjboq57Qt5WhSl/2IG5Qf5VFRKha/k6Jgd4eP/gdhpUMxTlRZM3ibuQWwshsRZWCJl+VSgQ==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
"@cornerstonejs/tools" "^1.83.0"
|
||||
buffer "^6.0.3"
|
||||
dcmjs "^0.29.8"
|
||||
gl-matrix "^3.4.3"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
ndarray "^1.0.19"
|
||||
|
||||
"@cornerstonejs/adapters@^1.81.6":
|
||||
version "1.82.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.82.0.tgz#4c7ae6341ad7bcf5cf0213e5252fbac009da23b6"
|
||||
integrity sha512-hcLmba3b7jC1wEorCzaS4OSFBuHz5V5I3IJW1PHbwIAWNHiuKMcC4j6ndMeQhVXHtmy56HUQP+yKAPez59k8Yw==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
"@cornerstonejs/tools" "^1.82.0"
|
||||
buffer "^6.0.3"
|
||||
dcmjs "^0.29.8"
|
||||
gl-matrix "^3.4.3"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
ndarray "^1.0.19"
|
||||
|
||||
"@cornerstonejs/adapters@^1.82.4":
|
||||
version "1.83.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.83.1.tgz#69c6bd33dd2ef3f392d4adafac89c0f50776ebf9"
|
||||
integrity sha512-AQlgSMpxNfWEKHthZp050aEI1BEhgzSbc2MmX2NAvHdQglg7YYDusFxZMKXUBB3jr1X9eTP+4VcYwzp4hPyM0Q==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
"@cornerstonejs/tools" "^1.83.1"
|
||||
"@cornerstonejs/tools" "^1.86.0"
|
||||
buffer "^6.0.3"
|
||||
dcmjs "^0.29.8"
|
||||
gl-matrix "^3.4.3"
|
||||
@ -2482,7 +2456,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-libjpeg-turbo-8bit/-/codec-libjpeg-turbo-8bit-1.2.2.tgz#ae384b149d6655e3dd6e18b9891fab479ab5e144"
|
||||
integrity sha512-aAUMK2958YNpOb/7G6e2/aG7hExTiFTASlMt/v90XA0pRHdWiNg5ny4S5SAju0FbIw4zcMnR0qfY+yW3VG2ivg==
|
||||
|
||||
"@cornerstonejs/codec-openjpeg@^1.2.2":
|
||||
"@cornerstonejs/codec-openjpeg@^1.2.2", "@cornerstonejs/codec-openjpeg@^1.2.4":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-1.2.4.tgz#c7cf67a34091eb74a6676abec80a5251c412b551"
|
||||
integrity sha512-UT2su6xZZnCPSuWf2ldzKa/2+guQ7BGgfBSKqxanggwJHh48gZqIAzekmsLyJHMMK5YDK+ti+fzvVJhBS3Xi/g==
|
||||
@ -2492,40 +2466,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
|
||||
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
||||
|
||||
"@cornerstonejs/core@^1.77.6", "@cornerstonejs/core@^1.83.0":
|
||||
version "1.83.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.83.0.tgz#d8781fe95a404f609ebd25a3f3fd93a3a4f58c70"
|
||||
integrity sha512-EEOHw+G6pjSmuPvTsANPKbY/sAuskIkt24qpuE6+1tpKMt7/1pTrJY8mVHIO4/g6cfyWJl9RO8w5nLyR77SomA==
|
||||
dependencies:
|
||||
"@kitware/vtk.js" "30.4.1"
|
||||
comlink "^4.4.1"
|
||||
detect-gpu "^5.0.22"
|
||||
gl-matrix "^3.4.3"
|
||||
lodash.clonedeep "4.5.0"
|
||||
|
||||
"@cornerstonejs/core@^1.81.3":
|
||||
version "1.81.3"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.81.3.tgz#76deaee4a2cce24db5695cb87857accea5645263"
|
||||
integrity sha512-Uievs/wBpw20Xj4B+8UEjb/qe+cmSfz2oWfQzBANcWoqI5pXD77evXE0s6hORCMr4yFUmTIMbGnWwXT2fF2bYw==
|
||||
dependencies:
|
||||
"@kitware/vtk.js" "30.4.1"
|
||||
comlink "^4.4.1"
|
||||
detect-gpu "^5.0.22"
|
||||
gl-matrix "^3.4.3"
|
||||
lodash.clonedeep "4.5.0"
|
||||
|
||||
"@cornerstonejs/core@^1.81.6", "@cornerstonejs/core@^1.82.0":
|
||||
version "1.82.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.82.0.tgz#ad9169e912370a2f0c984923edd9782d9d9402ec"
|
||||
integrity sha512-Pnqug+ZMPADd59CAISK7L+I2RiXlJFvdodV5CCgLkwLfqPXZv2nVyr9/BphE7gFAPhwBwKQwIr568Tuxlwim4Q==
|
||||
dependencies:
|
||||
"@kitware/vtk.js" "30.4.1"
|
||||
comlink "^4.4.1"
|
||||
detect-gpu "^5.0.22"
|
||||
gl-matrix "^3.4.3"
|
||||
lodash.clonedeep "4.5.0"
|
||||
|
||||
"@cornerstonejs/core@^1.82.4", "@cornerstonejs/core@^1.83.1":
|
||||
"@cornerstonejs/core@^1.83.1":
|
||||
version "1.83.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.83.1.tgz#626df83e896a5e8328012c681343f1158840dfc1"
|
||||
integrity sha512-VuLjcBZ+qPeUh+8RDrma7SvDGMEA/kXb1nsf+1yQTJQsqJmNiSyV+Pb7q/bn4X270KtGrEiyCJSNkb7b2zvfhQ==
|
||||
@ -2536,16 +2477,27 @@
|
||||
gl-matrix "^3.4.3"
|
||||
lodash.clonedeep "4.5.0"
|
||||
|
||||
"@cornerstonejs/dicom-image-loader@^1.77.6":
|
||||
version "1.81.3"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.81.3.tgz#8d9165b351954999bbc566d811a9eedd2363d55c"
|
||||
integrity sha512-QFqr0Jq9DWvoBB1rTwI6gL4fc3pawpnG6ebDSZpz+uPmG/QcGYaeGNT+Szrcu2V/q1ByOU/Jxn8ZG5Nuo+kD6A==
|
||||
"@cornerstonejs/core@^1.86.0":
|
||||
version "1.86.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.86.0.tgz#19f94b8a4d37ffbd775b82f3963af5fbe38709de"
|
||||
integrity sha512-yY3TC5abtU4hgcyBONQo+S0MXYgRyWzV/8bWujXilugAj5moDakex/zMc+YGIKlp4PvQBBRFjbu0H8YW3pseSw==
|
||||
dependencies:
|
||||
"@kitware/vtk.js" "30.4.1"
|
||||
comlink "^4.4.1"
|
||||
detect-gpu "^5.0.22"
|
||||
gl-matrix "^3.4.3"
|
||||
lodash.clonedeep "4.5.0"
|
||||
|
||||
"@cornerstonejs/dicom-image-loader@^1.86.0":
|
||||
version "1.86.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.86.0.tgz#19a529bf1e5d27a0a6a9d0994068b12dab5e88dc"
|
||||
integrity sha512-OPZCGdtdomj3TGktATQcLVIpTVv7ZqF8EyPgkEX+eIRXILzpu5Nm6gf3Lwofa5HVFwvmoJmVnU8jToKcoKPkqQ==
|
||||
dependencies:
|
||||
"@cornerstonejs/codec-charls" "^1.2.3"
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
||||
"@cornerstonejs/codec-openjpeg" "^1.2.2"
|
||||
"@cornerstonejs/codec-openjph" "^2.4.5"
|
||||
"@cornerstonejs/core" "^1.81.3"
|
||||
"@cornerstonejs/core" "^1.86.0"
|
||||
dicom-parser "^1.8.9"
|
||||
pako "^2.0.4"
|
||||
uuid "^9.0.0"
|
||||
@ -2558,36 +2510,12 @@
|
||||
"@cornerstonejs/core" "^1.83.1"
|
||||
comlink "^4.4.1"
|
||||
|
||||
"@cornerstonejs/tools@^1.82.0":
|
||||
version "1.82.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.82.0.tgz#c7c9a24cc80755121c2209489e348b8c9d252d0d"
|
||||
integrity sha512-1nnlloa6g5zfirAbVnZ+lfuM6DdtcN8kHkZ29xkedzpP24Le4fRHNeuNf6jTSGcgDXPhoZ+WA+q8NnH6wj1h1Q==
|
||||
"@cornerstonejs/tools@^1.86.0":
|
||||
version "1.86.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.86.0.tgz#ab7ca218faea4c6354866e02bcdf8005282d161c"
|
||||
integrity sha512-yyy7r9Vo0NrTYYeKQtVqpbLRrVaequd9Q6aU2ifiNusdi6w+8Xslh1U/AHfw2Y9fueK8JBkLnKL+o/nLfdaOuQ==
|
||||
dependencies:
|
||||
"@cornerstonejs/core" "^1.82.0"
|
||||
"@icr/polyseg-wasm" "0.4.0"
|
||||
"@types/offscreencanvas" "2019.7.3"
|
||||
comlink "^4.4.1"
|
||||
lodash.clonedeep "4.5.0"
|
||||
lodash.get "^4.4.2"
|
||||
|
||||
"@cornerstonejs/tools@^1.82.4", "@cornerstonejs/tools@^1.83.1":
|
||||
version "1.83.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.83.1.tgz#56761fe339bd575b6636a4b4e12ac702ceb2a246"
|
||||
integrity sha512-KbMIFgXhuMp2zl5ORLkbJY6SwioyGlDw91cU3Hu02WR2KNTbm2b8oIzTY09F4/UzI0n19064mmP5TsPMRPsjMw==
|
||||
dependencies:
|
||||
"@cornerstonejs/core" "^1.83.1"
|
||||
"@icr/polyseg-wasm" "0.4.0"
|
||||
"@types/offscreencanvas" "2019.7.3"
|
||||
comlink "^4.4.1"
|
||||
lodash.clonedeep "4.5.0"
|
||||
lodash.get "^4.4.2"
|
||||
|
||||
"@cornerstonejs/tools@^1.83.0":
|
||||
version "1.83.0"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.83.0.tgz#6148e72972d826abfa640924f5c34f0d4acd75a3"
|
||||
integrity sha512-P7b0iPGidBJ7rlfnpiIZV2jgivdVZKQ7emWd59CGN4HgEStPQOMrb6lnrDvZs1BDK2d2TjZB6vhwH0G/B/quUw==
|
||||
dependencies:
|
||||
"@cornerstonejs/core" "^1.83.0"
|
||||
"@cornerstonejs/core" "^1.86.0"
|
||||
"@icr/polyseg-wasm" "0.4.0"
|
||||
"@types/offscreencanvas" "2019.7.3"
|
||||
comlink "^4.4.1"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user