Skip to content

Commit

Permalink
Merge pull request #12 from gradienthealth/gradienthealth/Segmentatio…
Browse files Browse the repository at this point in the history
…n-modifications

Gradienthealth/segmentation modifications
  • Loading branch information
Ouwen authored Dec 21, 2023
2 parents 9663fd7 + c2b315a commit 6d34ef6
Show file tree
Hide file tree
Showing 19 changed files with 628 additions and 201 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/deploy_ghpages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Deploy viewer to github pages

on:
push:
branches: [ "gradienthealth/zip_deployment" ]
branches: [ "gradienthealth/Stack-Segmentation" ]
#pull_request:
#branches: [ "main" ]

Expand All @@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: gradienthealth/cornerstone3D-beta
ref: gradienthealth/dicom-zip-image-loader
ref: gradienthealth/stack-segmentation-support-with-zip-image-loader
path: ./cornerstone3D

- name: Build cornerstone3D
Expand All @@ -37,7 +37,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: gradienthealth/GradientExtensionsAndModes
ref: gradienthealth/zip_deployment
ref: gradienthealth/Segmentation-with-DicomJSON
path: ./GradientExtensionsAndModes

#- name: Build GradientExtensionsAndModes
Expand Down
31 changes: 15 additions & 16 deletions extensions/cornerstone-dicom-seg/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getUpdatedViewportsForSegmentation,
getTargetViewport,
} from './utils/hydrationUtils';
import generateLabelmaps2DFromImageIdMap from './utils/generateLabelmaps2DFromImageIdMap';

const { datasetToBlob } = dcmjs.data;

Expand Down Expand Up @@ -77,17 +78,6 @@ const commandsModule = ({
// Todo: add support for multiple display sets
const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];

const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);

if (!displaySet.isReconstructable) {
uiNotificationService.show({
title: 'Segmentation',
message: 'Segmentation is not supported for non-reconstructible displaysets yet',
type: 'error',
});
return;
}

updateViewportsForSegmentationRendering({
viewportId,
servicesManager,
Expand Down Expand Up @@ -238,14 +228,23 @@ const commandsModule = ({
*/
generateSegmentation: ({ segmentationId, options = {} }) => {
const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId);
const segmentationLabelmapData = segmentation.representationData.LABELMAP;

let referencedImages, labelmapObj;
if (segmentation.representationData.LABELMAP.referencedVolumeId) {
const { referencedVolumeId } = segmentationLabelmapData;

const { referencedVolumeId } = segmentation.representationData.LABELMAP;
const segmentationVolume = cache.getVolume(segmentationId);
const referencedVolume = cache.getVolume(referencedVolumeId);
referencedImages = referencedVolume.getCornerstoneImages();

const segmentationVolume = cache.getVolume(segmentationId);
const referencedVolume = cache.getVolume(referencedVolumeId);
const referencedImages = referencedVolume.getCornerstoneImages();
labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume);
} else {
const { imageIdReferenceMap } = segmentationLabelmapData;

const labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume);
({ referencedImages, labelmapObj } =
generateLabelmaps2DFromImageIdMap(imageIdReferenceMap));
}

// Generate fake metadata as an example
labelmapObj.metadata = [];
Expand Down
19 changes: 11 additions & 8 deletions extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,24 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet,
'@ohif/extension-cornerstone.utilityModule.common'
);

const { segmentationService, uiNotificationService } = servicesManager.services;
const { segmentationService, uiNotificationService, displaySetService } =
servicesManager.services;

const { dicomLoaderService } = utilityModule.exports;
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers);

const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId);
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
segDisplaySet.referencedDisplaySetInstanceUID
);
let imageIds;

if (!cachedReferencedVolume) {
throw new Error(
'Referenced Volume is missing for the SEG, and stack viewport SEG is not supported yet'
);
if (referencedDisplaySet.isReconstructable) {
const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId);
imageIds = cachedReferencedVolume.imageIds || cachedReferencedVolume._imageIds;
} else {
imageIds = referencedDisplaySet.instances.map(instance => instance.imageId);
}

const { imageIds } = cachedReferencedVolume;

// Todo: what should be defaults here
const tolerance = 0.001;
const skipOverlapping = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ export default function PanelSegmentation({
};
}, []);

const setSegmentationActive = segmentationId => {
const isSegmentationActive = segmentations.find(seg => seg.id === segmentationId)?.isActive;

if (isSegmentationActive) {
return;
}

segmentationService.setActiveSegmentationForToolGroup(segmentationId);
};

const getToolGroupIds = segmentationId => {
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);

Expand All @@ -62,10 +72,12 @@ export default function PanelSegmentation({
};

const onSegmentationDelete = (segmentationId: string) => {
setSegmentationActive(segmentationId);
segmentationService.remove(segmentationId);
};

const onSegmentAdd = segmentationId => {
setSegmentationActive(segmentationId);
segmentationService.addSegment(segmentationId);
};

Expand All @@ -75,13 +87,13 @@ export default function PanelSegmentation({
const toolGroupIds = getToolGroupIds(segmentationId);

toolGroupIds.forEach(toolGroupId => {
// const toolGroupId =
segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId);
});
};

const onSegmentEdit = (segmentationId, segmentIndex) => {
setSegmentationActive(segmentationId);
const segmentation = segmentationService.getSegmentation(segmentationId);

const segment = segmentation.segments[segmentIndex];
Expand All @@ -97,6 +109,7 @@ export default function PanelSegmentation({
};

const onSegmentationEdit = segmentationId => {
setSegmentationActive(segmentationId);
const segmentation = segmentationService.getSegmentation(segmentationId);
const { label } = segmentation;

Expand All @@ -117,6 +130,7 @@ export default function PanelSegmentation({
};

const onSegmentColorClick = (segmentationId, segmentIndex) => {
setSegmentationActive(segmentationId);
const segmentation = segmentationService.getSegmentation(segmentationId);

const segment = segmentation.segments[segmentIndex];
Expand Down Expand Up @@ -144,10 +158,12 @@ export default function PanelSegmentation({
};

const onSegmentDelete = (segmentationId, segmentIndex) => {
setSegmentationActive(segmentationId);
segmentationService.removeSegment(segmentationId, segmentIndex);
};

const onToggleSegmentVisibility = (segmentationId, segmentIndex) => {
setSegmentationActive(segmentationId);
const segmentation = segmentationService.getSegmentation(segmentationId);
const segmentInfo = segmentation.segments[segmentIndex];
const isVisible = !segmentInfo.isVisible;
Expand All @@ -165,10 +181,12 @@ export default function PanelSegmentation({
};

const onToggleSegmentLock = (segmentationId, segmentIndex) => {
setSegmentationActive(segmentationId);
segmentationService.toggleSegmentLocked(segmentationId, segmentIndex);
};

const onToggleSegmentationVisibility = segmentationId => {
setSegmentationActive(segmentationId);
segmentationService.toggleSegmentationVisibility(segmentationId);
};

Expand All @@ -183,12 +201,14 @@ export default function PanelSegmentation({
);

const onSegmentationDownload = segmentationId => {
setSegmentationActive(segmentationId);
commandsManager.runCommand('downloadSegmentation', {
segmentationId,
});
};

const storeSegmentation = async segmentationId => {
setSegmentationActive(segmentationId);
const datasources = extensionManager.getActiveDataSource();

const displaySetInstanceUIDs = await createReportAsync({
Expand Down Expand Up @@ -216,6 +236,7 @@ export default function PanelSegmentation({
};

const onSegmentationDownloadRTSS = segmentationId => {
setSegmentationActive(segmentationId);
commandsManager.runCommand('downloadRTSS', {
segmentationId,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { cache } from '@cornerstonejs/core';

const generateLabelmaps2DFromImageIdMap = imageIdReferenceMap => {
const labelmaps2D = [],
referencedImages = [],
segmentsOnLabelmap3D = new Set();
Array.from(imageIdReferenceMap.entries()).forEach((entry, index) => {
referencedImages.push(cache.getImage(entry[0]));

const segmentationImage = cache.getImage(entry[1]);
const { rows, columns } = segmentationImage;
const pixelData = segmentationImage.getPixelData();
const segmentsOnLabelmap = [];

for (let i = 0; i < pixelData.length; i++) {
const segment = pixelData[i];
if (!segmentsOnLabelmap.includes(segment) && segment !== 0) {
segmentsOnLabelmap.push(segment);
}
}

if (segmentsOnLabelmap.length) {
labelmaps2D[index] = {
segmentsOnLabelmap,
pixelData,
rows,
columns,
};

segmentsOnLabelmap.forEach(segmentIndex => {
segmentsOnLabelmap3D.add(segmentIndex);
});
}
});

const labelmapObj = {
segmentsOnLabelmap: Array.from(segmentsOnLabelmap3D),
labelmaps2D,
};

return { referencedImages, labelmapObj };
};

export default generateLabelmaps2DFromImageIdMap;
49 changes: 30 additions & 19 deletions extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Enums, cache } from '@cornerstonejs/core';
import { Enums, cache, eventTarget } from '@cornerstonejs/core';

/**
* Updates the viewports in preparation for rendering segmentations.
Expand All @@ -25,8 +25,12 @@ async function updateViewportsForSegmentationRendering({
servicesManager: any;
referencedDisplaySetInstanceUID?: string;
}) {
const { cornerstoneViewportService, segmentationService, viewportGridService } =
servicesManager.services;
const {
cornerstoneViewportService,
segmentationService,
viewportGridService,
displaySetService,
} = servicesManager.services;

const viewport = getTargetViewport({ viewportId, viewportGridService });
const targetViewportId = viewport.viewportOptions.viewportId;
Expand All @@ -42,7 +46,7 @@ async function updateViewportsForSegmentationRendering({

// create Segmentation callback which needs to be waited until
// the volume is created (if coming from stack)
const createSegmentationForVolume = async () => {
const createSegmentation = async () => {
const segmentationId = await loadFn();
segmentationService.hydrateSegmentation(segmentationId);
};
Expand All @@ -53,25 +57,38 @@ async function updateViewportsForSegmentationRendering({
volumeId.includes(referencedDisplaySetInstanceUID)
);

const referencedDisplaySet = displaySetService.getDisplaySetByUID(
referencedDisplaySetInstanceUID
);

updatedViewports.forEach(async viewport => {
viewport.viewportOptions = {
...viewport.viewportOptions,
viewportType: 'volume',
viewportType: referencedDisplaySet.isReconstructable ? 'volume' : 'stack',
needsRerendering: true,
};
const viewportId = viewport.viewportId;

const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const prevCamera = csViewport.getCamera();

// only run the createSegmentationForVolume for the targetViewportId
// only run the createSegmentation for the targetViewportId when volume cache is available
// since the rest will get handled by cornerstoneViewportService
if (volumeExists && viewportId === targetViewportId) {
await createSegmentationForVolume();
await createSegmentation();
return;
}
// TODO: Read from _imageCache and create segmentation when applicable

const newViewportEvent = referencedDisplaySet.isReconstructable
? Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME
: Enums.Events.STACK_VIEWPORT_NEW_STACK;

const createNewSegmentationWhenVolumeMounts = async evt => {
const eventTriggerer = referencedDisplaySet.isReconstructable
? csViewport.element
: eventTarget;

const createNewSegmentationOnNewViewport = async evt => {
const isTheActiveViewportVolumeMounted = evt.detail.volumeActors?.find(ac =>
ac.uid.includes(referencedDisplaySetInstanceUID)
);
Expand All @@ -82,25 +99,19 @@ async function updateViewportsForSegmentationRendering({
const volumeViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
volumeViewport.setCamera(prevCamera);

volumeViewport.element.removeEventListener(
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
createNewSegmentationWhenVolumeMounts
);
eventTriggerer.removeEventListener(newViewportEvent, createNewSegmentationOnNewViewport);

if (!isTheActiveViewportVolumeMounted) {
if (referencedDisplaySet.isReconstructable && !isTheActiveViewportVolumeMounted) {
// it means it is one of those other updated viewports so just update the camera
return;
}

if (viewportId === targetViewportId) {
await createSegmentationForVolume();
await createSegmentation();
}
};

csViewport.element.addEventListener(
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
createNewSegmentationWhenVolumeMounts
);
eventTriggerer.addEventListener(newViewportEvent, createNewSegmentationOnNewViewport);
});

// Set the displaySets for the viewports that require to be updated
Expand Down Expand Up @@ -174,7 +185,7 @@ function getUpdatedViewportsForSegmentation({
viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
viewportOptions: {
viewportType: 'volume',
viewportType: viewport.viewportType,
needsRerendering: true,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function OHIFCornerstoneSEGViewport(props) {
{...props}
displaySets={[referencedDisplaySet, segDisplaySet]}
viewportOptions={{
viewportType: 'volume',
viewportType: referencedDisplaySet.isReconstructable ? 'volume' : 'stack',
toolGroupId: toolGroupId,
orientation: viewportOptions.orientation,
viewportId: viewportOptions.viewportId,
Expand Down
Loading

0 comments on commit 6d34ef6

Please sign in to comment.