OHIF with Cornerstone3D 2.0 (#4347)

This commit is contained in:
Alireza authored and GitHub committed 2024-11-06 07:15:27 -05:00
1 parent 8981a61b8c
commit 7ce7127d7c
803 files changed
+32760 -15893

No files matched your search

@@ -97,7 +97,7 @@ function _getDisplaySetsFromSeries(
};
// Does not get the referenced volumeId during parametric displaySet creation because the
// referenced displaySet is still not avaialble (getDisplaySetByUID returns `undefined`).
// referenced displaySet is still not available (getDisplaySetByUID returns `undefined`).
displaySet.getReferencedVolumeId = () => {
if (displaySet.referencedVolumeId) {
return displaySet.referencedVolumeId;
@@ -119,6 +119,22 @@ function _getDisplaySetsFromSeries(
return [displaySet];
}
const getRangeFromPixelData = (pixelData: Float32Array) => {
let lowest = pixelData[0];
let highest = pixelData[0];
for (let i = 1; i < pixelData.length; i++) {
if (pixelData[i] < lowest) {
lowest = pixelData[i];
}
if (pixelData[i] > highest) {
highest = pixelData[i];
}
}
return [lowest, highest];
};
async function _load(
displaySet,
servicesManager: AppTypes.ServicesManager,
@@ -132,11 +148,11 @@ async function _load(
return volumeLoadObject.promise;
}
displaySet.loadStatus.loading = true;
displaySet.loading = true;
displaySet.isLoaded = false;
// We don't want to fire multiple loads, so we'll wait for the first to finish
// and also return the same promise to any other callers.
// loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
const promise = _loadParametricMap({
extensionManager,
displaySet,
@@ -149,18 +165,26 @@ async function _load(
promise
.then(() => {
displaySet.loadStatus.loading = false;
displaySet.loadStatus.loaded = true;
displaySet.loading = false;
displaySet.isLoaded = true;
// Broadcast that loading is complete
servicesManager.services.segmentationService._broadcastEvent(
servicesManager.services.segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
{
pmapDisplaySet: displaySet,
}
);
})
.catch(err => {
displaySet.loadStatus.loading = false;
displaySet.loading = false;
displaySet.isLoaded = false;
throw err;
});
return promise;
}
async function _loadParametricMap({ extensionManager, displaySet, headers }: withAppTypes) {
async function _loadParametricMap({ displaySet, headers }: withAppTypes) {
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(displaySet, null, headers);
const referencedVolumeId = displaySet.getReferencedVolumeId();
const cachedReferencedVolume = cache.getVolume(referencedVolumeId);
@@ -189,9 +213,12 @@ async function _loadParametricMap({ extensionManager, displaySet, headers }: wit
},
});
derivedVolume.getScalarData().set(pixelData);
const range = derivedVolume.imageData.getPointData().getScalars().getRange();
const newPixelData = new TypedArrayConstructor(pixelData.length);
for (let i = 0; i < pixelData.length; i++) {
newPixelData[i] = pixelData[i] * 100;
}
derivedVolume.voxelManager.setCompleteScalarDataArray(newPixelData);
const range = getRangeFromPixelData(newPixelData);
const windowLevel = csUtils.windowLevel.toWindowLevel(range[0], range[1]);
derivedVolume.metadata.voiLut = [windowLevel];
@@ -1,6 +0,0 @@
function createPMAPToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
}
export default createPMAPToolGroupAndAddTools;
@@ -1,16 +1,19 @@
import PropTypes from 'prop-types';
import React, { useCallback, useEffect, useRef } from 'react';
import { useViewportGrid } from '@ohif/ui';
import createPMAPToolGroupAndAddTools from '../utils/initPMAPToolGroup';
const PMAP_TOOLGROUP_BASE_NAME = 'PMAPToolGroup';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui';
function OHIFCornerstonePMAPViewport(props: withAppTypes) {
const { displaySets, viewportOptions, displaySetOptions, servicesManager, extensionManager } =
props;
const {
displaySets,
children,
viewportOptions,
displaySetOptions,
servicesManager,
extensionManager,
} = props;
const viewportId = viewportOptions.viewportId;
const { displaySetService, toolGroupService, customizationService } = servicesManager.services;
const toolGroupId = `${PMAP_TOOLGROUP_BASE_NAME}-${viewportId}`;
const { displaySetService, segmentationService, uiNotificationService } =
servicesManager.services;
// PMAP viewport will always have a single display set
if (displaySets.length !== 1) {
@@ -32,6 +35,24 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
metadata: referencedDisplaySetMetadata,
};
const [pmapIsLoading, setPmapIsLoading] = useState(!pmapDisplaySet.isLoaded);
// Add effect to listen for loading complete
useEffect(() => {
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
evt => {
if (evt.pmapDisplaySet?.displaySetInstanceUID === pmapDisplaySet.displaySetInstanceUID) {
setPmapIsLoading(false);
}
}
);
return () => {
unsubscribe();
};
}, [pmapDisplaySet]);
const getCornerstoneViewport = useCallback(() => {
const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current;
const { component: Component } = extensionManager.getModuleEntry(
@@ -46,12 +67,25 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
Object.assign(pmapDisplaySetOptions.options, {
colormap: {
name: 'rainbow',
name: 'rainbow_2',
opacity: [
{ value: 0, opacity: 0.5 },
{ value: 1, opacity: 1 },
{ value: 0, opacity: 0 },
{ value: 0.25, opacity: 0.25 },
{ value: 0.5, opacity: 0.5 },
{ value: 0.75, opacity: 0.75 },
{ value: 0.9, opacity: 0.99 },
],
},
voi: {
windowCenter: 50,
windowWidth: 100,
},
});
uiNotificationService.show({
title: 'Parametric Map',
type: 'warning',
message: 'The values are multiplied by 100 in the viewport for better visibility',
});
return (
@@ -61,7 +95,6 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
displaySets={[referencedDisplaySet, pmapDisplaySet]}
viewportOptions={{
viewportType: 'volume',
toolGroupId: toolGroupId,
orientation: viewportOptions.orientation,
viewportId: viewportOptions.viewportId,
}}
@@ -73,7 +106,6 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
displaySetOptions,
props,
pmapDisplaySet,
toolGroupId,
viewportOptions.orientation,
viewportOptions.viewportId,
]);
@@ -98,23 +130,33 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
};
}, [activeViewportId, displaySetService, viewportGridService, viewports]);
useEffect(() => {
let toolGroup = toolGroupService.getToolGroup(toolGroupId);
let childrenWithProps = null;
if (toolGroup) {
return;
}
// This creates a custom tool group which has the lifetime of this view only
toolGroup = createPMAPToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
return () => toolGroupService.destroyToolGroup(toolGroupId);
}, [customizationService, toolGroupId, toolGroupService]);
if (children && children.length) {
childrenWithProps = children.map((child, index) => {
return (
child &&
React.cloneElement(child, {
viewportId,
key: index,
})
);
});
}
return (
<>
<div className="relative flex h-full w-full flex-row overflow-hidden">
{pmapIsLoading && (
<LoadingIndicatorTotalPercent
className="h-full w-full"
totalNumbers={null}
percentComplete={null}
loadingText="Loading Parametric Map..."
/>
)}
{getCornerstoneViewport()}
{childrenWithProps}
</div>
</>
);