feat(SR): SCOORD3D point annotations support for stack viewports (#4315)
This commit is contained in:
parent
e0fca8c88c
commit
ac1cad25af
@ -46,8 +46,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.77.6",
|
"@cornerstonejs/adapters": "^1.86.0",
|
||||||
"@cornerstonejs/core": "^1.77.6",
|
"@cornerstonejs/core": "^1.86.0",
|
||||||
"@kitware/vtk.js": "30.4.1",
|
"@kitware/vtk.js": "30.4.1",
|
||||||
"react-color": "^2.19.3"
|
"react-color": "^2.19.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,8 +46,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.81.6",
|
"@cornerstonejs/adapters": "^1.86.0",
|
||||||
"@cornerstonejs/core": "^1.81.6",
|
"@cornerstonejs/core": "^1.86.0",
|
||||||
"@kitware/vtk.js": "30.4.1",
|
"@kitware/vtk.js": "30.4.1",
|
||||||
"react-color": "^2.19.3"
|
"react-color": "^2.19.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,9 +46,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.82.4",
|
"@cornerstonejs/adapters": "^1.86.0",
|
||||||
"@cornerstonejs/core": "^1.82.4",
|
"@cornerstonejs/core": "^1.86.0",
|
||||||
"@cornerstonejs/tools": "^1.82.4",
|
"@cornerstonejs/tools": "^1.86.0",
|
||||||
"classnames": "^2.3.2"
|
"classnames": "^2.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CodeNameCodeSequenceValues from '../constants/CodeNameCodeSequenceValues';
|
import { CodeNameCodeSequenceValues } from '../enums';
|
||||||
import formatContentItemValue from '../utils/formatContentItem';
|
import formatContentItemValue from '../utils/formatContentItem';
|
||||||
|
|
||||||
const EMPTY_TAG_VALUE = '[empty]';
|
const EMPTY_TAG_VALUE = '[empty]';
|
||||||
|
|||||||
@ -135,13 +135,17 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
newMeasurementSelected,
|
newMeasurementSelected,
|
||||||
displaySetService
|
displaySetService
|
||||||
).then(({ referencedDisplaySet, referencedDisplaySetMetadata }) => {
|
).then(({ referencedDisplaySet, referencedDisplaySetMetadata }) => {
|
||||||
|
if (!referencedDisplaySet || !referencedDisplaySetMetadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setMeasurementSelected(newMeasurementSelected);
|
setMeasurementSelected(newMeasurementSelected);
|
||||||
setActiveImageDisplaySetData(referencedDisplaySet);
|
setActiveImageDisplaySetData(referencedDisplaySet);
|
||||||
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);
|
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
referencedDisplaySet.displaySetInstanceUID ===
|
referencedDisplaySet.displaySetInstanceUID ===
|
||||||
activeImageDisplaySetData?.displaySetInstanceUID
|
activeImageDisplaySetData.displaySetInstanceUID
|
||||||
) {
|
) {
|
||||||
const { measurements } = srDisplaySet;
|
const { measurements } = srDisplaySet;
|
||||||
|
|
||||||
@ -151,6 +155,10 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
// new measurement
|
// new measurement
|
||||||
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
|
|
||||||
|
if (!csViewport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const imageIds = csViewport.getImageIds();
|
const imageIds = csViewport.getImageIds();
|
||||||
|
|
||||||
const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId);
|
const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId);
|
||||||
@ -395,6 +403,10 @@ async function _getViewportReferencedDisplaySetData(
|
|||||||
displaySet.keyImageDisplaySet = createReferencedImageDisplaySet(displaySetService, displaySet);
|
displaySet.keyImageDisplaySet = createReferencedImageDisplaySet(displaySetService, displaySet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!displaySetInstanceUID) {
|
||||||
|
return { referencedDisplaySetMetadata: null, referencedDisplaySet: null };
|
||||||
|
}
|
||||||
|
|
||||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
|
|
||||||
const image0 = referencedDisplaySet.images[0];
|
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 { utils, classes, DisplaySetService, Types } from '@ohif/core';
|
||||||
import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation';
|
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||||
import isRehydratable from './utils/isRehydratable';
|
|
||||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
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;
|
type InstanceMetadata = Types.InstanceMetadata;
|
||||||
|
|
||||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
/**
|
||||||
|
* TODO
|
||||||
const { ImageSet, MetadataProvider: metadataProvider } = classes;
|
* - [ ] Add SR thumbnail
|
||||||
|
* - [ ] Make viewport
|
||||||
// TODO ->
|
* - [ ] Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport
|
||||||
// Add SR thumbnail
|
*/
|
||||||
// Make viewport
|
|
||||||
// Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport.
|
|
||||||
|
|
||||||
const sopClassUids = [
|
const sopClassUids = [
|
||||||
'1.2.840.10008.5.1.4.1.1.88.11', // BASIC TEXT SR
|
sopClassDictionary.BasicTextSR,
|
||||||
'1.2.840.10008.5.1.4.1.1.88.22', // ENHANCED SR
|
sopClassDictionary.EnhancedSR,
|
||||||
'1.2.840.10008.5.1.4.1.1.88.33', // COMPREHENSIVE SR
|
sopClassDictionary.ComprehensiveSR,
|
||||||
'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
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|
||||||
|
|
||||||
const validateSameStudyUID = (uid: string, instances): void => {
|
const validateSameStudyUID = (uid: string, instances): void => {
|
||||||
instances.forEach(it => {
|
instances.forEach(it => {
|
||||||
if (it.StudyInstanceUID !== uid) {
|
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
|
* 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
|
* series, so that as SR's are saved, they append to the series, and the
|
||||||
@ -102,6 +95,8 @@ function _getDisplaySetsFromSeries(
|
|||||||
} = instance;
|
} = instance;
|
||||||
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
||||||
|
|
||||||
|
const is3DSR = SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
||||||
|
|
||||||
const isImagingMeasurementReport =
|
const isImagingMeasurementReport =
|
||||||
ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurementReport;
|
ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurementReport;
|
||||||
|
|
||||||
@ -114,7 +109,7 @@ function _getDisplaySetsFromSeries(
|
|||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
SOPClassHandlerId,
|
SOPClassHandlerId: is3DSR ? SOPClassHandlerId3D : SOPClassHandlerId,
|
||||||
SOPClassUID,
|
SOPClassUID,
|
||||||
instances,
|
instances,
|
||||||
referencedImages: null,
|
referencedImages: null,
|
||||||
@ -132,11 +127,21 @@ function _getDisplaySetsFromSeries(
|
|||||||
return [displaySet];
|
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 { displaySetService, measurementService } = servicesManager.services;
|
||||||
const dataSources = extensionManager.getDataSources();
|
const dataSources = extensionManager.getDataSources();
|
||||||
const dataSource = dataSources[0];
|
const dataSource = dataSources[0];
|
||||||
const { ContentSequence } = displaySet.instance;
|
const { ContentSequence } = srDisplaySet.instance;
|
||||||
|
|
||||||
async function retrieveBulkData(obj, parentObj = null, key = null) {
|
async function retrieveBulkData(obj, parentObj = null, key = null) {
|
||||||
for (const prop in obj) {
|
for (const prop in obj) {
|
||||||
@ -147,9 +152,9 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
|||||||
} else if (prop === 'BulkDataURI') {
|
} else if (prop === 'BulkDataURI') {
|
||||||
const value = await dataSource.retrieve.bulkDataURI({
|
const value = await dataSource.retrieve.bulkDataURI({
|
||||||
BulkDataURI: obj[prop],
|
BulkDataURI: obj[prop],
|
||||||
StudyInstanceUID: displaySet.instance.StudyInstanceUID,
|
StudyInstanceUID: srDisplaySet.instance.StudyInstanceUID,
|
||||||
SeriesInstanceUID: displaySet.instance.SeriesInstanceUID,
|
SeriesInstanceUID: srDisplaySet.instance.SeriesInstanceUID,
|
||||||
SOPInstanceUID: displaySet.instance.SOPInstanceUID,
|
SOPInstanceUID: srDisplaySet.instance.SOPInstanceUID,
|
||||||
});
|
});
|
||||||
if (parentObj && key) {
|
if (parentObj && key) {
|
||||||
parentObj[key] = new Float32Array(value);
|
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);
|
await retrieveBulkData(ContentSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displaySet.isImagingMeasurementReport) {
|
if (srDisplaySet.isImagingMeasurementReport) {
|
||||||
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
srDisplaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
||||||
displaySet.measurements = _getMeasurements(ContentSequence);
|
srDisplaySet.measurements = _getMeasurements(ContentSequence);
|
||||||
} else {
|
} else {
|
||||||
displaySet.referencedImages = [];
|
srDisplaySet.referencedImages = [];
|
||||||
displaySet.measurements = [];
|
srDisplaySet.measurements = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappings = measurementService.getSourceMappings(
|
const mappings = measurementService.getSourceMappings(
|
||||||
@ -175,28 +180,30 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
|||||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||||
);
|
);
|
||||||
|
|
||||||
displaySet.isHydrated = false;
|
srDisplaySet.isHydrated = false;
|
||||||
displaySet.isRehydratable = isRehydratable(displaySet, mappings);
|
srDisplaySet.isRehydratable = isRehydratable(srDisplaySet, mappings);
|
||||||
displaySet.isLoaded = true;
|
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 => {
|
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||||
_checkIfCanAddMeasurementsToDisplaySet(
|
_checkIfCanAddMeasurementsToDisplaySet(
|
||||||
displaySet,
|
srDisplaySet,
|
||||||
activeDisplaySet,
|
activeDisplaySet,
|
||||||
dataSource,
|
dataSource,
|
||||||
servicesManager
|
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 => {
|
displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => {
|
||||||
const { displaySetsAdded } = 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 => {
|
displaySetsAdded.forEach(newDisplaySet => {
|
||||||
_checkIfCanAddMeasurementsToDisplaySet(
|
_checkIfCanAddMeasurementsToDisplaySet(
|
||||||
displaySet,
|
srDisplaySet,
|
||||||
newDisplaySet,
|
newDisplaySet,
|
||||||
dataSource,
|
dataSource,
|
||||||
servicesManager
|
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(
|
function _checkIfCanAddMeasurementsToDisplaySet(
|
||||||
srDisplaySet,
|
srDisplaySet,
|
||||||
newDisplaySet,
|
newDisplaySet,
|
||||||
@ -212,108 +227,103 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
servicesManager: AppTypes.ServicesManager
|
servicesManager: AppTypes.ServicesManager
|
||||||
) {
|
) {
|
||||||
const { customizationService } = servicesManager.services;
|
const { customizationService } = servicesManager.services;
|
||||||
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
|
||||||
|
const unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||||
measurement => measurement.loaded === false
|
measurement => measurement.loaded === false
|
||||||
);
|
);
|
||||||
|
|
||||||
if (unloadedMeasurements.length === 0) {
|
if (
|
||||||
// All already loaded!
|
unloadedMeasurements.length === 0 ||
|
||||||
|
!(newDisplaySet instanceof ImageSet) ||
|
||||||
|
newDisplaySet.unsupported
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!newDisplaySet) instanceof ImageSet) {
|
// const { sopClassUids } = newDisplaySet;
|
||||||
// This also filters out _this_ displaySet, as it is not an ImageSet.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newDisplaySet.unsupported) {
|
const is3DSR = srDisplaySet.SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
||||||
unloadedMeasurements = unloadedMeasurements.filter(measurement =>
|
'onBeforeSRAddMeasurement'
|
||||||
measurement.coords.some(coord =>
|
)?.value;
|
||||||
sopClassUids.includes(coord.ReferencedSOPSequence.ReferencedSOPClassUID)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (unloadedMeasurements.length === 0) {
|
if (typeof onBeforeSRAddMeasurement === 'function') {
|
||||||
// New displaySet isn't the correct SOPClass, so can't contain the referenced images.
|
measurement = onBeforeSRAddMeasurement({
|
||||||
return;
|
measurement,
|
||||||
}
|
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
||||||
|
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
const referencedSOPSequence = measurement.coords[0].ReferencedSOPSequence;
|
||||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
if (!referencedSOPSequence) {
|
||||||
let measurement = unloadedMeasurements[j];
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
const { ReferencedSOPInstanceUID } = referencedSOPSequence;
|
||||||
'onBeforeSRAddMeasurement'
|
const frame = referencedSOPSequence.ReferencedFrameNumber || 1;
|
||||||
)?.value;
|
const key = `${ReferencedSOPInstanceUID}:${frame}`;
|
||||||
|
const imageId = imageIdMap.get(key);
|
||||||
|
|
||||||
if (typeof onBeforeSRAddMeasurement === 'function') {
|
if (
|
||||||
measurement = onBeforeSRAddMeasurement({
|
imageId &&
|
||||||
measurement,
|
_measurementReferencesSOPInstanceUID(measurement, ReferencedSOPInstanceUID, frame)
|
||||||
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
) {
|
||||||
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
addSRAnnotation(measurement, imageId, frame);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
// Update measurement properties
|
||||||
const frame =
|
measurement.loaded = true;
|
||||||
(measurement.coords[0].ReferencedSOPSequence &&
|
measurement.imageId = imageId;
|
||||||
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||||
1;
|
measurement.ReferencedSOPInstanceUID = ReferencedSOPInstanceUID;
|
||||||
|
measurement.frameNumber = frame;
|
||||||
|
|
||||||
/** Add DICOMSRDisplay annotation for the SR viewport (only) */
|
unloadedMeasurements.splice(j, 1);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
||||||
const { coords } = measurement;
|
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 =
|
const ReferencedFrameNumber =
|
||||||
(measurement.coords[0].ReferencedSOPSequence &&
|
(measurement.coords[0].ReferencedSOPSequence &&
|
||||||
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
||||||
@ -326,27 +336,46 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
|||||||
for (let j = 0; j < coords.length; j++) {
|
for (let j = 0; j < coords.length; j++) {
|
||||||
const coord = coords[j];
|
const coord = coords[j];
|
||||||
const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence;
|
const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence;
|
||||||
|
|
||||||
if (ReferencedSOPInstanceUID === SOPInstanceUID) {
|
if (ReferencedSOPInstanceUID === SOPInstanceUID) {
|
||||||
return true;
|
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 }) {
|
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||||
const getDisplaySetsFromSeries = instances => {
|
const getDisplaySetsFromSeries = instances => {
|
||||||
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: SOPClassHandlerName,
|
name: SOPClassHandlerName,
|
||||||
sopClassUids,
|
sopClassUids,
|
||||||
getDisplaySetsFromSeries,
|
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) {
|
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||||
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||||
item =>
|
item =>
|
||||||
@ -363,7 +392,6 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|||||||
|
|
||||||
const mergedContentSequencesByTrackingUniqueIdentifiers =
|
const mergedContentSequencesByTrackingUniqueIdentifiers =
|
||||||
_getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);
|
_getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);
|
||||||
|
|
||||||
const measurements = [];
|
const measurements = [];
|
||||||
|
|
||||||
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
||||||
@ -372,7 +400,6 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|||||||
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier];
|
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier];
|
||||||
|
|
||||||
const measurement = _processMeasurement(mergedContentSequence);
|
const measurement = _processMeasurement(mergedContentSequence);
|
||||||
|
|
||||||
if (measurement) {
|
if (measurement) {
|
||||||
measurements.push(measurement);
|
measurements.push(measurement);
|
||||||
}
|
}
|
||||||
@ -382,6 +409,12 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|||||||
return measurements;
|
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) {
|
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
||||||
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
||||||
|
|
||||||
@ -393,7 +426,6 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
|||||||
item.ConceptNameCodeSequence.CodeValue ===
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!TrackingUniqueIdentifierItem) {
|
if (!TrackingUniqueIdentifierItem) {
|
||||||
console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.');
|
console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.');
|
||||||
}
|
}
|
||||||
@ -422,6 +454,15 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
|||||||
return mergedContentSequencesByTrackingUniqueIdentifiers;
|
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) {
|
function _processMeasurement(mergedContentSequence) {
|
||||||
if (
|
if (
|
||||||
mergedContentSequence.some(
|
mergedContentSequence.some(
|
||||||
@ -434,11 +475,21 @@ function _processMeasurement(mergedContentSequence) {
|
|||||||
return _processNonGeometricallyDefinedMeasurement(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) {
|
function _processTID1410Measurement(mergedContentSequence) {
|
||||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
// 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
|
// 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');
|
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||||
|
|
||||||
@ -465,7 +516,6 @@ function _processTID1410Measurement(mergedContentSequence) {
|
|||||||
|
|
||||||
NUMContentItems.forEach(item => {
|
NUMContentItems.forEach(item => {
|
||||||
const { ConceptNameCodeSequence, MeasuredValueSequence } = item;
|
const { ConceptNameCodeSequence, MeasuredValueSequence } = item;
|
||||||
|
|
||||||
if (MeasuredValueSequence) {
|
if (MeasuredValueSequence) {
|
||||||
measurement.labels.push(
|
measurement.labels.push(
|
||||||
_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence)
|
_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;
|
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) {
|
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||||
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
||||||
|
|
||||||
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||||
|
|
||||||
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
||||||
@ -508,10 +575,10 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||||
finding.ConceptCodeSequence.CodingSchemeDesignator
|
finding.ConceptCodeSequence.CodingSchemeDesignator
|
||||||
) &&
|
) &&
|
||||||
finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
finding.ConceptCodeSequence.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||||
) {
|
) {
|
||||||
measurement.labels.push({
|
measurement.labels.push({
|
||||||
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
||||||
value: finding.ConceptCodeSequence.CodeMeaning,
|
value: finding.ConceptCodeSequence.CodeMeaning,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -523,12 +590,13 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||||
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
|
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
|
||||||
) &&
|
) &&
|
||||||
FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
FindingSite.ConceptCodeSequence.CodeValue ===
|
||||||
|
Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cornerstoneFreeTextFindingSite) {
|
if (cornerstoneFreeTextFindingSite) {
|
||||||
measurement.labels.push({
|
measurement.labels.push({
|
||||||
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
||||||
value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning,
|
value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -538,15 +606,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item;
|
const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item;
|
||||||
|
|
||||||
const { ValueType } = ContentSequence;
|
const { ValueType } = ContentSequence;
|
||||||
|
|
||||||
if (!ValueType === 'SCOORD') {
|
if (!ValueType === 'SCOORD') {
|
||||||
console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`);
|
console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
|
const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
|
||||||
|
|
||||||
if (coords) {
|
if (coords) {
|
||||||
measurement.coords.push(coords);
|
measurement.coords.push(coords);
|
||||||
}
|
}
|
||||||
@ -561,51 +626,47 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
return measurement;
|
return measurement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getCoordsFromSCOORDOrSCOORD3D(item) {
|
/**
|
||||||
const { ValueType, RelationshipType, GraphicType, GraphicData } = item;
|
* Extracts coordinates from a graphic item of type SCOORD or SCOORD3D.
|
||||||
|
* @param {object} graphicItem - The graphic item containing the coordinates.
|
||||||
if (
|
* @returns {object} - The extracted coordinates.
|
||||||
!(
|
*/
|
||||||
RelationshipType == RELATIONSHIP_TYPE.INFERRED_FROM ||
|
const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
||||||
RelationshipType == RELATIONSHIP_TYPE.CONTAINS
|
const { ValueType, GraphicType, GraphicData } = graphicItem;
|
||||||
)
|
|
||||||
) {
|
|
||||||
console.warn(
|
|
||||||
`Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM" or "CONTAINS"`
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const coords = { ValueType, GraphicType, GraphicData };
|
const coords = { ValueType, GraphicType, GraphicData };
|
||||||
|
coords.ReferencedSOPSequence = graphicItem.ContentSequence?.ReferencedSOPSequence;
|
||||||
// ContentSequence has length of 1 as RelationshipType === 'INFERRED FROM'
|
coords.ReferencedFrameOfReferenceSequence =
|
||||||
if (ValueType === 'SCOORD') {
|
graphicItem.ReferencedFrameOfReferenceUID ||
|
||||||
const { ReferencedSOPSequence } = item.ContentSequence;
|
graphicItem.ContentSequence?.ReferencedFrameOfReferenceSequence;
|
||||||
|
|
||||||
coords.ReferencedSOPSequence = ReferencedSOPSequence;
|
|
||||||
} else if (ValueType === 'SCOORD3D') {
|
|
||||||
const { ReferencedFrameOfReferenceSequence } = item.ContentSequence;
|
|
||||||
|
|
||||||
coords.ReferencedFrameOfReferenceSequence = ReferencedFrameOfReferenceSequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
return coords;
|
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) {
|
function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) {
|
||||||
const { CodeMeaning } = ConceptNameCodeSequence;
|
const { CodeMeaning } = ConceptNameCodeSequence;
|
||||||
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
||||||
const { CodeValue } = MeasurementUnitsCodeSequence;
|
const { CodeValue } = MeasurementUnitsCodeSequence;
|
||||||
|
|
||||||
const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : '';
|
const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: CodeMeaning,
|
label: CodeMeaning,
|
||||||
value: `${formatedNumericValue} ${CodeValue}`,
|
value: `${formatedNumericValue} ${CodeValue}`,
|
||||||
}; // E.g. Long Axis: 31.0 mm
|
}; // 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) {
|
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||||
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
||||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary
|
||||||
@ -618,6 +679,9 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
|||||||
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(
|
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(
|
||||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup
|
||||||
);
|
);
|
||||||
|
if (!ImageLibraryGroup) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const referencedImages = [];
|
const referencedImages = [];
|
||||||
|
|
||||||
@ -641,6 +705,15 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
|||||||
return referencedImages;
|
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) {
|
function _getSequenceAsArray(sequence) {
|
||||||
if (!sequence) {
|
if (!sequence) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@ -5,4 +5,7 @@ const id = packageJson.name;
|
|||||||
const SOPClassHandlerName = 'dicom-sr';
|
const SOPClassHandlerName = 'dicom-sr';
|
||||||
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
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 toolNames from './tools/toolNames';
|
||||||
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
||||||
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
||||||
|
import Enums from './enums';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport');
|
return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport');
|
||||||
@ -70,4 +71,4 @@ const dicomSRExtension = {
|
|||||||
export default dicomSRExtension;
|
export default dicomSRExtension;
|
||||||
|
|
||||||
// Put static exports here so they can be type checked
|
// 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,
|
LengthTool,
|
||||||
PlanarFreehandROITool,
|
PlanarFreehandROITool,
|
||||||
RectangleROITool,
|
RectangleROITool,
|
||||||
|
utilities as csToolsUtils,
|
||||||
} from '@cornerstonejs/tools';
|
} 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 DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
|
||||||
|
import SCOORD3DPointTool from './tools/SCOORD3DPointTool';
|
||||||
|
import SRSCOOR3DProbeMapper from './utils/SRSCOOR3DProbeMapper';
|
||||||
import addToolInstance from './utils/addToolInstance';
|
import addToolInstance from './utils/addToolInstance';
|
||||||
import { Types } from '@ohif/core';
|
|
||||||
import toolNames from './tools/toolNames';
|
import toolNames from './tools/toolNames';
|
||||||
|
|
||||||
|
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} configuration
|
* @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.DICOMSRDisplay, DICOMSRDisplayTool);
|
||||||
addToolInstance(toolNames.SRLength, LengthTool);
|
addToolInstance(toolNames.SRLength, LengthTool);
|
||||||
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
||||||
@ -28,10 +40,26 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP
|
|||||||
addToolInstance(toolNames.SRAngle, AngleTool);
|
addToolInstance(toolNames.SRAngle, AngleTool);
|
||||||
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
|
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
|
||||||
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
|
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
|
||||||
|
addToolInstance(toolNames.SRSCOORD3DPoint, SCOORD3DPointTool);
|
||||||
|
|
||||||
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
|
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
|
||||||
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);
|
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
|
// Modify annotation tools to use dashed lines on SR
|
||||||
const dashedLine = {
|
const dashedLine = {
|
||||||
lineDash: '4,4',
|
lineDash: '4,4',
|
||||||
@ -49,4 +77,28 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP
|
|||||||
SRRectangleROI: dashedLine,
|
SRRectangleROI: dashedLine,
|
||||||
global: {},
|
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 }) {
|
export default function onModeEnter({ servicesManager }) {
|
||||||
const { displaySetService } = servicesManager.services;
|
const { displaySetService } = servicesManager.services;
|
||||||
const displaySetCache = displaySetService.getDisplaySetCache();
|
const displaySetCache = displaySetService.getDisplaySetCache();
|
||||||
|
|
||||||
const srDisplaySets = [...displaySetCache.values()].filter(
|
const srDisplaySets = [...displaySetCache.values()].filter(
|
||||||
ds => ds.SOPClassHandlerId === SOPClassHandlerId
|
ds => ds.SOPClassHandlerId === SOPClassHandlerId || ds.SOPClassHandlerId === SOPClassHandlerId3D
|
||||||
);
|
);
|
||||||
|
|
||||||
srDisplaySets.forEach(ds => {
|
srDisplaySets.forEach(ds => {
|
||||||
|
|||||||
@ -7,10 +7,11 @@ import {
|
|||||||
Types as cs3DToolsTypes,
|
Types as cs3DToolsTypes,
|
||||||
} from '@cornerstonejs/tools';
|
} from '@cornerstonejs/tools';
|
||||||
import { getTrackingUniqueIdentifiersForElement } from './modules/dicomSRModule';
|
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 {
|
export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||||
static toolName = 'DICOMSRDisplay';
|
static toolName = toolNames.DICOMSRDisplay;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
toolProps = {},
|
toolProps = {},
|
||||||
@ -112,19 +113,19 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
let canvasCoordinatesAdapter;
|
let canvasCoordinatesAdapter;
|
||||||
|
|
||||||
switch (GraphicType) {
|
switch (GraphicType) {
|
||||||
case SCOORD_TYPES.POINT:
|
case SCOORDTypes.POINT:
|
||||||
renderMethod = this.renderPoint;
|
renderMethod = this.renderPoint;
|
||||||
break;
|
break;
|
||||||
case SCOORD_TYPES.MULTIPOINT:
|
case SCOORDTypes.MULTIPOINT:
|
||||||
renderMethod = this.renderMultipoint;
|
renderMethod = this.renderMultipoint;
|
||||||
break;
|
break;
|
||||||
case SCOORD_TYPES.POLYLINE:
|
case SCOORDTypes.POLYLINE:
|
||||||
renderMethod = this.renderPolyLine;
|
renderMethod = this.renderPolyLine;
|
||||||
break;
|
break;
|
||||||
case SCOORD_TYPES.CIRCLE:
|
case SCOORDTypes.CIRCLE:
|
||||||
renderMethod = this.renderEllipse;
|
renderMethod = this.renderEllipse;
|
||||||
break;
|
break;
|
||||||
case SCOORD_TYPES.ELLIPSE:
|
case SCOORDTypes.ELLIPSE:
|
||||||
renderMethod = this.renderEllipse;
|
renderMethod = this.renderEllipse;
|
||||||
canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners;
|
canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners;
|
||||||
break;
|
break;
|
||||||
@ -335,7 +336,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { annotationUID, data = {} } = annotation;
|
const { annotationUID, data = {} } = annotation;
|
||||||
const { label } = data;
|
const { labels } = data;
|
||||||
const { color } = options;
|
const { color } = options;
|
||||||
|
|
||||||
let adaptedCanvasCoordinates = canvasCoordinates;
|
let adaptedCanvasCoordinates = canvasCoordinates;
|
||||||
@ -343,7 +344,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
if (typeof canvasCoordinatesAdapter === 'function') {
|
if (typeof canvasCoordinatesAdapter === 'function') {
|
||||||
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
||||||
}
|
}
|
||||||
const textLines = this._getTextBoxLinesFromLabels(label);
|
const textLines = this._getTextBoxLinesFromLabels(labels);
|
||||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||||
|
|
||||||
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
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 = {
|
const toolNames = {
|
||||||
DICOMSRDisplay: DICOMSRDisplayTool.toolName,
|
DICOMSRDisplay: 'DICOMSRDisplay',
|
||||||
SRLength: 'SRLength',
|
SRLength: 'SRLength',
|
||||||
SRBidirectional: 'SRBidirectional',
|
SRBidirectional: 'SRBidirectional',
|
||||||
SREllipticalROI: 'SREllipticalROI',
|
SREllipticalROI: 'SREllipticalROI',
|
||||||
@ -11,6 +9,7 @@ const toolNames = {
|
|||||||
SRCobbAngle: 'SRCobbAngle',
|
SRCobbAngle: 'SRCobbAngle',
|
||||||
SRRectangleROI: 'SRRectangleROI',
|
SRRectangleROI: 'SRRectangleROI',
|
||||||
SRPlanarFreehandROI: 'SRPlanarFreehandROI',
|
SRPlanarFreehandROI: 'SRPlanarFreehandROI',
|
||||||
|
SRSCOORD3DPoint: 'SRSCOORD3DPoint',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default toolNames;
|
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';
|
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 {
|
class InstanceClass extends toolClass {
|
||||||
static toolName = name;
|
static toolName = name;
|
||||||
|
constructor(toolProps, defaultToolProps) {
|
||||||
|
toolProps.configuration = toolProps.configuration
|
||||||
|
? { ...toolProps.configuration, ...configuration }
|
||||||
|
: configuration;
|
||||||
|
super(toolProps, defaultToolProps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
addTool(InstanceClass);
|
addTool(InstanceClass);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,6 +61,11 @@ const createReferencedImageDisplaySet = (displaySetService, displaySet) => {
|
|||||||
|
|
||||||
const imageSet = new ImageSet(instances);
|
const imageSet = new ImageSet(instances);
|
||||||
const instance = instances[0];
|
const instance = instances[0];
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
imageSet.setAttributes({
|
imageSet.setAttributes({
|
||||||
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
||||||
SeriesDate: instance.SeriesDate,
|
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
|
* 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.
|
* as dcmjs does not depeend on OHIF/the measurementService, it just produces data for cornestoneTools.
|
||||||
@ -9,13 +13,15 @@
|
|||||||
export default function getLabelFromDCMJSImportedToolData(toolData) {
|
export default function getLabelFromDCMJSImportedToolData(toolData) {
|
||||||
const { findingSites = [], finding } = 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) {
|
if (freeTextLabel) {
|
||||||
return freeTextLabel.CodeMeaning;
|
return freeTextLabel.CodeMeaning;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finding && finding.CodeValue === 'CORNERSTONEFREETEXT') {
|
if (finding && finding.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT) {
|
||||||
return finding.CodeMeaning;
|
return finding.CodeMeaning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,106 +1,49 @@
|
|||||||
import { vec3 } from 'gl-matrix';
|
import { vec3 } from 'gl-matrix';
|
||||||
import { Types, annotation } from '@cornerstonejs/tools';
|
|
||||||
import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core';
|
import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core';
|
||||||
|
|
||||||
import toolNames from '../tools/toolNames';
|
import { SCOORDTypes } from '../enums';
|
||||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
|
||||||
|
|
||||||
const EPSILON = 1e-4;
|
const EPSILON = 1e-4;
|
||||||
|
|
||||||
export default function addDICOMSRDisplayAnnotation(measurement, imageId, frameNumber) {
|
const getRenderableCoords = ({ GraphicData, ValueType, imageId }) => {
|
||||||
const toolName = toolNames.DICOMSRDisplay;
|
const renderableData = [];
|
||||||
|
if (ValueType === 'SCOORD3D') {
|
||||||
const measurementData = {
|
for (let i = 0; i < GraphicData.length; i += 3) {
|
||||||
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
renderableData.push([GraphicData[i], GraphicData[i + 1], GraphicData[i + 2]]);
|
||||||
renderableData: {},
|
|
||||||
labels: measurement.labels,
|
|
||||||
imageId,
|
|
||||||
};
|
|
||||||
|
|
||||||
measurement.coords.forEach(coord => {
|
|
||||||
const { GraphicType, GraphicData } = coord;
|
|
||||||
|
|
||||||
if (measurementData.renderableData[GraphicType] === undefined) {
|
|
||||||
measurementData.renderableData[GraphicType] = [];
|
|
||||||
}
|
}
|
||||||
|
} 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(
|
function getRenderableData({ GraphicType, GraphicData, ValueType, imageId }) {
|
||||||
_getRenderableData(GraphicType, GraphicData, imageId)
|
let renderableData = [];
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
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[];
|
|
||||||
|
|
||||||
switch (GraphicType) {
|
switch (GraphicType) {
|
||||||
case SCOORD_TYPES.POINT:
|
case SCOORDTypes.POINT:
|
||||||
case SCOORD_TYPES.MULTIPOINT:
|
case SCOORDTypes.MULTIPOINT:
|
||||||
case SCOORD_TYPES.POLYLINE:
|
case SCOORDTypes.POLYLINE: {
|
||||||
renderableData = [];
|
renderableData = getRenderableCoords({ GraphicData, ValueType, imageId });
|
||||||
|
|
||||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
||||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
|
||||||
GraphicData[i],
|
|
||||||
GraphicData[i + 1],
|
|
||||||
]);
|
|
||||||
|
|
||||||
renderableData.push(worldPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SCOORD_TYPES.CIRCLE: {
|
}
|
||||||
const pointsWorld = [];
|
case SCOORDTypes.CIRCLE: {
|
||||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
const pointsWorld: csTypes.Point3[] = getRenderableCoords({
|
||||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
GraphicData,
|
||||||
GraphicData[i],
|
ValueType,
|
||||||
GraphicData[i + 1],
|
imageId,
|
||||||
]);
|
});
|
||||||
|
|
||||||
pointsWorld.push(worldPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do not have an explicit draw circle svg helper in Cornerstone3D at
|
// 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
|
// this time, but we can use the ellipse svg helper to draw a circle, so
|
||||||
// here we reshape the data for that purpose.
|
// here we reshape the data for that purpose.
|
||||||
const center = pointsWorld[0];
|
const center = pointsWorld[0];
|
||||||
const onPerimeter = pointsWorld[1];
|
const onPerimeter = pointsWorld[1];
|
||||||
|
|
||||||
const radius = vec3.distance(center, onPerimeter);
|
const radius = vec3.distance(center, onPerimeter);
|
||||||
|
|
||||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||||
|
|
||||||
if (!imagePlaneModule) {
|
if (!imagePlaneModule) {
|
||||||
throw new Error('No imagePlaneModule found');
|
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)
|
// we need to get major/minor axis (which are both the same size major = minor)
|
||||||
|
|
||||||
// first axisStart
|
|
||||||
const firstAxisStart = vec3.create();
|
const firstAxisStart = vec3.create();
|
||||||
vec3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
|
vec3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
|
||||||
|
|
||||||
const firstAxisEnd = vec3.create();
|
const firstAxisEnd = vec3.create();
|
||||||
vec3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
|
vec3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
|
||||||
|
|
||||||
// second axisStart
|
|
||||||
const secondAxisStart = vec3.create();
|
const secondAxisStart = vec3.create();
|
||||||
vec3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
|
vec3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
|
||||||
|
|
||||||
@ -138,21 +79,16 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SCOORD_TYPES.ELLIPSE: {
|
case SCOORDTypes.ELLIPSE: {
|
||||||
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
||||||
// But Cornerstone3D points are ordered as top, bottom, left, right for the
|
// 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
|
// ellipse so we need to identify if the majorAxis is horizontal or vertical
|
||||||
// and then choose the correct points to use for the ellipse.
|
// and then choose the correct points to use for the ellipse.
|
||||||
|
const pointsWorld: csTypes.Point3[] = getRenderableCoords({
|
||||||
const pointsWorld: csTypes.Point3[] = [];
|
GraphicData,
|
||||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
ValueType,
|
||||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
imageId,
|
||||||
GraphicData[i],
|
});
|
||||||
GraphicData[i + 1],
|
|
||||||
]);
|
|
||||||
|
|
||||||
pointsWorld.push(worldPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
const majorAxisStart = vec3.fromValues(...pointsWorld[0]);
|
const majorAxisStart = vec3.fromValues(...pointsWorld[0]);
|
||||||
const majorAxisEnd = vec3.fromValues(...pointsWorld[1]);
|
const majorAxisEnd = vec3.fromValues(...pointsWorld[1]);
|
||||||
@ -202,3 +138,5 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
|||||||
|
|
||||||
return renderableData;
|
return renderableData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default getRenderableData;
|
||||||
@ -3,14 +3,12 @@ import OHIF, { DicomMetadataStore } from '@ohif/core';
|
|||||||
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
|
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
|
||||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||||
import { annotation as CsAnnotation } from '@cornerstonejs/tools';
|
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 { guid } = OHIF.utils;
|
||||||
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
|
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
|
||||||
|
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|
||||||
|
|
||||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||||
|
|
||||||
const convertCode = (codingValues, code) => {
|
const convertCode = (codingValues, code) => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const cornerstoneAdapters =
|
|||||||
adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
||||||
|
|
||||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
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`.
|
* Checks if the given `displaySet`can be rehydrated into the `measurementService`.
|
||||||
|
|||||||
@ -42,9 +42,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/core": "^1.82.4",
|
"@cornerstonejs/core": "^1.86.0",
|
||||||
"@cornerstonejs/streaming-image-volume-loader": "^1.82.4",
|
"@cornerstonejs/streaming-image-volume-loader": "^1.82.4",
|
||||||
"@cornerstonejs/tools": "^1.82.4",
|
"@cornerstonejs/tools": "^1.86.0",
|
||||||
"classnames": "^2.3.2"
|
"classnames": "^2.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,9 +36,9 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@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.2",
|
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||||
"@cornerstonejs/dicom-image-loader": "^1.82.4",
|
"@cornerstonejs/dicom-image-loader": "^1.86.0",
|
||||||
"@icr/polyseg-wasm": "^0.4.0",
|
"@icr/polyseg-wasm": "^0.4.0",
|
||||||
"@ohif/core": "3.9.0-beta.98",
|
"@ohif/core": "3.9.0-beta.98",
|
||||||
"@ohif/ui": "3.9.0-beta.98",
|
"@ohif/ui": "3.9.0-beta.98",
|
||||||
@ -55,10 +55,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.82.4",
|
"@cornerstonejs/adapters": "^1.86.0",
|
||||||
"@cornerstonejs/core": "^1.82.4",
|
"@cornerstonejs/core": "^1.86.0",
|
||||||
"@cornerstonejs/streaming-image-volume-loader": "^1.82.4",
|
"@cornerstonejs/streaming-image-volume-loader": "^1.82.4",
|
||||||
"@cornerstonejs/tools": "^1.82.4",
|
"@cornerstonejs/tools": "^1.86.0",
|
||||||
"@icr/polyseg-wasm": "^0.4.0",
|
"@icr/polyseg-wasm": "^0.4.0",
|
||||||
"@kitware/vtk.js": "30.4.1",
|
"@kitware/vtk.js": "30.4.1",
|
||||||
"html2canvas": "^1.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
|
// to set the initial state of the viewport's first image to render
|
||||||
initialImageIdOrIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
initialImageIdOrIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OHIFCornerstoneViewport;
|
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 * as csStreamingImageVolumeLoader from '@cornerstonejs/streaming-image-volume-loader';
|
||||||
import { Enums as cs3DToolsEnums } from '@cornerstonejs/tools';
|
import { Enums as cs3DToolsEnums } from '@cornerstonejs/tools';
|
||||||
import { Types } from '@ohif/core';
|
import { Types } from '@ohif/core';
|
||||||
|
import Enums from './enums';
|
||||||
|
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import getCustomizationModule from './getCustomizationModule';
|
import getCustomizationModule from './getCustomizationModule';
|
||||||
@ -234,5 +235,6 @@ export {
|
|||||||
ImageOverlayViewerTool,
|
ImageOverlayViewerTool,
|
||||||
getSOPInstanceAttributes,
|
getSOPInstanceAttributes,
|
||||||
dicomLoaderService,
|
dicomLoaderService,
|
||||||
|
Enums,
|
||||||
};
|
};
|
||||||
export default cornerstoneExtension;
|
export default cornerstoneExtension;
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
import { eventTarget } from '@cornerstonejs/core';
|
import { eventTarget } from '@cornerstonejs/core';
|
||||||
import { Enums, annotation } from '@cornerstonejs/tools';
|
import { Enums, annotation } from '@cornerstonejs/tools';
|
||||||
import { DicomMetadataStore } from '@ohif/core';
|
import { DicomMetadataStore } from '@ohif/core';
|
||||||
|
|
||||||
|
import * as CSExtensionEnums from './enums';
|
||||||
import { toolNames } from './initCornerstoneTools';
|
import { toolNames } from './initCornerstoneTools';
|
||||||
import { onCompletedCalibrationLine } from './tools/CalibrationLineTool';
|
import { onCompletedCalibrationLine } from './tools/CalibrationLineTool';
|
||||||
|
|
||||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||||
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||||
|
|
||||||
|
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||||
const { removeAnnotation } = annotation.state;
|
const { removeAnnotation } = annotation.state;
|
||||||
|
|
||||||
const csToolsEvents = Enums.Events;
|
const csToolsEvents = Enums.Events;
|
||||||
|
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|
||||||
|
|
||||||
const initMeasurementService = (
|
const initMeasurementService = (
|
||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
@ -189,11 +187,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
|||||||
cornerstoneViewportService,
|
cornerstoneViewportService,
|
||||||
customizationService
|
customizationService
|
||||||
);
|
);
|
||||||
connectMeasurementServiceToTools(
|
connectMeasurementServiceToTools(measurementService, cornerstoneViewportService);
|
||||||
measurementService,
|
|
||||||
cornerstoneViewportService,
|
|
||||||
csTools3DVer1MeasurementSource
|
|
||||||
);
|
|
||||||
const { annotationToMeasurement, remove } = csTools3DVer1MeasurementSource;
|
const { annotationToMeasurement, remove } = csTools3DVer1MeasurementSource;
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -210,7 +204,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
|||||||
onCompletedCalibrationLine(servicesManager, csToolsEvent)
|
onCompletedCalibrationLine(servicesManager, csToolsEvent)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
console.log('calibration applied');
|
console.log('Calibration applied.');
|
||||||
},
|
},
|
||||||
() => true
|
() => true
|
||||||
)
|
)
|
||||||
@ -230,7 +224,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
|||||||
annotationToMeasurement(toolName, annotationAddedEventDetail);
|
annotationToMeasurement(toolName, annotationAddedEventDetail);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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);
|
console.warn('Failed to update measurement:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectMeasurement(csToolsEvent) {
|
function selectMeasurement(csToolsEvent) {
|
||||||
try {
|
try {
|
||||||
const annotationSelectionEventDetail = csToolsEvent.detail;
|
const annotationSelectionEventDetail = csToolsEvent.detail;
|
||||||
@ -276,7 +271,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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) {
|
function removeMeasurement(csToolsEvent) {
|
||||||
try {
|
try {
|
||||||
try {
|
const annotationRemovedEventDetail = csToolsEvent.detail;
|
||||||
const annotationRemovedEventDetail = csToolsEvent.detail;
|
const {
|
||||||
const {
|
annotation: { annotationUID },
|
||||||
annotation: { annotationUID },
|
} = annotationRemovedEventDetail;
|
||||||
} = annotationRemovedEventDetail;
|
const measurement = measurementService.getMeasurement(annotationUID);
|
||||||
|
if (measurement) {
|
||||||
const measurement = measurementService.getMeasurement(annotationUID);
|
remove(annotationUID, annotationRemovedEventDetail);
|
||||||
|
|
||||||
if (measurement) {
|
|
||||||
console.log('~~ removeEvt', csToolsEvent);
|
|
||||||
remove(annotationUID, annotationRemovedEventDetail);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to update measurement:', error);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to remove measurement:', error);
|
console.warn('Failed to remove measurement:', error);
|
||||||
@ -325,19 +313,10 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
|||||||
return csTools3DVer1MeasurementSource;
|
return csTools3DVer1MeasurementSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectMeasurementServiceToTools = (
|
const connectMeasurementServiceToTools = (measurementService, cornerstoneViewportService) => {
|
||||||
measurementService,
|
|
||||||
cornerstoneViewportService,
|
|
||||||
measurementSource
|
|
||||||
) => {
|
|
||||||
const { MEASUREMENT_REMOVED, MEASUREMENTS_CLEARED, MEASUREMENT_UPDATED, RAW_MEASUREMENT_ADDED } =
|
const { MEASUREMENT_REMOVED, MEASUREMENTS_CLEARED, MEASUREMENT_UPDATED, RAW_MEASUREMENT_ADDED } =
|
||||||
measurementService.EVENTS;
|
measurementService.EVENTS;
|
||||||
|
|
||||||
const csTools3DVer1MeasurementSource = measurementService.getSource(
|
|
||||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
|
||||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
|
||||||
);
|
|
||||||
|
|
||||||
measurementService.subscribe(MEASUREMENTS_CLEARED, ({ measurements }) => {
|
measurementService.subscribe(MEASUREMENTS_CLEARED, ({ measurements }) => {
|
||||||
if (!Object.keys(measurements).length) {
|
if (!Object.keys(measurements).length) {
|
||||||
return;
|
return;
|
||||||
@ -348,7 +327,6 @@ const connectMeasurementServiceToTools = (
|
|||||||
if (source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) {
|
if (source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAnnotation(uid);
|
removeAnnotation(uid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -188,6 +188,12 @@ class CornerstoneCacheService {
|
|||||||
|
|
||||||
const displaySet = nonOverlayDisplaySets[0];
|
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);
|
let stackImageIds = this.stackImageIds.get(displaySet.displaySetInstanceUID);
|
||||||
|
|
||||||
if (!stackImageIds) {
|
if (!stackImageIds) {
|
||||||
|
|||||||
@ -404,6 +404,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
): void {
|
): void {
|
||||||
const renderingEngine = this.getRenderingEngine();
|
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
|
// This is the old viewportInfo, which may have old options but we might be
|
||||||
// using its viewport (same viewportId as the new viewportInfo)
|
// using its viewport (same viewportId as the new viewportInfo)
|
||||||
const viewportInfo = this.viewportsById.get(viewportId);
|
const viewportInfo = this.viewportsById.get(viewportId);
|
||||||
@ -641,6 +649,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
|
|
||||||
this._handleOverlays(viewport);
|
this._handleOverlays(viewport);
|
||||||
|
|
||||||
|
if (!imageIds?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
||||||
viewport.setProperties({ ...properties });
|
viewport.setProperties({ ...properties });
|
||||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||||
|
|||||||
@ -19,11 +19,11 @@ const Probe = {
|
|||||||
getValueTypeFromToolType,
|
getValueTypeFromToolType,
|
||||||
customizationService
|
customizationService
|
||||||
) => {
|
) => {
|
||||||
const { annotation, viewportId } = csToolsEventDetail;
|
const { annotation } = csToolsEventDetail;
|
||||||
const { metadata, data, annotationUID } = annotation;
|
const { metadata, data, annotationUID } = annotation;
|
||||||
|
|
||||||
if (!metadata || !data) {
|
if (!metadata || !data) {
|
||||||
console.warn('Length tool: Missing metadata or data');
|
console.warn('Probe tool: Missing metadata or data');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export default [
|
const supportedTools = [
|
||||||
'Length',
|
'Length',
|
||||||
'EllipticalROI',
|
'EllipticalROI',
|
||||||
'CircleROI',
|
'CircleROI',
|
||||||
@ -11,6 +11,8 @@ export default [
|
|||||||
'PlanarFreehandROI',
|
'PlanarFreehandROI',
|
||||||
'SplineROI',
|
'SplineROI',
|
||||||
'LivewireContour',
|
'LivewireContour',
|
||||||
'Probe',
|
|
||||||
'UltrasoundDirectionalTool',
|
'UltrasoundDirectionalTool',
|
||||||
|
'SCOORD3DPoint',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export default supportedTools;
|
||||||
|
|||||||
@ -94,7 +94,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
EllipticalROI: {
|
EllipticalROI: {
|
||||||
toAnnotation: EllipticalROI.toAnnotation,
|
toAnnotation: EllipticalROI.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -111,7 +110,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
CircleROI: {
|
CircleROI: {
|
||||||
toAnnotation: CircleROI.toAnnotation,
|
toAnnotation: CircleROI.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -128,7 +126,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
RectangleROI: {
|
RectangleROI: {
|
||||||
toAnnotation: RectangleROI.toAnnotation,
|
toAnnotation: RectangleROI.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -145,7 +142,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
PlanarFreehandROI: {
|
PlanarFreehandROI: {
|
||||||
toAnnotation: PlanarFreehandROI.toAnnotation,
|
toAnnotation: PlanarFreehandROI.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -162,7 +158,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
SplineROI: {
|
SplineROI: {
|
||||||
toAnnotation: SplineROI.toAnnotation,
|
toAnnotation: SplineROI.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -179,7 +174,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
LivewireContour: {
|
LivewireContour: {
|
||||||
toAnnotation: LivewireContour.toAnnotation,
|
toAnnotation: LivewireContour.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -196,7 +190,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
ArrowAnnotate: {
|
ArrowAnnotate: {
|
||||||
toAnnotation: ArrowAnnotate.toAnnotation,
|
toAnnotation: ArrowAnnotate.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -214,7 +207,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
Probe: {
|
Probe: {
|
||||||
toAnnotation: Probe.toAnnotation,
|
toAnnotation: Probe.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -232,7 +224,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
CobbAngle: {
|
CobbAngle: {
|
||||||
toAnnotation: CobbAngle.toAnnotation,
|
toAnnotation: CobbAngle.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
@ -249,7 +240,6 @@ const measurementServiceMappingsFactory = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
Angle: {
|
Angle: {
|
||||||
toAnnotation: Angle.toAnnotation,
|
toAnnotation: Angle.toAnnotation,
|
||||||
toMeasurement: csToolsAnnotation =>
|
toMeasurement: csToolsAnnotation =>
|
||||||
|
|||||||
@ -45,7 +45,7 @@
|
|||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||||
"colormap": "^2.3",
|
"colormap": "^2.3",
|
||||||
"mathjs": "^12.4.2"
|
"mathjs": "^12.4.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,8 +32,8 @@
|
|||||||
"start": "yarn run dev"
|
"start": "yarn run dev"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@cornerstonejs/core": "^1.82.0",
|
"@cornerstonejs/core": "^1.86.0",
|
||||||
"@cornerstonejs/tools": "^1.82.0",
|
"@cornerstonejs/tools": "^1.86.0",
|
||||||
"@ohif/core": "3.9.0-beta.98",
|
"@ohif/core": "3.9.0-beta.98",
|
||||||
"@ohif/extension-cornerstone-dicom-sr": "3.9.0-beta.98",
|
"@ohif/extension-cornerstone-dicom-sr": "3.9.0-beta.98",
|
||||||
"@ohif/ui": "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 { DicomMetadataStore, utils } from '@ohif/core';
|
||||||
import { useDebounce } from '@hooks';
|
import { useDebounce } from '@hooks';
|
||||||
import { useAppConfig } from '@state';
|
import { useAppConfig } from '@state';
|
||||||
import { useTrackedMeasurements } from '../../getContextModule';
|
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Separator } from '@ohif/ui-next';
|
import { Separator } from '@ohif/ui-next';
|
||||||
|
|
||||||
|
import { useTrackedMeasurements } from '../../getContextModule';
|
||||||
|
|
||||||
const { downloadCSVReport } = utils;
|
const { downloadCSVReport } = utils;
|
||||||
const { formatDate } = utils;
|
const { formatDate } = utils;
|
||||||
|
|
||||||
@ -43,49 +44,50 @@ function PanelMeasurementTableTracking({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const measurements = measurementService.getMeasurements();
|
const measurements = measurementService.getMeasurements();
|
||||||
const filteredMeasurements = measurements.filter(
|
const mappedMeasurements = measurements.map(m =>
|
||||||
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
|
_mapMeasurementToDisplay(m, displaySetService)
|
||||||
);
|
|
||||||
|
|
||||||
const mappedMeasurements = filteredMeasurements.map(m =>
|
|
||||||
_mapMeasurementToDisplay(m, measurementService.VALUE_TYPES, displaySetService)
|
|
||||||
);
|
);
|
||||||
setDisplayMeasurements(mappedMeasurements);
|
setDisplayMeasurements(mappedMeasurements);
|
||||||
// eslint-ignore-next-line
|
// eslint-ignore-next-line
|
||||||
}, [measurementService, trackedStudy, trackedSeries, debouncedMeasurementChangeTimestamp]);
|
}, [
|
||||||
|
measurementService,
|
||||||
const updateDisplayStudySummary = async () => {
|
displaySetService,
|
||||||
if (trackedMeasurements.matches('tracking')) {
|
trackedStudy,
|
||||||
const StudyInstanceUID = trackedStudy;
|
trackedSeries,
|
||||||
const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID);
|
debouncedMeasurementChangeTimestamp,
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ~~ DisplayStudySummary
|
// ~~ DisplayStudySummary
|
||||||
useEffect(() => {
|
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();
|
updateDisplayStudySummary();
|
||||||
}, [displayStudySummary.key, trackedMeasurements, trackedStudy, updateDisplayStudySummary]);
|
}, [displayStudySummary.key, trackedSeries, trackedMeasurements, trackedStudy]);
|
||||||
|
|
||||||
// TODO: Better way to consolidated, debounce, check on change?
|
// TODO: Better way to consolidated, debounce, check on change?
|
||||||
// Are we exposing the right API for measurementService?
|
// Are we exposing the right API for measurementService?
|
||||||
@ -125,13 +127,11 @@ function PanelMeasurementTableTracking({
|
|||||||
const trackedMeasurements = measurements.filter(
|
const trackedMeasurements = measurements.filter(
|
||||||
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
|
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
|
||||||
);
|
);
|
||||||
|
downloadCSVReport(trackedMeasurements);
|
||||||
downloadCSVReport(trackedMeasurements, measurementService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jumpToImage = ({ uid, isActive }) => {
|
const jumpToImage = ({ uid, isActive }) => {
|
||||||
measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid);
|
measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid);
|
||||||
|
|
||||||
onMeasurementItemClickHandler({ uid, isActive });
|
onMeasurementItemClickHandler({ uid, isActive });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,7 +160,6 @@ function PanelMeasurementTableTracking({
|
|||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
const measurements = [...displayMeasurements];
|
const measurements = [...displayMeasurements];
|
||||||
const measurement = measurements.find(m => m.uid === uid);
|
const measurement = measurements.find(m => m.uid === uid);
|
||||||
|
|
||||||
measurements.forEach(m => (m.isActive = m.uid !== uid ? false : true));
|
measurements.forEach(m => (m.isActive = m.uid !== uid ? false : true));
|
||||||
measurement.isActive = true;
|
measurement.isActive = true;
|
||||||
setDisplayMeasurements(measurements);
|
setDisplayMeasurements(measurements);
|
||||||
@ -168,14 +167,25 @@ function PanelMeasurementTableTracking({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const displayMeasurementsWithoutFindings = displayMeasurements.filter(
|
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(
|
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 =
|
const disabled =
|
||||||
additionalFindings.length === 0 &&
|
additionalFindings.length === 0 &&
|
||||||
displayMeasurementsWithoutFindings.length === 0 &&
|
displayMeasurementsWithoutFindings.length === 0 &&
|
||||||
@ -278,23 +288,11 @@ PanelMeasurementTableTracking.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// TODO: This could be a measurementService mapper
|
// TODO: This could be a measurementService mapper
|
||||||
function _mapMeasurementToDisplay(measurement, types, displaySetService) {
|
function _mapMeasurementToDisplay(measurement, displaySetService) {
|
||||||
const { referenceStudyUID, referenceSeriesUID, SOPInstanceUID } = measurement;
|
const { referenceSeriesUID } = measurement;
|
||||||
|
|
||||||
// TODO: We don't deal with multiframe well yet, would need to update
|
// TODO: We don't deal with multiframe well yet, would need to update
|
||||||
// This in OHIF-312 when we add FrameIndex to measurements.
|
// 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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
displayText: baseDisplayText,
|
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';
|
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||||
|
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} servicesManager
|
* @param {Object} servicesManager
|
||||||
@ -17,11 +22,12 @@ export default function init({ servicesManager }) {
|
|||||||
addTool(RectangleROIStartEndThresholdTool);
|
addTool(RectangleROIStartEndThresholdTool);
|
||||||
addTool(CircleROIStartEndThresholdTool);
|
addTool(CircleROIStartEndThresholdTool);
|
||||||
|
|
||||||
const { RectangleROIStartEndThreshold, CircleROIStartEndThreshold } = measurementServiceMappingsFactory(
|
const { RectangleROIStartEndThreshold, CircleROIStartEndThreshold } =
|
||||||
measurementService,
|
measurementServiceMappingsFactory(
|
||||||
displaySetService,
|
measurementService,
|
||||||
cornerstoneViewportService
|
displaySetService,
|
||||||
);
|
cornerstoneViewportService
|
||||||
|
);
|
||||||
|
|
||||||
const csTools3DVer1MeasurementSource = measurementService.getSource(
|
const csTools3DVer1MeasurementSource = measurementService.getSource(
|
||||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
import { getSOPInstanceAttributes } from '@ohif/extension-cornerstone';
|
||||||
|
|
||||||
const CircleROIStartEndThreshold = {
|
const CircleROIStartEndThreshold = {
|
||||||
toAnnotation: (measurement, definition) => { },
|
toAnnotation: (measurement, definition) => {},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps cornerstone annotation event data to measurement service format.
|
* Maps cornerstone annotation event data to measurement service format.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
import { getSOPInstanceAttributes } from '@ohif/extension-cornerstone';
|
||||||
|
|
||||||
const RectangleROIStartEndThreshold = {
|
const RectangleROIStartEndThreshold = {
|
||||||
toAnnotation: (measurement, definition) => {},
|
toAnnotation: (measurement, definition) => {},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold';
|
import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold';
|
||||||
import CircleROIStartEndThreshold from './CircleROIStartEndThreshold'
|
import CircleROIStartEndThreshold from './CircleROIStartEndThreshold';
|
||||||
|
|
||||||
const measurementServiceMappingsFactory = (
|
const measurementServiceMappingsFactory = (
|
||||||
measurementService,
|
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,
|
namespace: dicomsr.viewport,
|
||||||
displaySetsToDisplay: [dicomsr.sopClassHandler],
|
displaySetsToDisplay: [dicomsr.sopClassHandler],
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// namespace: dicomvideo.viewport,
|
namespace: dicomvideo.viewport,
|
||||||
// displaySetsToDisplay: [dicomvideo.sopClassHandler],
|
displaySetsToDisplay: [dicomvideo.sopClassHandler],
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
namespace: dicompdf.viewport,
|
namespace: dicompdf.viewport,
|
||||||
displaySetsToDisplay: [dicompdf.sopClassHandler],
|
displaySetsToDisplay: [dicompdf.sopClassHandler],
|
||||||
|
|||||||
@ -26,6 +26,7 @@ const tracked = {
|
|||||||
|
|
||||||
const dicomsr = {
|
const dicomsr = {
|
||||||
sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr',
|
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',
|
viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,6 +200,7 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
displaySetsToDisplay: [
|
displaySetsToDisplay: [
|
||||||
ohif.sopClassHandler,
|
ohif.sopClassHandler,
|
||||||
dicomvideo.sopClassHandler,
|
dicomvideo.sopClassHandler,
|
||||||
|
dicomsr.sopClassHandler3D,
|
||||||
ohif.wsiSopClassHandler,
|
ohif.wsiSopClassHandler,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -206,10 +208,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
namespace: dicomsr.viewport,
|
namespace: dicomsr.viewport,
|
||||||
displaySetsToDisplay: [dicomsr.sopClassHandler],
|
displaySetsToDisplay: [dicomsr.sopClassHandler],
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// namespace: dicomvideo.viewport,
|
|
||||||
// displaySetsToDisplay: [dicomvideo.sopClassHandler],
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
namespace: dicompdf.viewport,
|
namespace: dicompdf.viewport,
|
||||||
displaySetsToDisplay: [dicompdf.sopClassHandler],
|
displaySetsToDisplay: [dicompdf.sopClassHandler],
|
||||||
@ -246,6 +244,7 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
ohif.sopClassHandler,
|
ohif.sopClassHandler,
|
||||||
ohif.wsiSopClassHandler,
|
ohif.wsiSopClassHandler,
|
||||||
dicompdf.sopClassHandler,
|
dicompdf.sopClassHandler,
|
||||||
|
dicomsr.sopClassHandler3D,
|
||||||
dicomsr.sopClassHandler,
|
dicomsr.sopClassHandler,
|
||||||
dicomRT.sopClassHandler,
|
dicomRT.sopClassHandler,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { toolNames as SRToolNames } from '@ohif/extension-cornerstone-dicom-sr';
|
||||||
|
|
||||||
const colours = {
|
const colours = {
|
||||||
'viewport-0': 'rgb(200, 0, 0)',
|
'viewport-0': 'rgb(200, 0, 0)',
|
||||||
'viewport-1': 'rgb(200, 200, 0)',
|
'viewport-1': 'rgb(200, 200, 0)',
|
||||||
@ -89,7 +91,13 @@ function initDefaultToolGroup(
|
|||||||
{ toolName: toolNames.LivewireContour },
|
{ toolName: toolNames.LivewireContour },
|
||||||
{ toolName: toolNames.WindowLevelRegion },
|
{ toolName: toolNames.WindowLevelRegion },
|
||||||
],
|
],
|
||||||
enabled: [{ toolName: toolNames.ImageOverlayViewer }, { toolName: toolNames.ReferenceLines }],
|
enabled: [
|
||||||
|
{ toolName: toolNames.ImageOverlayViewer },
|
||||||
|
{ toolName: toolNames.ReferenceLines },
|
||||||
|
{
|
||||||
|
toolName: SRToolNames.SRSCOORD3DPoint,
|
||||||
|
},
|
||||||
|
],
|
||||||
disabled: [
|
disabled: [
|
||||||
{
|
{
|
||||||
toolName: toolNames.AdvancedMagnify,
|
toolName: toolNames.AdvancedMagnify,
|
||||||
@ -165,7 +173,6 @@ function initSRToolGroup(extensionManager, toolGroupService) {
|
|||||||
enabled: [
|
enabled: [
|
||||||
{
|
{
|
||||||
toolName: SRToolNames.DICOMSRDisplay,
|
toolName: SRToolNames.DICOMSRDisplay,
|
||||||
bindings: [],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// disabled
|
// disabled
|
||||||
|
|||||||
@ -51,9 +51,9 @@
|
|||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@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/codec-openjph": "^2.4.5",
|
||||||
"@cornerstonejs/dicom-image-loader": "^1.77.6",
|
"@cornerstonejs/dicom-image-loader": "^1.86.0",
|
||||||
"@emotion/serialize": "^1.1.3",
|
"@emotion/serialize": "^1.1.3",
|
||||||
"@ohif/core": "3.9.0-beta.98",
|
"@ohif/core": "3.9.0-beta.98",
|
||||||
"@ohif/extension-cornerstone": "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 { useResizeDetector } from 'react-resize-detector';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Types, MeasurementService } from '@ohif/core';
|
import { Types, MeasurementService } from '@ohif/core';
|
||||||
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
||||||
import EmptyViewport from './EmptyViewport';
|
import EmptyViewport from './EmptyViewport';
|
||||||
@ -24,8 +23,13 @@ function ViewerViewportGrid(props: withAppTypes) {
|
|||||||
});
|
});
|
||||||
const layoutHash = useRef(null);
|
const layoutHash = useRef(null);
|
||||||
|
|
||||||
const { displaySetService, measurementService, hangingProtocolService, uiNotificationService } =
|
const {
|
||||||
servicesManager.services;
|
displaySetService,
|
||||||
|
measurementService,
|
||||||
|
hangingProtocolService,
|
||||||
|
cornerstoneViewportService,
|
||||||
|
uiNotificationService,
|
||||||
|
} = servicesManager.services;
|
||||||
|
|
||||||
const generateLayoutHash = () => `${numCols}-${numRows}`;
|
const generateLayoutHash = () => `${numCols}-${numRows}`;
|
||||||
|
|
||||||
@ -108,6 +112,10 @@ function ViewerViewportGrid(props: withAppTypes) {
|
|||||||
|
|
||||||
const _getUpdatedViewports = useCallback(
|
const _getUpdatedViewports = useCallback(
|
||||||
(viewportId, displaySetInstanceUID) => {
|
(viewportId, displaySetInstanceUID) => {
|
||||||
|
if (!displaySetInstanceUID) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
let updatedViewports = [];
|
let updatedViewports = [];
|
||||||
try {
|
try {
|
||||||
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||||
@ -175,6 +183,7 @@ function ViewerViewportGrid(props: withAppTypes) {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbitrarily assign the viewport to element 0
|
// Arbitrarily assign the viewport to element 0
|
||||||
// TODO - this should perform a search to find the most suitable viewport.
|
// TODO - this should perform a search to find the most suitable viewport.
|
||||||
updatedViewports[0] = { ...updatedViewports[0] };
|
updatedViewports[0] = { ...updatedViewports[0] };
|
||||||
|
|||||||
@ -35,9 +35,9 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@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/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",
|
"@ohif/ui": "3.9.0-beta.98",
|
||||||
"cornerstone-math": "0.1.9",
|
"cornerstone-math": "0.1.9",
|
||||||
"dicom-parser": "^1.8.21"
|
"dicom-parser": "^1.8.21"
|
||||||
|
|||||||
@ -466,7 +466,6 @@ class MeasurementService extends PubSubService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sourceInfo = this._getSourceToString(source);
|
const sourceInfo = this._getSourceToString(source);
|
||||||
|
|
||||||
if (!this._sourceHasMappings(source)) {
|
if (!this._sourceHasMappings(source)) {
|
||||||
throw new Error(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`);
|
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"
|
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||||
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||||
|
|
||||||
"@cornerstonejs/adapters@^1.77.6":
|
"@cornerstonejs/adapters@^1.86.0":
|
||||||
version "1.83.0"
|
version "1.86.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.83.0.tgz#934c462d98b7857ef9670702547505d112fa13f8"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.86.0.tgz#389938f25b4b3f784c13dda46c3d2523a1db38cf"
|
||||||
integrity sha512-PKNdzwI/i79Gu9ZxM4SemttCKJpSnMUSQSBu27mXzFzm5ENyOGnus3sLV5ciqZBRaHiuyteaHosJhxOavxaX1g==
|
integrity sha512-Ln8hC2W6d30q97Mhjboq57Qt5WhSl/2IG5Qf5VFRKha/k6Jgd4eP/gdhpUMxTlRZM3ibuQWwshsRZWCJl+VSgQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime-corejs2" "^7.17.8"
|
"@babel/runtime-corejs2" "^7.17.8"
|
||||||
"@cornerstonejs/tools" "^1.83.0"
|
"@cornerstonejs/tools" "^1.86.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"
|
|
||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
dcmjs "^0.29.8"
|
dcmjs "^0.29.8"
|
||||||
gl-matrix "^3.4.3"
|
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"
|
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==
|
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"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-1.2.4.tgz#c7cf67a34091eb74a6676abec80a5251c412b551"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-1.2.4.tgz#c7cf67a34091eb74a6676abec80a5251c412b551"
|
||||||
integrity sha512-UT2su6xZZnCPSuWf2ldzKa/2+guQ7BGgfBSKqxanggwJHh48gZqIAzekmsLyJHMMK5YDK+ti+fzvVJhBS3Xi/g==
|
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"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
|
||||||
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
||||||
|
|
||||||
"@cornerstonejs/core@^1.77.6", "@cornerstonejs/core@^1.83.0":
|
"@cornerstonejs/core@^1.83.1":
|
||||||
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":
|
|
||||||
version "1.83.1"
|
version "1.83.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.83.1.tgz#626df83e896a5e8328012c681343f1158840dfc1"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.83.1.tgz#626df83e896a5e8328012c681343f1158840dfc1"
|
||||||
integrity sha512-VuLjcBZ+qPeUh+8RDrma7SvDGMEA/kXb1nsf+1yQTJQsqJmNiSyV+Pb7q/bn4X270KtGrEiyCJSNkb7b2zvfhQ==
|
integrity sha512-VuLjcBZ+qPeUh+8RDrma7SvDGMEA/kXb1nsf+1yQTJQsqJmNiSyV+Pb7q/bn4X270KtGrEiyCJSNkb7b2zvfhQ==
|
||||||
@ -2536,16 +2477,27 @@
|
|||||||
gl-matrix "^3.4.3"
|
gl-matrix "^3.4.3"
|
||||||
lodash.clonedeep "4.5.0"
|
lodash.clonedeep "4.5.0"
|
||||||
|
|
||||||
"@cornerstonejs/dicom-image-loader@^1.77.6":
|
"@cornerstonejs/core@^1.86.0":
|
||||||
version "1.81.3"
|
version "1.86.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.81.3.tgz#8d9165b351954999bbc566d811a9eedd2363d55c"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.86.0.tgz#19f94b8a4d37ffbd775b82f3963af5fbe38709de"
|
||||||
integrity sha512-QFqr0Jq9DWvoBB1rTwI6gL4fc3pawpnG6ebDSZpz+uPmG/QcGYaeGNT+Szrcu2V/q1ByOU/Jxn8ZG5Nuo+kD6A==
|
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:
|
dependencies:
|
||||||
"@cornerstonejs/codec-charls" "^1.2.3"
|
"@cornerstonejs/codec-charls" "^1.2.3"
|
||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
||||||
"@cornerstonejs/codec-openjpeg" "^1.2.2"
|
"@cornerstonejs/codec-openjpeg" "^1.2.2"
|
||||||
"@cornerstonejs/codec-openjph" "^2.4.5"
|
"@cornerstonejs/codec-openjph" "^2.4.5"
|
||||||
"@cornerstonejs/core" "^1.81.3"
|
"@cornerstonejs/core" "^1.86.0"
|
||||||
dicom-parser "^1.8.9"
|
dicom-parser "^1.8.9"
|
||||||
pako "^2.0.4"
|
pako "^2.0.4"
|
||||||
uuid "^9.0.0"
|
uuid "^9.0.0"
|
||||||
@ -2558,36 +2510,12 @@
|
|||||||
"@cornerstonejs/core" "^1.83.1"
|
"@cornerstonejs/core" "^1.83.1"
|
||||||
comlink "^4.4.1"
|
comlink "^4.4.1"
|
||||||
|
|
||||||
"@cornerstonejs/tools@^1.82.0":
|
"@cornerstonejs/tools@^1.86.0":
|
||||||
version "1.82.0"
|
version "1.86.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.82.0.tgz#c7c9a24cc80755121c2209489e348b8c9d252d0d"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.86.0.tgz#ab7ca218faea4c6354866e02bcdf8005282d161c"
|
||||||
integrity sha512-1nnlloa6g5zfirAbVnZ+lfuM6DdtcN8kHkZ29xkedzpP24Le4fRHNeuNf6jTSGcgDXPhoZ+WA+q8NnH6wj1h1Q==
|
integrity sha512-yyy7r9Vo0NrTYYeKQtVqpbLRrVaequd9Q6aU2ifiNusdi6w+8Xslh1U/AHfw2Y9fueK8JBkLnKL+o/nLfdaOuQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cornerstonejs/core" "^1.82.0"
|
"@cornerstonejs/core" "^1.86.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"
|
|
||||||
"@icr/polyseg-wasm" "0.4.0"
|
"@icr/polyseg-wasm" "0.4.0"
|
||||||
"@types/offscreencanvas" "2019.7.3"
|
"@types/offscreencanvas" "2019.7.3"
|
||||||
comlink "^4.4.1"
|
comlink "^4.4.1"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user