perf(segmentation): make segmentation read 3x faster (#3577)

This commit is contained in:
Alireza 2023-08-08 14:39:31 -04:00 committed by GitHub
parent ec76ea24dd
commit 156c1ba60b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 187 additions and 315 deletions

View File

@ -1,11 +1,14 @@
import vtkMath from '@kitware/vtk.js/Common/Core/Math';
import { utils } from '@ohif/core';
import {
metaData,
cache,
triggerEvent,
eventTarget,
} from '@cornerstonejs/core';
import { adaptersSEG, Enums } from '@cornerstonejs/adapters';
import { SOPClassHandlerId } from './id';
import dcmjs from 'dcmjs';
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
import { dicomlabToRGB } from './utils/dicomlabToRGB';
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4'];
@ -122,13 +125,12 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) {
!segDisplaySet.segments ||
Object.keys(segDisplaySet.segments).length === 0
) {
const segments = await _loadSegments(
await _loadSegments({
extensionManager,
servicesManager,
segDisplaySet,
headers
);
segDisplaySet.segments = segments;
headers,
});
}
const suppressEvents = true;
@ -147,28 +149,65 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) {
return loadPromises[SOPInstanceUID];
}
async function _loadSegments(extensionManager, segDisplaySet, headers) {
async function _loadSegments({
extensionManager,
servicesManager,
segDisplaySet,
headers,
}) {
const utilityModule = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.utilityModule.common'
);
const { segmentationService } = servicesManager.services;
const { dicomLoaderService } = utilityModule.exports;
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(
segDisplaySet,
null,
headers
);
const dicomData = DicomMessage.readFile(segArrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
const cachedReferencedVolume = cache.getVolume(
segDisplaySet.referencedVolumeId
);
if (!Array.isArray(dataset.SegmentSequence)) {
dataset.SegmentSequence = [dataset.SegmentSequence];
if (!cachedReferencedVolume) {
throw new Error(
'Referenced Volume is missing for the SEG, and stack viewport SEG is not supported yet'
);
}
const segments = _getSegments(dataset);
return segments;
const { imageIds } = cachedReferencedVolume;
// Todo: what should be defaults here
const tolerance = 0.001;
const skipOverlapping = true;
eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => {
const { percentComplete } = evt.detail;
segmentationService._broadcastEvent(
segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE,
{
percentComplete,
}
);
});
const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState(
imageIds,
arrayBuffer,
metaData,
{ skipOverlapping, tolerance, eventTarget, triggerEvent }
);
results.segMetadata.data.forEach((data, i) => {
if (i > 0) {
data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue);
}
});
Object.assign(segDisplaySet, results);
}
function _segmentationExists(segDisplaySet, segmentationService) {
@ -178,114 +217,6 @@ function _segmentationExists(segDisplaySet, segmentationService) {
);
}
function _getPixelData(dataset, segments) {
let frameSize = Math.ceil((dataset.Rows * dataset.Columns) / 8);
let nextOffset = 0;
Object.keys(segments).forEach(segmentKey => {
const segment = segments[segmentKey];
segment.numberOfFrames = segment.functionalGroups.length;
segment.size = segment.numberOfFrames * frameSize;
segment.offset = nextOffset;
nextOffset = segment.offset + segment.size;
const packedSegment = dataset.PixelData[0].slice(
segment.offset,
nextOffset
);
segment.pixelData = dcmjs.data.BitArray.unpack(packedSegment);
segment.geometry = geometryFromFunctionalGroups(
dataset,
segment.functionalGroups
);
});
return segments;
}
function geometryFromFunctionalGroups(dataset, perFrame) {
let pixelMeasures =
dataset.SharedFunctionalGroupsSequence.PixelMeasuresSequence;
let planeOrientation =
dataset.SharedFunctionalGroupsSequence.PlaneOrientationSequence;
let planePosition = perFrame[0].PlanePositionSequence; // TODO: assume sorted frames!
const geometry = {};
// NB: DICOM PixelSpacing is defined as Row then Column,
// unlike ImageOrientationPatient
let spacingBetweenSlices = pixelMeasures.SpacingBetweenSlices;
if (!spacingBetweenSlices) {
if (pixelMeasures.SliceThickness) {
console.log('Using SliceThickness as SpacingBetweenSlices');
spacingBetweenSlices = pixelMeasures.SliceThickness;
}
}
geometry.spacing = [
pixelMeasures.PixelSpacing[1],
pixelMeasures.PixelSpacing[0],
spacingBetweenSlices,
].map(Number);
geometry.dimensions = [dataset.Columns, dataset.Rows, perFrame.length].map(
Number
);
let orientation = planeOrientation.ImageOrientationPatient.map(Number);
const columnStepToPatient = orientation.slice(0, 3);
const rowStepToPatient = orientation.slice(3, 6);
geometry.planeNormal = [];
vtkMath.cross(columnStepToPatient, rowStepToPatient, geometry.planeNormal);
let firstPosition = perFrame[0].PlanePositionSequence.ImagePositionPatient.map(
Number
);
let lastPosition = perFrame[
perFrame.length - 1
].PlanePositionSequence.ImagePositionPatient.map(Number);
geometry.sliceStep = [];
vtkMath.subtract(lastPosition, firstPosition, geometry.sliceStep);
vtkMath.normalize(geometry.sliceStep);
geometry.direction = columnStepToPatient
.concat(rowStepToPatient)
.concat(geometry.sliceStep);
geometry.origin = planePosition.ImagePositionPatient.map(Number);
return geometry;
}
function _getSegments(dataset) {
const segments = {};
dataset.SegmentSequence.forEach(segment => {
const cielab = segment.RecommendedDisplayCIELabValue;
const rgba = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x =>
Math.round(x * 255)
);
rgba.push(255);
const segmentNumber = segment.SegmentNumber;
segments[segmentNumber] = {
color: rgba,
functionalGroups: [],
offset: null,
size: null,
pixelData: null,
label: segment.SegmentLabel,
};
});
// make a list of functional groups per segment
dataset.PerFrameFunctionalGroupsSequence.forEach(functionalGroup => {
const segmentNumber =
functionalGroup.SegmentIdentificationSequence.ReferencedSegmentNumber;
segments[segmentNumber].functionalGroups.push(functionalGroup);
});
return _getPixelData(dataset, segments);
}
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
const getDisplaySetsFromSeries = instances => {
return _getDisplaySetsFromSeries(

View File

@ -0,0 +1,16 @@
import dcmjs from 'dcmjs';
/**
* Converts a CIELAB color to an RGB color using the dcmjs library.
* @param cielab - The CIELAB color to convert.
* @returns The RGB color as an array of three integers between 0 and 255.
*/
function dicomlabToRGB(cielab: number[]): number[] {
const rgb = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x =>
Math.round(x * 255)
);
return rgb;
}
export { dicomlabToRGB };

View File

@ -317,7 +317,6 @@ function OHIFCornerstoneSEGViewport(props) {
setIsHydrated(isHydrated);
};
return (
<>
<ViewportActionBar
@ -344,10 +343,13 @@ function OHIFCornerstoneSEGViewport(props) {
patientSex: PatientSex || '',
patientAge: PatientAge || '',
MRN: PatientID || '',
thickness: SliceThickness ? `${parseFloat(SliceThickness).toFixed(2)}mm` : '',
thickness: SliceThickness
? utils.roundNumber(SliceThickness, 2)
: '',
thicknessUnits: SliceThickness !== undefined ? 'mm' : '',
spacing:
SpacingBetweenSlices !== undefined
? `${parseFloat(SpacingBetweenSlices).toFixed(2)}mm`
? utils.roundNumber(SpacingBetweenSlices, 2)
: '',
scanner: ManufacturerModelName || '',
},

View File

@ -44,9 +44,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^1.7.1",
"@cornerstonejs/core": "^1.7.1",
"@cornerstonejs/tools": "^1.7.1",
"@cornerstonejs/adapters": "^1.9.3",
"@cornerstonejs/core": "^1.9.3",
"@cornerstonejs/tools": "^1.9.3",
"classnames": "^2.3.2"
}
}

View File

@ -36,7 +36,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
"@cornerstonejs/dicom-image-loader": "^1.7.1",
"@cornerstonejs/dicom-image-loader": "^1.9.3",
"@ohif/core": "3.7.0-beta.42",
"@ohif/ui": "3.7.0-beta.42",
"dcmjs": "^0.29.6",
@ -52,10 +52,10 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^1.7.1",
"@cornerstonejs/core": "^1.7.1",
"@cornerstonejs/streaming-image-volume-loader": "^1.7.1",
"@cornerstonejs/tools": "^1.7.1",
"@cornerstonejs/adapters": "^1.9.3",
"@cornerstonejs/core": "^1.9.3",
"@cornerstonejs/streaming-image-volume-loader": "^1.9.3",
"@cornerstonejs/tools": "^1.9.3",
"@kitware/vtk.js": "27.3.1",
"html2canvas": "^1.4.1",
"lodash.debounce": "4.0.8",

View File

@ -563,11 +563,11 @@ class SegmentationService extends PubSubService {
);
}
const { segments, referencedVolumeId } = segDisplaySet;
const { labelmapBufferArray, referencedVolumeId } = segDisplaySet;
if (!segments || !referencedVolumeId) {
if (!labelmapBufferArray || !referencedVolumeId) {
throw new Error(
'To create the segmentation from SEG displaySet, the displaySet should be loaded first, you can perform segDisplaySet.load() before calling this method.'
'No labelmapBufferArray or referencedVolumeId found for the SEG displaySet'
);
}
@ -593,88 +593,27 @@ class SegmentationService extends PubSubService {
},
}
);
const [rows, columns] = derivedVolume.dimensions;
const derivedVolumeScalarData = derivedVolume.getScalarData();
const { imageIds } = referencedVolume;
const sopUIDImageIdIndexMap = imageIds.reduce((acc, imageId, index) => {
const { sopInstanceUid } = metaData.get('generalImageModule', imageId);
acc[sopInstanceUid] = index;
return acc;
}, {} as { [sopUID: string]: number });
const segmentsInfo = segDisplaySet.segMetadata.data;
derivedVolumeScalarData.set(new Uint8Array(labelmapBufferArray[0]));
const numSegments = Object.keys(segments).length;
// Note: ideally we could use the TypedArray set method, but since each
// slice can have multiple segments, we need to loop over each slice and
// set the segment value for each segment.
let overlappingSegments = false;
const _segmentInfoUpdate = (segmentInfo, segmentIndex) => {
const { pixelData: segPixelData } = segmentInfo;
let segmentX = 0;
let segmentY = 0;
let segmentZ = 0;
let count = 0;
for (const [
functionalGroupIndex,
functionalGroup,
] of segmentInfo.functionalGroups.entries()) {
const {
ReferencedSOPInstanceUID,
} = functionalGroup.DerivationImageSequence.SourceImageSequence;
const imageIdIndex = sopUIDImageIdIndexMap[ReferencedSOPInstanceUID];
if (imageIdIndex === -1) {
return;
}
const step = rows * columns;
// we need a faster way to get the pixel data for the current
// functional group, which we use typed array view
const functionGroupPixelData = new Uint8Array(
segPixelData.buffer,
functionalGroupIndex * step,
step
);
const functionalGroupStartIndex = imageIdIndex * step;
const functionalGroupEndIndex = (imageIdIndex + 1) * step;
// Note: this for loop is not optimized, since DICOM SEG stores
// each segment as a separate labelmap so if there is a slice
// that has multiple segments, we will have to loop over each
// segment and we cannot use the TypedArray set method.
for (
let i = functionalGroupStartIndex, j = 0;
i < functionalGroupEndIndex;
i++, j++
) {
if (functionGroupPixelData[j] !== 0) {
if (derivedVolumeScalarData[i] !== 0) {
overlappingSegments = true;
}
derivedVolumeScalarData[i] = segmentIndex;
// centroid calculations
segmentX += i % columns;
segmentY += Math.floor(i / columns) % rows;
segmentZ += Math.floor(i / (columns * rows));
count++;
}
}
segmentation.segments = segmentsInfo.map((segmentInfo, segmentIndex) => {
if (segmentIndex === 0) {
return;
}
// centroid calculations
const x = Math.floor(segmentX / count);
const y = Math.floor(segmentY / count);
const z = Math.floor(segmentZ / count);
const {
SegmentedPropertyCategoryCodeSequence,
SegmentNumber,
SegmentLabel,
SegmentAlgorithmType,
SegmentAlgorithmName,
SegmentedPropertyTypeCodeSequence,
rgba,
} = segmentInfo;
const { x, y, z } = segDisplaySet.centroids.get(segmentIndex);
const centerWorld = derivedVolume.imageData.indexToWorld([x, y, z]);
segmentation.cachedStats = {
@ -691,65 +630,31 @@ class SegmentationService extends PubSubService {
},
};
const numInitialized = Object.keys(segmentation.cachedStats.segmentCenter)
.length;
// Calculate percentage completed
const percentComplete = Math.round((numInitialized / numSegments) * 100);
this._broadcastEvent(EVENTS.SEGMENT_LOADING_COMPLETE, {
percentComplete,
numSegments: numSegments,
});
};
const promiseArray = [];
for (const segmentIndex in segments) {
const segmentInfo = segments[segmentIndex];
// Important: we need a non-blocking way to update the segmentation
// state, otherwise the UI will freeze and the user will not be able
// to interact with the app or progress bars will not be updated.
const promise = new Promise<void>((resolve, reject) => {
setTimeout(() => {
_segmentInfoUpdate(segmentInfo, segmentIndex);
resolve();
}, 0);
});
promiseArray.push(promise);
}
await Promise.all(promiseArray);
segmentation.segmentCount = Object.keys(segments).length;
segmentation.segments = [null]; // segment 0
Object.keys(segments).forEach(segmentIndex => {
const segmentInfo = segments[segmentIndex];
const segIndex = Number(segmentIndex);
segmentation.segments[segIndex] = {
label: segmentInfo.label || `Segment ${segIndex}`,
segmentIndex: Number(segmentIndex),
color: [
segmentInfo.color[0],
segmentInfo.color[1],
segmentInfo.color[2],
],
opacity: segmentInfo.color[3],
return {
label: SegmentLabel || `Segment ${SegmentNumber}`,
segmentIndex: Number(SegmentNumber),
category: SegmentedPropertyCategoryCodeSequence
? SegmentedPropertyCategoryCodeSequence.CodeMeaning
: '',
type: SegmentedPropertyTypeCodeSequence
? SegmentedPropertyTypeCodeSequence.CodeMeaning
: '',
algorithmType: SegmentAlgorithmType,
algorithmName: SegmentAlgorithmName,
color: rgba,
opacity: 255,
isVisible: true,
isLocked: false,
};
});
segmentation.segmentCount = segmentsInfo.length - 1;
segDisplaySet.isLoaded = true;
this._broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, {
segmentationId,
segDisplaySet,
overlappingSegments,
});
return this.addOrUpdateSegmentation(segmentation, suppressEvents);

View File

@ -30,8 +30,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "^1.7.1",
"@cornerstonejs/tools": "^1.7.1",
"@cornerstonejs/core": "^1.9.3",
"@cornerstonejs/tools": "^1.9.3",
"@ohif/core": "3.7.0-beta.42",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.42",
"@ohif/ui": "3.7.0-beta.42",

View File

@ -30,7 +30,6 @@
"dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js",
"dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js",
"dev:viewer": "yarn run dev",
"preinstall": "node preinstall.js",
"start": "yarn run dev",
"test:e2e": "cypress open",
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
@ -51,7 +50,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
"@cornerstonejs/dicom-image-loader": "^1.7.1",
"@cornerstonejs/dicom-image-loader": "^1.9.3",
"@ohif/core": "3.7.0-beta.42",
"@ohif/extension-cornerstone": "3.7.0-beta.42",
"@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.42",

View File

@ -35,7 +35,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
"@cornerstonejs/dicom-image-loader": "^1.7.1",
"@cornerstonejs/dicom-image-loader": "^1.9.3",
"@ohif/ui": "3.7.0-beta.42",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"

View File

@ -70,6 +70,11 @@ class MetadataProvider {
SeriesInstanceUID,
SOPInstanceUID
);
if (!instance) {
return;
}
return (
(frameNumber && combineFrameInstance(frameNumber, instance)) || instance
);

View File

@ -27,6 +27,7 @@ export type ExtensionConfiguration = Record<string, unknown>;
*/
export interface ExtensionParams extends ExtensionConstructor {
extensionManager: ExtensionManager;
servicesManager: ServicesManager;
configuration?: ExtensionConfiguration;
}
@ -43,6 +44,7 @@ export interface Extension {
getViewportModule?: (p: ExtensionParams) => unknown;
getUtilityModule?: (p: ExtensionParams) => unknown;
getCustomizationModule?: (p: ExtensionParams) => unknown;
getSopClassHandlerModule?: (p: ExtensionParams) => unknown;
onModeEnter?: () => void;
onModeExit?: () => void;
}

View File

@ -1,3 +1,4 @@
import { ExtensionManager } from '../../extensions';
import { InstanceMetadata } from '../../types';
import { PubSubService } from '../_shared/pubSubServiceInterface';
import EVENTS from './EVENTS';
@ -47,6 +48,7 @@ export default class DisplaySetService extends PubSubService {
};
public activeDisplaySets = [];
extensionManager: ExtensionManager;
protected activeDisplaySetsMap = new Map<string, DisplaySet>();

View File

@ -161,3 +161,9 @@ devServer: {
}
}
```
:::tip
Take a look at how other people have integrated OHIF in their react app
example1: https://github.com/OHIF/Viewers/issues/3371#issuecomment-1630405255
:::

View File

@ -21,26 +21,23 @@ function LoadingIndicatorTotalPercent({
loadingText = 'Loading...',
targetText = 'segments',
}: Props): JSX.Element {
percentComplete = percentComplete !== null ? percentComplete : null;
const progress = percentComplete !== null ? percentComplete : null;
const progress = percentComplete;
const totalNumbersText = totalNumbers !== null ? `${totalNumbers}` : '';
const numTargetsLoadedText =
percentComplete !== null
? Math.floor((percentComplete * totalNumbers) / 100)
: '';
const textBlock = !totalNumbers ? (
<div className="text-white text-sm">{loadingText}</div>
) : (
<div className="text-white text-sm flex items-baseline space-x-1">
<div>Loaded</div>
<div>{numTargetsLoadedText}</div>
<div>of</div>
<div>{totalNumbersText}</div>
<div>{targetText}</div>
</div>
);
const textBlock =
!totalNumbers && percentComplete === null ? (
<div className="text-white text-sm">{loadingText}</div>
) : !totalNumbers && percentComplete !== null ? (
<div className="text-white text-sm">Loaded {percentComplete}%</div>
) : (
<div className="text-white text-sm">
Loaded {numTargetsLoadedText} of {totalNumbersText} {targetText}
</div>
);
return (
<LoadingIndicatorProgress

View File

@ -19,6 +19,7 @@ function PatientInfo({
patientAge,
MRN,
thickness,
thicknessUnits,
spacing,
scanner,
isOpen,
@ -29,7 +30,6 @@ function PatientInfo({
while (patientAge.charAt(0) === '0') {
patientAge = patientAge.substr(1);
}
return (
<div ref={showPatientInfoRef}>
<Tooltip
@ -90,7 +90,9 @@ function PatientInfo({
className={classnames(classes.infoText)}
title={thickness}
>
{thickness}
{thicknessUnits
? `${thickness}${thicknessUnits}`
: `${thickness}`}
</span>
</div>
<div className={classnames(classes.row)}>
@ -136,6 +138,7 @@ PatientInfo.propTypes = {
patientAge: PropTypes.string,
MRN: PropTypes.string,
thickness: PropTypes.string,
thicknessUnits: PropTypes.string,
spacing: PropTypes.string,
scanner: PropTypes.string,
isOpen: PropTypes.bool,

View File

@ -34,6 +34,7 @@ const ViewportActionBar = ({
patientAge,
MRN,
thickness,
thicknessUnits,
spacing,
scanner,
} = patientInformation;
@ -169,6 +170,7 @@ const ViewportActionBar = ({
patientAge={patientAge}
MRN={MRN}
thickness={thickness}
thicknessUnits={thicknessUnits}
spacing={spacing}
scanner={scanner}
/>
@ -192,6 +194,7 @@ ViewportActionBar.propTypes = {
patientAge: PropTypes.string.isRequired,
MRN: PropTypes.string.isRequired,
thickness: PropTypes.string.isRequired,
thicknessUnits: PropTypes.string.isRequired,
spacing: PropTypes.string.isRequired,
scanner: PropTypes.string.isRequired,
}),

View File

@ -1321,9 +1321,9 @@
regenerator-runtime "^0.13.11"
"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
version "7.22.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb"
integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
dependencies:
regenerator-runtime "^0.13.11"
@ -1376,13 +1376,14 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cornerstonejs/adapters@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.7.1.tgz#5ae8b510791f5817420ade4b5637f9e84a8ada52"
integrity sha512-iwowMUVoNKWZ3PTmYSjnZ2vDThR+OUkvIsSB913A1YhPCl+FVv2Y0JLdueFCYKlTFLnzFa8FOlFwzAOI+AAB+Q==
"@cornerstonejs/adapters@^1.9.3":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.9.3.tgz#b1031e26af175ac15c3c975aff89f51c214ec092"
integrity sha512-bjpUq78yupE3+z5qNAS7j27OgCn1BAJQKwcQNY+PBUzTojwg2KpMpG69I+CucSCAxHKDv4waQy4hEyNaiI5JAA==
dependencies:
"@babel/runtime-corejs2" "^7.17.8"
dcmjs "^0.29.5"
buffer "^6.0.3"
dcmjs "^0.29.8"
gl-matrix "^3.4.3"
lodash.clonedeep "^4.5.0"
ndarray "^1.0.19"
@ -1427,43 +1428,43 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.2.tgz#e96721d56f6ec96f7f95c16321d88cc8467d8d81"
integrity sha512-lgdvBvvNezleY+4pIe2ceUsJzlZe/0PipdeubQ3vZZOz3xxtHHMR1XFCl4fgd8gosR8COHuD7h6q+MwgrwBsng==
"@cornerstonejs/core@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.7.1.tgz#38c2fa24cc166b15088a0f8d220ae33e9a80cb7e"
integrity sha512-XS44HcRwXJ81QqEnOjbpstLCrhApzNIvBuV4UM1+qhvUn0fg6LrXEkR/cT4xQ0RhHB9oq+0LPsfm9/X2xxWZAA==
"@cornerstonejs/core@^1.9.3":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.9.3.tgz#43c04b5c2a929e1f2ffc71d71aa2311dea2b1e7d"
integrity sha512-TMgxFe23Rur0k3ISDh0JGkQ6x2xCYFnqON68A+Eovxc7g/sHUZ0tbzXiTd+IaVpuRsaDxspb0fwklPISKTJDuA==
dependencies:
"@kitware/vtk.js" "27.3.1"
detect-gpu "^5.0.22"
gl-matrix "^3.4.3"
lodash.clonedeep "4.5.0"
"@cornerstonejs/dicom-image-loader@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.7.1.tgz#c5ee643bd1c846c1ca7c536f6db3ae26050353fd"
integrity sha512-U15NsCdjKkU6OKKWvRs6IwBxRqAudE2vZh+fwAWB9tmyjZCHnRs0KgscOVoYQvR5EV7f1gz7777Iz/u8tnSoFQ==
"@cornerstonejs/dicom-image-loader@^1.9.3":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.9.3.tgz#6ec07e8f22ba20c78cab930a1396effc1f86885e"
integrity sha512-+JRiI5hb9Cuo7yRGg3DzqgXTmNUMn9DAI1ASh721mbqJcPymHBDs+QTfF2bXyAnh4Ijxr+WfCEUSBm4PRPvrAw==
dependencies:
"@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
"@cornerstonejs/codec-openjpeg" "^1.2.2"
"@cornerstonejs/codec-openjph" "^2.4.2"
"@cornerstonejs/core" "^1.7.1"
"@cornerstonejs/core" "^1.9.3"
dicom-parser "^1.8.9"
pako "^2.0.4"
uuid "^9.0.0"
"@cornerstonejs/streaming-image-volume-loader@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.7.1.tgz#1102cde7136f4804c21b665b6429210545a79087"
integrity sha512-eIILqRpWodUVX4HgJbWlZhVebsz4gLxZaOtErOvL/knSK3zyb/D+Rsz8RjWbMJsixHp+gQ/Q51q/FYab/RvJrw==
"@cornerstonejs/streaming-image-volume-loader@^1.9.3":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.9.3.tgz#1772273bfd59f7f470ba254d9463c2d1f9c22359"
integrity sha512-xD1+ZNrYa1FQTgieG11d6jYnuZFH2zpDzg5eigFeZx9e8Vsu9rO1lpkwVWUw1Cf3qhqmsG9PA+BxHQm3ACjwTg==
dependencies:
"@cornerstonejs/core" "^1.7.1"
"@cornerstonejs/core" "^1.9.3"
"@cornerstonejs/tools@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.7.1.tgz#7cd589db556ba8a2139fcdaf01b84e03d4b78d2e"
integrity sha512-iulzSXInFlcsLTWsn4xCF+dpZEwSWf/UjZBLmVNfTA262bncmE083cn57VEB9mRuFN+doA50/8UJi7uNew+Q5Q==
"@cornerstonejs/tools@^1.9.3":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.9.3.tgz#40e967c4bc275b4bf4d38f6c26d3a979779a632b"
integrity sha512-mesMJi8L0JHebYy7F3+VE+6zRwkv9ZaYw4cwplguTgOq7s7xmINVHomIVkffHUQeUPhwPVmxbjIqeW6O5MkXeg==
dependencies:
"@cornerstonejs/core" "^1.7.1"
"@cornerstonejs/core" "^1.9.3"
lodash.clonedeep "4.5.0"
lodash.get "^4.4.2"
@ -8219,7 +8220,7 @@ dcmjs@^0.27:
loglevelnext "^3.0.1"
ndarray "^1.0.19"
dcmjs@^0.29.5:
dcmjs@^0.29.5, dcmjs@^0.29.8:
version "0.29.8"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.29.8.tgz#3daa5224f8e75b2e5b069590bef26a272741fa44"
integrity sha512-Y0/KZAmT1siVo7eH3KK4ZflEbNi61soUpD0N7lsXMVVJQ6IZkHlaSzb9DtqnEpMs7RJDfvZGr1uXpv1vBBIypQ==