diff --git a/packages/adapters/examples/segmentationStack/index.ts b/packages/adapters/examples/segmentationStack/index.ts
index 422490e986..0c7f839994 100644
--- a/packages/adapters/examples/segmentationStack/index.ts
+++ b/packages/adapters/examples/segmentationStack/index.ts
@@ -2,65 +2,45 @@ import { api } from "dicomweb-client";
import * as cornerstone from "@cornerstonejs/core";
import * as cornerstoneTools from "@cornerstonejs/tools";
-import * as cornerstoneDicomImageLoader from "@cornerstonejs/dicom-image-loader";
import * as cornerstoneAdapters from "@cornerstonejs/adapters";
import { dicomMap } from "./demo";
import {
- addBrushSizeSlider,
addButtonToToolbar,
addDropdownToToolbar,
- addLabelToToolbar,
addManipulationBindings,
+ addToggleButtonToToolbar,
addUploadToToolbar,
createImageIdsAndCacheMetaData,
- createInfoSection,
initDemo,
labelmapTools,
setTitleAndDescription
} from "../../../../utils/demo/helpers";
-import dcmjs from "dcmjs";
+import { BrushTool } from "@cornerstonejs/tools";
// This is for debugging purposes
console.warn(
"Click on index.ts to open source code for this example --------->"
);
-const {
- Enums: csEnums,
- RenderingEngine,
- cache,
- eventTarget,
- imageLoader,
- metaData,
- utilities: csUtilities
-} = cornerstone;
+const { Enums: csEnums, RenderingEngine, utilities: csUtilities } = cornerstone;
const { ViewportType } = csEnums;
-
-const {
- Enums: csToolsEnums,
- ToolGroupManager,
- segmentation: csToolsSegmentation,
- utilities: csToolsUtilities
-} = cornerstoneTools;
-const { MouseBindings } = csToolsEnums;
-
-const { wadouri } = cornerstoneDicomImageLoader;
-
-const { adaptersSEG, helpers } = cornerstoneAdapters;
-const { Cornerstone3D } = adaptersSEG;
-const { downloadDICOMData } = helpers;
-
-//
-let renderingEngine;
-const renderingEngineId = "MY_RENDERING_ENGINE_ID";
-let toolGroup;
-const toolGroupId = "MY_TOOL_GROUP_ID";
-const viewportIds = ["CT_STACK"];
-let imageIds: string[] = [];
-
+import {
+ readDicom,
+ loadDicom,
+ readSegmentation,
+ loadSegmentation,
+ exportSegmentation,
+ restart,
+ getSegmentationIds,
+ handleFileSelect,
+ handleDragOver
+} from "../segmentationVolume/utils";
+
+const referenceImageIds: string[] = [];
+const segImageIds: string[] = [];
// ======== Set up page ======== //
setTitleAndDescription(
@@ -68,22 +48,8 @@ setTitleAndDescription(
"Here we demonstrate how to import or export a DICOM SEG from a Cornerstone3D stack."
);
-// TODO
-const descriptionContainer = document.getElementById(
- "demo-description-container"
-);
-
-const warn = document.createElement("div");
-descriptionContainer.prepend(warn);
-
-const textA = document.createElement("p");
-textA.style.color = "red";
-textA.innerHTML =
- "Warning:
Load or import into dicom or segmentation, just one frame. Several frames are not yet completed.
When exporting segmentation, also just one frame.";
-warn.appendChild(textA);
-// END TODO
-
const size = "500px";
+const skipOverlapping = false;
const demoToolbar = document.getElementById("demo-toolbar");
@@ -95,18 +61,6 @@ const group2 = document.createElement("div");
group2.style.marginBottom = "10px";
demoToolbar.appendChild(group2);
-const group3 = document.createElement("div");
-group3.style.marginBottom = "10px";
-demoToolbar.appendChild(group3);
-
-const group4 = document.createElement("div");
-group4.style.marginBottom = "10px";
-demoToolbar.appendChild(group4);
-
-const group5 = document.createElement("div");
-group5.style.marginBottom = "10px";
-demoToolbar.appendChild(group5);
-
const content = document.getElementById("content");
const viewportGrid = document.createElement("div");
@@ -119,975 +73,183 @@ viewportGrid.addEventListener("drop", handleFileSelect, false);
const element1 = document.createElement("div");
element1.style.width = size;
element1.style.height = size;
+const element2 = document.createElement("div");
+element2.style.width = size;
+element2.style.height = size;
+const element3 = document.createElement("div");
+element3.style.width = size;
+element3.style.height = size;
// Disable right click context menu so we can have right click tools
element1.oncontextmenu = e => e.preventDefault();
+element2.oncontextmenu = e => e.preventDefault();
+element3.oncontextmenu = e => e.preventDefault();
viewportGrid.appendChild(element1);
+viewportGrid.appendChild(element2);
+viewportGrid.appendChild(element3);
content.appendChild(viewportGrid);
-
-createInfoSection(content)
- .addInstruction('You can try configuring "dev" in the console:')
- .openNestedSection()
- .addInstruction("fetchDicom")
- .closeNestedSection();
-
-// ============================= //
-
-let devConfig = {
- ...dicomMap.values().next().value
-};
-const dev = {
- get getConfig() {
- return devConfig;
- },
- set setConfig(obj: object) {
- devConfig = csUtilities.deepMerge(devConfig, obj);
- }
+const info = document.createElement("div");
+content.appendChild(info);
+
+function addInstruction(text) {
+ const instructions = document.createElement("p");
+ instructions.innerText = `- ${text}`;
+ info.appendChild(instructions);
+}
+
+const state = {
+ renderingEngine: null,
+ renderingEngineId: "MY_RENDERING_ENGINE_ID",
+ toolGroup: null,
+ toolGroupId: "MY_TOOL_GROUP_ID",
+ viewportIds: ["CT_AXIAL"],
+ segmentationId: "LOAD_SEG_ID:" + cornerstone.utilities.uuidv4(),
+ referenceImageIds: [],
+ segImageIds: [],
+ skipOverlapping: false,
+ devConfig: { ...dicomMap.values().next().value }
};
-(window as any).dev = dev;
-
-// ============================= //
-
-async function fetchDicom() {
- restart();
-
- // Get Cornerstone imageIds for the source data and fetch metadata into RAM
- imageIds = await createImageIdsAndCacheMetaData(dev.getConfig.fetchDicom);
-
- // TODO
- if (
- dev.getConfig.fetchDicom.StudyInstanceUID ===
- "1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046"
- ) {
- imageIds = imageIds.slice(50, 51);
- }
-
- //
- imageIds = imageIds.slice(0, 1);
-
- await loadDicom(imageIds);
-}
-async function readDicom(files: FileList) {
- restart();
-
- // TODO
- const arr = [files[0]];
-
- imageIds = [];
-
- for (const file of arr) {
- const imageId = wadouri.fileManager.add(file);
+viewportGrid.addEventListener("dragover", evt => handleDragOver(evt), false);
+viewportGrid.addEventListener(
+ "drop",
+ evt => handleFileSelect(evt, state),
+ false
+);
- await imageLoader.loadAndCacheImage(imageId);
+function loadDicom() {
+ restart(state);
- imageIds.push(imageId);
- }
+ const { toolGroup, viewportIds, renderingEngineId, renderingEngine } =
+ state;
- await loadDicom(imageIds);
-}
-
-async function loadDicom(imageIds: string[]) {
- //
toolGroup.addViewport(viewportIds[0], renderingEngineId);
- //
- const viewport = renderingEngine.getViewport(viewportIds[0]);
- //
- await viewport.setStack(imageIds, 0);
-
- // Generate segmentation id
- const newSegmentationId = "NEW_SEG_ID:" + csUtilities.uuidv4();
- // Add some segmentations based on the source data stack
- await addSegmentationsToState(newSegmentationId);
- // Update the dropdown
- updateSegmentationDropdown();
-
- // Render the image
- renderingEngine.renderViewports(viewportIds);
-}
-
-async function fetchSegmentation() {
- if (!imageIds.length) {
- return;
- }
-
- const configSeg = dev.getConfig.fetchSegmentation;
-
- // @ts-expect-error
- const client = new api.DICOMwebClient({
- url: configSeg.wadoRsRoot
- });
- const arrayBuffer = await client.retrieveInstance({
- studyInstanceUID: configSeg.StudyInstanceUID,
- seriesInstanceUID: configSeg.SeriesInstanceUID,
- sopInstanceUID: configSeg.SOPInstanceUID
- });
-
- //
- await loadSegmentation(arrayBuffer);
-}
-
-async function importSegmentation(files: FileList) {
- if (!imageIds.length) {
- return;
- }
-
- for (const file of files) {
- await readSegmentation(file);
- }
-}
-
-async function readSegmentation(file: File) {
- const imageId = wadouri.fileManager.add(file);
-
- const image = await imageLoader.loadAndCacheImage(imageId);
-
- if (!image) {
- return;
- }
-
- const instance = metaData.get("instance", imageId);
-
- if (instance.Modality !== "SEG") {
- console.error("This is not segmentation: " + file.name);
- return;
- }
-
- const arrayBuffer = image.data.byteArray.buffer;
-
- loadSegmentation(arrayBuffer);
-}
-
-async function loadSegmentation(arrayBuffer: ArrayBuffer) {
- // Generate segmentation id
- const newSegmentationId = "LOAD_SEG_ID:" + csUtilities.uuidv4();
-
- // Add some segmentations based on the source data stack
- const derivedImages = await addSegmentationsToState(newSegmentationId);
- // Update the dropdown
- updateSegmentationDropdown(newSegmentationId);
-
- //
- const generateToolState =
- await Cornerstone3D.Segmentation.generateToolState(
- imageIds,
- arrayBuffer,
- metaData
- );
-
- //
- derivedImages.forEach(image => {
- const cachedImage = cache.getImage(image.imageId);
-
- if (cachedImage) {
- const pixelData = cachedImage.getPixelData();
-
- //
- pixelData.set(
- new Uint8Array(generateToolState.labelmapBufferArray[0])
- );
- }
- });
-
- // TODO
- setTimeout(function () {
- //
- csToolsSegmentation.triggerSegmentationEvents.triggerSegmentationDataModified(
- newSegmentationId
- );
- }, 200);
-}
-
-function exportSegmentation() {
- //
- const segmentationIds = getSegmentationIds();
- //
- if (!segmentationIds.length) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- // Get active segmentation representation
-
- if (!activeSegmentation) {
- return;
- }
-
- //
- const labelmap = activeSegmentation.representationData[
- csToolsEnums.SegmentationRepresentations.Labelmap
- ] as cornerstoneTools.Types.LabelmapToolOperationDataStack;
+ const viewport = state.renderingEngine.getStackViewport(viewportIds[0]);
- //
- if (labelmap.imageIds) {
- //
- labelmap.imageIds.forEach(async (derivedImagesId: string) => {
- //
- await imageLoader.loadAndCacheImage(derivedImagesId);
- //
- const cacheImage = cache.getImage(derivedImagesId);
+ viewport.setStack(state.referenceImageIds);
+ cornerstoneTools.utilities.stackContextPrefetch.enable(element1);
- //
- const cacheSegmentationImage = cache.getImage(derivedImagesId);
-
- // TODO
- // generateLabelMaps2DFrom3D required "scalarData" and "dimensions"
- cacheSegmentationImage.scalarData =
- cacheSegmentationImage.getPixelData();
- cacheSegmentationImage.dimensions = [
- cacheSegmentationImage.columns,
- cacheSegmentationImage.rows,
- 1
- ];
-
- //
- const labelmapData =
- Cornerstone3D.Segmentation.generateLabelMaps2DFrom3D(
- cacheSegmentationImage
- );
-
- // Generate fake metadata as an example
- labelmapData.metadata = [];
- labelmapData.segmentsOnLabelmap.forEach((segmentIndex: number) => {
- const color =
- csToolsSegmentation.config.color.getSegmentIndexColor(
- viewportIds[0],
- activeSegmentation.segmentationId,
- segmentIndex
- );
-
- const segmentMetadata = generateMockMetadata(
- segmentIndex,
- color
- );
- labelmapData.metadata[segmentIndex] = segmentMetadata;
- });
-
- // TODO
- // https://github.com/cornerstonejs/cornerstone3D/issues/1059#issuecomment-2181016046
- const generatedSegmentation =
- Cornerstone3D.Segmentation.generateSegmentation(
- [cacheImage, cacheImage],
- labelmapData,
- metaData
- );
-
- downloadDICOMData(generatedSegmentation.dataset, "mySEG.dcm");
- });
- }
-}
-
-async function addActiveSegmentation() {
- if (!imageIds.length) {
- return;
- }
-
- // Generate segmentation id
- const newSegmentationId = "NEW_SEG_ID:" + csUtilities.uuidv4();
- // Add some segmentations based on the source data stack
- await addSegmentationsToState(newSegmentationId);
- // Update the dropdown
- updateSegmentationDropdown(newSegmentationId);
-}
-
-function removeActiveSegmentation() {
- //
- const segmentationIds = getSegmentationIds();
- //
- if (!segmentationIds.length) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- // Get active segmentation representation
-
- if (!activeSegmentation) {
- return;
- }
-
- //
- csToolsSegmentation.removeSegmentationRepresentations(viewportIds[0], {
- segmentationId: activeSegmentation.segmentationId
- });
-
- //
- csToolsSegmentation.state.removeSegmentation(
- activeSegmentation.segmentationId
- );
-
- //
- const labelmap = activeSegmentation.representationData[
- csToolsEnums.SegmentationRepresentations.Labelmap
- ] as cornerstoneTools.Types.LabelmapToolOperationDataStack;
-
- //
- if (labelmap.imageIds) {
- //
- labelmap.imageIds.forEach((derivedImagesId: string) => {
- //
- cache.removeImageLoadObject(derivedImagesId);
- });
- }
-
- // Update the dropdown
- updateSegmentationDropdown();
-}
-
-function plusActiveSegment() {
- if (!imageIds.length) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- //
- if (!activeSegmentation) {
- return;
- }
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- if (activeSegmentIndex + 1 <= 255) {
- csToolsSegmentation.segmentIndex.setActiveSegmentIndex(
- activeSegmentation.segmentationId,
- activeSegmentIndex + 1
- );
-
- // Update the dropdown
- updateSegmentDropdown();
- }
-}
-
-function minusActiveSegment() {
- if (!imageIds.length) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- //
- if (!activeSegmentation) {
- return;
- }
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- if (activeSegmentIndex - 1 >= 1) {
- csToolsSegmentation.segmentIndex.setActiveSegmentIndex(
- activeSegmentation.segmentationId,
- activeSegmentIndex - 1
- );
-
- // Update the dropdown
- updateSegmentDropdown();
- }
-}
-
-function removeActiveSegment() {
- if (!imageIds.length) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- //
- if (!activeSegmentation) {
- return;
- }
-
- //
- const labelmap = activeSegmentation.representationData[
- csToolsEnums.SegmentationRepresentations.Labelmap
- ] as cornerstoneTools.Types.LabelmapToolOperationDataStack;
-
- //
- const modifiedFrames = new Set();
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- //
- if (labelmap.imageIds) {
- //
- labelmap.imageIds.forEach((derivedImagesId: string) => {
- // Get image
- const image = cache.getImage(derivedImagesId);
-
- // Get pixel data
- const pixelData = image.getPixelData();
-
- //
- const frameLength = image.columns * image.rows;
- const numFrames = 1;
-
- //
- let index = 0;
-
- //
- for (let f = 0; f < numFrames; f++) {
- //
- for (let p = 0; p < frameLength; p++) {
- if (pixelData[index] === activeSegmentIndex) {
- pixelData[index] = 0;
-
- modifiedFrames.add(f);
- }
-
- index++;
- }
- }
- });
- }
-
- //
- const modifiedFramesArray = Array.from(modifiedFrames);
-
- // Event trigger (SEGMENTATION_DATA_MODIFIED)
- csToolsSegmentation.triggerSegmentationEvents.triggerSegmentationDataModified(
- activeSegmentation.segmentationId,
- modifiedFramesArray
- );
-
- // Update the dropdown
- updateSegmentDropdown();
+ renderingEngine.render();
}
// ============================= //
-
-// TODO
-const inputConfig = {
- attr: {
- multiple: false
- }
-};
-
-addDropdownToToolbar({
- id: "DICOM_DROPDOWN",
- style: {
- marginRight: "10px"
- },
- options: { map: dicomMap, defaultIndex: 0 },
- onSelectedValueChange: (key, value) => {
- dev.setConfig = value;
- },
- container: group1
-});
-
addButtonToToolbar({
id: "LOAD_DICOM",
title: "Load DICOM",
- style: {
- marginRight: "5px"
+ onClick: async () => {
+ state.referenceImageIds = await createImageIdsAndCacheMetaData(
+ state.devConfig.fetchDicom
+ );
+
+ loadDicom();
},
- onClick: fetchDicom,
container: group1
});
addButtonToToolbar({
id: "LOAD_SEGMENTATION",
title: "Load SEG",
- style: {
- marginRight: "5px"
+ onClick: async () => {
+ if (!state.referenceImageIds.length) {
+ alert("load source dicom first");
+ return;
+ }
+
+ const configSeg = state.devConfig.fetchSegmentation;
+ const client = new api.DICOMwebClient({
+ url: configSeg.wadoRsRoot
+ });
+ const arrayBuffer = await client.retrieveInstance({
+ studyInstanceUID: configSeg.StudyInstanceUID,
+ seriesInstanceUID: configSeg.SeriesInstanceUID,
+ sopInstanceUID: configSeg.SOPInstanceUID
+ });
+
+ await loadSegmentation(arrayBuffer, state);
},
- onClick: fetchSegmentation,
container: group1
});
addUploadToToolbar({
id: "IMPORT_DICOM",
title: "Import DICOM",
- style: {
- marginRight: "5px"
+ onChange: (files: FileList) => {
+ readDicom(files, state);
+ loadDicom();
},
- onChange: readDicom,
- container: group2,
- input: inputConfig
+ container: group2
});
addUploadToToolbar({
id: "IMPORT_SEGMENTATION",
title: "Import SEG",
- style: {
- marginRight: "5px"
+ onChange: async (files: FileList) => {
+ for (const file of files) {
+ await readSegmentation(file, state);
+ }
},
- onChange: importSegmentation,
- container: group2,
- input: inputConfig
+ container: group2
});
addButtonToToolbar({
id: "EXPORT_SEGMENTATION",
title: "Export SEG",
- onClick: exportSegmentation,
- container: group2
-});
-
-addDropdownToToolbar({
- id: "LABELMAP_TOOLS_DROPDOWN",
- style: {
- width: "150px",
- marginRight: "10px"
- },
- options: { map: labelmapTools.toolMap },
- onSelectedValueChange: nameAsStringOrNumber => {
- const tool = String(nameAsStringOrNumber);
-
- const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
-
- if (!toolGroup) {
- return;
- }
-
- // Set the currently active tool disabled
- const toolName = toolGroup.getActivePrimaryMouseButtonTool();
-
- if (toolName) {
- toolGroup.setToolDisabled(toolName);
- }
-
- toolGroup.setToolActive(tool, {
- bindings: [{ mouseButton: MouseBindings.Primary }]
- });
- },
- labelText: "Tools: ",
- container: group3
-});
-
-addBrushSizeSlider({
- toolGroupId: toolGroupId,
- container: group3
-});
-
-addDropdownToToolbar({
- id: "ACTIVE_SEGMENTATION_DROPDOWN",
- style: {
- width: "200px",
- marginRight: "10px"
- },
- options: { values: [], defaultValue: "" },
- placeholder: "No active segmentation...",
- onSelectedValueChange: nameAsStringOrNumber => {
- const segmentationId = String(nameAsStringOrNumber);
-
- csToolsSegmentation.activeSegmentation.setActiveSegmentation(
- viewportIds[0],
- segmentationId
- );
-
- // Update the dropdown
- updateSegmentationDropdown(segmentationId);
- },
- labelText: "Set Active Segmentation: ",
- container: group4
+ onClick: () => exportSegmentation(state)
});
-addButtonToToolbar({
- id: "ADD_ACTIVE_SEGMENTATION",
- style: {
- marginRight: "10px"
+addToggleButtonToToolbar({
+ id: "SKIP_OVERLAPPING",
+ title: "Override Overlapping Segments",
+ onClick: () => {
+ state.skipOverlapping = !state.skipOverlapping;
},
- title: "Add Active Segmentation",
- onClick: addActiveSegmentation,
- container: group4
-});
-
-addButtonToToolbar({
- id: "REMOVE_ACTIVE_SEGMENTATION",
- title: "Remove Active Segmentation",
- onClick: removeActiveSegmentation,
- container: group4
-});
-
-addLabelToToolbar({
- id: "CURRENT_ACTIVE_SEGMENT_LABEL",
- title: "Current Active Segment: 1",
- style: {
- marginRight: "10px"
- },
- container: group5
-});
-
-addButtonToToolbar({
- id: "PLUS_ACTIVE_SEGMENT",
- attr: {
- title: "Plus Active Segment"
- },
- style: {
- marginRight: "10px"
- },
- title: "+",
- onClick: plusActiveSegment,
- container: group5
-});
-
-addButtonToToolbar({
- id: "MINUS_ACTIVE_SEGMENT",
- attr: {
- title: "Minus Active Segment"
- },
- style: {
- marginRight: "10px"
- },
- title: "-",
- onClick: minusActiveSegment,
- container: group5
-});
-
-addDropdownToToolbar({
- id: "ACTIVE_SEGMENT_DROPDOWN",
- style: {
- width: "200px",
- marginRight: "10px"
- },
- options: { values: [], defaultValue: "" },
- placeholder: "No active segment...",
- onSelectedValueChange: nameAsStringOrNumber => {
- const segmentIndex = Number(nameAsStringOrNumber);
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
-
- csToolsSegmentation.segmentIndex.setActiveSegmentIndex(
- activeSegmentation.segmentationId,
- segmentIndex
- );
-
- // Update the dropdown
- updateSegmentDropdown();
- },
- labelText: "Set Active Segment: ",
- container: group5
-});
-
-addButtonToToolbar({
- id: "REMOVE_ACTIVE_SEGMENT",
- title: "Remove Active Segment",
- onClick: removeActiveSegment,
- container: group5
+ container: group1
});
-
// ============================= //
-// If you import the dicom again, before clearing the cache or starting from scratch
-function restart() {
- if (!imageIds.length) {
- return;
- }
-
- //
- imageIds.forEach(imageId => {
- if (cache.getImage(imageId)) {
- cache.removeImageLoadObject(imageId);
- }
- });
-
- //
- csToolsSegmentation.removeSegmentationRepresentations(viewportIds[0]);
-
- //
- const segmentations = csToolsSegmentation.state.getSegmentations();
- //
- segmentations.forEach(segmentation => {
- csToolsSegmentation.state.removeSegmentation(
- segmentation.segmentationId
- );
-
- //
- const labelmap = segmentation.representationData[
- csToolsEnums.SegmentationRepresentations.Labelmap
- ] as cornerstoneTools.Types.LabelmapToolOperationDataStack;
-
- //
- if (labelmap.imageIds) {
- //
- labelmap.imageIds.forEach(derivedImagesId => {
- cache.removeImageLoadObject(derivedImagesId);
- });
- }
- });
-}
-
-function getSegmentationIds(): string[] {
- return csToolsSegmentation.state
- .getSegmentations()
- .map(x => x.segmentationId);
-}
-
-async function addSegmentationsToState(segmentationId: string) {
- //
- const derivedImages =
- imageLoader.createAndCacheDerivedLabelmapImages(imageIds);
-
- // Add the segmentations to state
- csToolsSegmentation.addSegmentations([
- {
- segmentationId,
- representation: {
- type: csToolsEnums.SegmentationRepresentations.Labelmap,
- data: {
- imageIds: derivedImages.map(x => x.imageId)
- }
- }
- }
- ]);
-
- // Add the segmentation representation to the toolgroup
- await csToolsSegmentation.addSegmentationRepresentations(viewportIds[0], [
- {
- segmentationId,
- type: csToolsEnums.SegmentationRepresentations.Labelmap
- }
- ]);
-
- //
- return derivedImages;
-}
-
-function generateMockMetadata(segmentIndex, color) {
- const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB(
- color.slice(0, 3).map(value => value / 255)
- ).map(value => Math.round(value));
-
- return {
- SegmentedPropertyCategoryCodeSequence: {
- CodeValue: "T-D0050",
- CodingSchemeDesignator: "SRT",
- CodeMeaning: "Tissue"
- },
- SegmentNumber: segmentIndex.toString(),
- SegmentLabel: "Tissue " + segmentIndex.toString(),
- SegmentAlgorithmType: "SEMIAUTOMATIC",
- SegmentAlgorithmName: "Slicer Prototype",
- RecommendedDisplayCIELabValue,
- SegmentedPropertyTypeCodeSequence: {
- CodeValue: "T-D0050",
- CodingSchemeDesignator: "SRT",
- CodeMeaning: "Tissue"
- }
- };
-}
-
-function updateSegmentationDropdown(activeSegmentationId?: string) {
- const dropdown = document.getElementById(
- "ACTIVE_SEGMENTATION_DROPDOWN"
- ) as HTMLSelectElement;
-
- dropdown.innerHTML = "";
-
- // Get segmentationIds
- const segmentationIds = getSegmentationIds();
-
- //
- if (segmentationIds.length) {
- segmentationIds.forEach((segmentationId: string) => {
- const option = document.createElement("option");
- option.value = segmentationId;
- option.innerText = segmentationId;
- dropdown.appendChild(option);
- });
-
- if (activeSegmentationId) {
- dropdown.value = activeSegmentationId;
- }
- }
- //
- else {
- const option = document.createElement("option");
- option.setAttribute("disabled", "");
- option.setAttribute("hidden", "");
- option.setAttribute("selected", "");
- option.innerText = "No active segmentation...";
- dropdown.appendChild(option);
- }
-
- //
- updateSegmentDropdown();
-}
-
-function updateSegmentDropdown() {
- const dropdown = document.getElementById(
- "ACTIVE_SEGMENT_DROPDOWN"
- ) as HTMLSelectElement;
-
- dropdown.innerHTML = "";
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
-
- //
- if (!activeSegmentation) {
- const option = document.createElement("option");
- option.setAttribute("disabled", "");
- option.setAttribute("hidden", "");
- option.setAttribute("selected", "");
- option.innerText = "No active segment...";
- dropdown.appendChild(option);
-
- return;
- }
-
- //
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- const segmentIndices =
- csToolsUtilities.segmentation.getUniqueSegmentIndices(
- activeSegmentation.segmentationId
- );
-
- //
- const optionDraw = function () {
- const option = document.createElement("option");
- option.setAttribute("disabled", "");
- option.setAttribute("hidden", "");
- option.setAttribute("selected", "");
- option.innerText = "Draw or set segment index";
- dropdown.appendChild(option);
- };
-
- //
- if (segmentIndices.length) {
- if (!segmentIndices.includes(activeSegmentIndex)) {
- optionDraw();
- }
-
- segmentIndices.forEach((segmentIndex: number) => {
- const option = document.createElement("option");
- option.value = segmentIndex.toString();
- option.innerText = segmentIndex.toString();
- dropdown.appendChild(option);
- });
-
- if (segmentIndices.includes(activeSegmentIndex)) {
- dropdown.value = activeSegmentIndex.toString();
- }
- }
- //
- else {
- optionDraw();
- }
-
- //
- updateSegmentLabel();
-}
-
-function updateSegmentLabel() {
- const label = document.getElementById(
- "CURRENT_ACTIVE_SEGMENT_LABEL"
- ) as HTMLSelectElement;
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- label.innerHTML = "Current Active Segment: " + activeSegmentIndex;
-}
-
-function handleFileSelect(evt) {
- evt.stopPropagation();
- evt.preventDefault();
-
- //
- const files = evt.dataTransfer.files;
-
- //
- readDicom(files);
-}
-
-function handleDragOver(evt) {
- evt.stopPropagation();
- evt.preventDefault();
- evt.dataTransfer.dropEffect = "copy";
-}
-
// ============================= //
/**
* Runs the demo
*/
async function run() {
- // Init Cornerstone and related libraries
await initDemo();
- //
- labelmapTools.toolMap.forEach(x => {
- if (x.configuration?.preview) {
- x.configuration.preview.enabled = false;
- }
+ state.toolGroup = cornerstoneTools.ToolGroupManager.createToolGroup(
+ state.toolGroupId
+ );
+ addManipulationBindings(state.toolGroup, {
+ toolMap: labelmapTools.toolMap
});
- // Define tool groups to add the segmentation display tool to
- toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
- addManipulationBindings(toolGroup, { toolMap: labelmapTools.toolMap });
- //
- // Instantiate a rendering engine
- renderingEngine = new RenderingEngine(renderingEngineId);
+ cornerstoneTools.addTool(BrushTool);
+
+ state.toolGroup.addToolInstance("CircularBrush", BrushTool.toolName, {
+ activeStrategy: "FILL_INSIDE_CIRCLE"
+ });
+
+ state.toolGroup.setToolActive("CircularBrush", {
+ bindings: [
+ { mouseButton: cornerstoneTools.Enums.MouseBindings.Primary }
+ ]
+ });
+
+ state.renderingEngine = new cornerstone.RenderingEngine(
+ state.renderingEngineId
+ );
- // Create the viewports
const viewportInputArray = [
{
- viewportId: viewportIds[0],
- type: ViewportType.STACK,
- element: element1,
- defaultOptions: {
- background: [0.2, 0, 0.2]
- }
+ viewportId: state.viewportIds[0],
+ type: cornerstone.Enums.ViewportType.STACK,
+ element: element1
}
];
- //
- renderingEngine.setViewports(viewportInputArray);
-
- //
- eventTarget.addEventListener(
- csToolsEnums.Events.SEGMENTATION_DATA_MODIFIED,
- function () {
- updateSegmentDropdown();
- }
- );
+ state.renderingEngine.setViewports(viewportInputArray);
}
run();
diff --git a/packages/adapters/examples/segmentationVolume/index.ts b/packages/adapters/examples/segmentationVolume/index.ts
index 54498b9c51..873452c7ce 100644
--- a/packages/adapters/examples/segmentationVolume/index.ts
+++ b/packages/adapters/examples/segmentationVolume/index.ts
@@ -2,70 +2,37 @@ import { api } from "dicomweb-client";
import * as cornerstone from "@cornerstonejs/core";
import * as cornerstoneTools from "@cornerstonejs/tools";
-import * as cornerstoneDicomImageLoader from "@cornerstonejs/dicom-image-loader";
-import * as cornerstoneAdapters from "@cornerstonejs/adapters";
import { dicomMap } from "./demo";
import {
- addBrushSizeSlider,
addButtonToToolbar,
- addDropdownToToolbar,
- addLabelToToolbar,
addManipulationBindings,
+ addToggleButtonToToolbar,
addUploadToToolbar,
createImageIdsAndCacheMetaData,
- createInfoSection,
initDemo,
labelmapTools,
setTitleAndDescription
} from "../../../../utils/demo/helpers";
-import dcmjs from "dcmjs";
+import { BrushTool } from "@cornerstonejs/tools";
// This is for debugging purposes
console.warn(
"Click on index.ts to open source code for this example --------->"
);
-const {
- Enums: csEnums,
- RenderingEngine,
- cache,
- eventTarget,
- imageLoader,
- metaData,
- setVolumesForViewports,
- utilities: csUtilities,
- volumeLoader
-} = cornerstone;
-const { ViewportType } = csEnums;
-
-const {
- Enums: csToolsEnums,
- ToolGroupManager,
- segmentation: csToolsSegmentation,
- utilities: csToolsUtilities
-} = cornerstoneTools;
-const { MouseBindings } = csToolsEnums;
-
-const { wadouri } = cornerstoneDicomImageLoader;
-
-const { adaptersSEG, helpers } = cornerstoneAdapters;
-const { Cornerstone3D } = adaptersSEG;
-const { downloadDICOMData } = helpers;
-
-//
-let renderingEngine;
-const renderingEngineId = "MY_RENDERING_ENGINE_ID";
-let toolGroup;
-const toolGroupId = "MY_TOOL_GROUP_ID";
-const viewportIds = ["CT_AXIAL", "CT_SAGITTAL", "CT_CORONAL"];
-let imageIds: string[] = [];
-const volumeLoaderScheme = "cornerstoneStreamingImageVolume";
-let volumeId;
-
-// ======== Set up page ======== //
+const { utilities: csUtilities } = cornerstone;
+import {
+ readDicom,
+ readSegmentation,
+ loadSegmentation,
+ exportSegmentation,
+ handleFileSelect,
+ handleDragOver,
+ restart
+} from "../segmentationVolume/utils";
setTitleAndDescription(
"DICOM SEG VOLUME",
@@ -84,18 +51,6 @@ const group2 = document.createElement("div");
group2.style.marginBottom = "10px";
demoToolbar.appendChild(group2);
-const group3 = document.createElement("div");
-group3.style.marginBottom = "10px";
-demoToolbar.appendChild(group3);
-
-const group4 = document.createElement("div");
-group4.style.marginBottom = "10px";
-demoToolbar.appendChild(group4);
-
-const group5 = document.createElement("div");
-group5.style.marginBottom = "10px";
-demoToolbar.appendChild(group5);
-
const content = document.getElementById("content");
const viewportGrid = document.createElement("div");
@@ -125,916 +80,221 @@ viewportGrid.appendChild(element2);
viewportGrid.appendChild(element3);
content.appendChild(viewportGrid);
-
-createInfoSection(content)
- .addInstruction("Viewports:")
- .openNestedSection()
- .addInstruction("Axial | Sagittal | Coronal")
- .closeNestedSection();
-
-createInfoSection(content)
- .addInstruction('You can try configuring "dev" in the console:')
- .openNestedSection()
- .addInstruction("fetchDicom")
- .addInstruction("fetchSegmentation")
- .closeNestedSection();
-
-// ============================= //
-
-let devConfig = {
- ...dicomMap.values().next().value
-};
-const dev = {
- get getConfig() {
- return devConfig;
- },
- set setConfig(obj: object) {
- devConfig = csUtilities.deepMerge(devConfig, obj);
- }
-};
-(window as any).dev = dev;
-
-// ============================= //
-
-async function fetchDicom() {
- // Get Cornerstone imageIds for the source data and fetch metadata into RAM
- imageIds = await createImageIdsAndCacheMetaData(dev.getConfig.fetchDicom);
-
- //
- await loadDicom(imageIds.reverse());
+const info = document.createElement("div");
+content.appendChild(info);
+function addInstruction(text) {
+ const instructions = document.createElement("p");
+ instructions.innerText = `- ${text}`;
+ info.appendChild(instructions);
}
-async function readDicom(files: FileList) {
- if (files.length <= 1) {
- console.error(
- "Viewport volume does not support just one image, it must be two or more images"
- );
- return;
- }
-
- imageIds = [];
-
- for (const file of files) {
- const imageId = wadouri.fileManager.add(file);
-
- await imageLoader.loadAndCacheImage(imageId);
+addInstruction(
+ "Load a source DICOM volume first, either using the 'Load DICOM' button or by dragging and dropping multiple DICOM files onto the viewports."
+);
+addInstruction(
+ "Once a volume is loaded, you can import a DICOM SEG file by clicking 'Load SEG' "
+);
+addInstruction(
+ "Use the brush tool to edit segmentation labels on the displayed volume."
+);
+addInstruction(
+ "The 'Override Overlapping Segments' toggle allows you to choose how overlapping segments are handled during load"
+);
+addInstruction(
+ "After making changes, click 'Export SEG' to download the updated segmentation as a DICOM SEG file."
+);
+addInstruction(
+ "You can also upload local DICOM images or SEG files by using the 'Import DICOM' and 'Import SEG' buttons."
+);
- imageIds.push(imageId);
- }
+const state = {
+ renderingEngine: null,
+ renderingEngineId: "MY_RENDERING_ENGINE_ID",
+ toolGroup: null,
+ toolGroupId: "MY_TOOL_GROUP_ID",
+ viewportIds: ["CT_AXIAL", "CT_SAGITTAL", "CT_CORONAL"],
+ volumeId: "",
+ segmentationId: "LOAD_SEG_ID:" + cornerstone.utilities.uuidv4(),
+ referenceImageIds: [],
+ skipOverlapping: false,
+ segImageIds: [],
+ devConfig: { ...dicomMap.values().next().value }
+};
- await loadDicom(imageIds);
-}
+viewportGrid.addEventListener("dragover", evt => handleDragOver(evt), false);
+viewportGrid.addEventListener(
+ "drop",
+ evt => handleFileSelect(evt, state),
+ false
+);
-async function loadDicom(imageIds: string[]) {
- restart();
+async function loadDicom() {
+ restart(state);
- // Generate volume id
- volumeId = volumeLoaderScheme + ":" + csUtilities.uuidv4();
+ const volumeLoaderScheme = "cornerstoneStreamingImageVolume";
+ state.volumeId = volumeLoaderScheme + ":" + csUtilities.uuidv4();
- // Define a volume in memory
- const volume = await volumeLoader.createAndCacheVolume(volumeId, {
- imageIds
- });
+ const volume = await cornerstone.volumeLoader.createAndCacheVolume(
+ state.volumeId,
+ {
+ imageIds: state.referenceImageIds
+ }
+ );
- // Generate segmentation id
- const newSegmentationId = "MY_SEG_ID:" + csUtilities.uuidv4();
- // Add some segmentations based on the source data volume
- await addSegmentationsToState(newSegmentationId);
- // Update the dropdown
- updateSegmentationDropdown();
+ const { toolGroup, viewportIds, renderingEngineId, renderingEngine } =
+ state;
- //
toolGroup.addViewport(viewportIds[0], renderingEngineId);
toolGroup.addViewport(viewportIds[1], renderingEngineId);
toolGroup.addViewport(viewportIds[2], renderingEngineId);
- // Set the volume to load
- volume.load();
- // Set volumes on the viewports
- await setVolumesForViewports(renderingEngine, [{ volumeId }], viewportIds);
-
- // Render the image
- renderingEngine.renderViewports(viewportIds);
-}
-
-async function fetchSegmentation() {
- if (!volumeId) {
- return;
- }
-
- const configSeg = dev.getConfig.fetchSegmentation;
-
- const client = new api.DICOMwebClient({
- url: configSeg.wadoRsRoot
- });
- const arrayBuffer = await client.retrieveInstance({
- studyInstanceUID: configSeg.StudyInstanceUID,
- seriesInstanceUID: configSeg.SeriesInstanceUID,
- sopInstanceUID: configSeg.SOPInstanceUID
- });
-
- //
- await loadSegmentation(arrayBuffer);
-}
-
-async function importSegmentation(files: FileList) {
- if (!volumeId) {
- return;
- }
-
- for (const file of files) {
- await readSegmentation(file);
- }
-}
-
-async function readSegmentation(file: File) {
- const imageId = wadouri.fileManager.add(file);
-
- const image = await imageLoader.loadAndCacheImage(imageId);
-
- if (!image) {
- return;
- }
-
- const instance = metaData.get("instance", imageId);
-
- if (instance.Modality !== "SEG") {
- console.error("This is not segmentation: " + file.name);
- return;
- }
-
- const arrayBuffer = image.data.byteArray.buffer;
-
- loadSegmentation(arrayBuffer);
-}
-
-async function loadSegmentation(arrayBuffer: ArrayBuffer) {
- // Generate segmentation id
- const newSegmentationId = "LOAD_SEG_ID:" + csUtilities.uuidv4();
-
- //
- const generateToolState =
- await Cornerstone3D.Segmentation.generateToolState(
- imageIds,
- arrayBuffer,
- metaData
- );
-
- //
- const derivedVolume = await addSegmentationsToState(newSegmentationId);
- derivedVolume?.voxelManager?.setCompleteScalarDataArray?.(
- new Uint8Array(generateToolState.labelmapBufferArray[0])
- );
-
- // Update the dropdown
- updateSegmentationDropdown(newSegmentationId);
-}
-
-async function exportSegmentation() {
- //
- const segmentationIds = getSegmentationIds();
- //
- if (!segmentationIds.length) {
- return;
- }
-
- // Get cache volume
- const cacheVolume = cache.getVolume(volumeId);
- const csImages = cacheVolume.getCornerstoneImages();
-
- // Get active segmentation representation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- viewportIds[0]
- );
-
- const cacheSegmentationVolume = cache.getVolume(
- activeSegmentation.segmentationId
+ await volume.load();
+ await cornerstone.setVolumesForViewports(
+ renderingEngine,
+ [{ volumeId: state.volumeId }],
+ viewportIds
);
- //
- const labelmapData = Cornerstone3D.Segmentation.generateLabelMaps2DFrom3D(
- cacheSegmentationVolume
- );
-
- // Generate fake metadata as an example
- labelmapData.metadata = [];
- labelmapData.segmentsOnLabelmap.forEach(segmentIndex => {
- const color = csToolsSegmentation.config.color.getSegmentIndexColor(
- viewportIds[0],
- activeSegmentation.segmentationId,
- segmentIndex
- );
-
- const segmentMetadata = generateMockMetadata(segmentIndex, color);
- labelmapData.metadata[segmentIndex] = segmentMetadata;
- });
-
- //
- const generatedSegmentation =
- Cornerstone3D.Segmentation.generateSegmentation(
- csImages,
- labelmapData,
- metaData
- );
-
- downloadDICOMData(generatedSegmentation.dataset, "mySEG.dcm");
-}
-
-async function addActiveSegmentation() {
- if (!volumeId) {
- return;
- }
-
- // Generate segmentation id
- const newSegmentationId = "NEW_SEG_ID:" + csUtilities.uuidv4();
- // Add some segmentations based on the source data stack
- await addSegmentationsToState(newSegmentationId);
- // Update the dropdown
- updateSegmentationDropdown(newSegmentationId);
-}
-
-function removeActiveSegmentation() {
- //
- const segmentationIds = getSegmentationIds();
- //
- if (segmentationIds.length <= 1) {
- return;
- }
-
- // Get active segmentation representation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- viewportIds[0]
- );
-
- //
- csToolsSegmentation.removeSegmentationRepresentations(viewportIds[0], {
- segmentationId: activeSegmentation.segmentationId
- });
-
- //
- csToolsSegmentation.state.removeSegmentation(
- activeSegmentation.segmentationId
- );
- //
- cache.removeVolumeLoadObject(activeSegmentation.segmentationId);
-
- // Update the dropdown
- updateSegmentationDropdown();
-}
-
-function plusActiveSegment() {
- if (!volumeId) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- //
- if (!activeSegmentation) {
- return;
- }
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- if (activeSegmentIndex + 1 <= 255) {
- csToolsSegmentation.segmentIndex.setActiveSegmentIndex(
- activeSegmentation.segmentationId,
- activeSegmentIndex + 1
- );
-
- // Update the dropdown
- updateSegmentDropdown();
- }
-}
-
-function minusActiveSegment() {
- if (!volumeId) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- //
- if (!activeSegmentation) {
- return;
- }
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- if (activeSegmentIndex - 1 >= 1) {
- csToolsSegmentation.segmentIndex.setActiveSegmentIndex(
- activeSegmentation.segmentationId,
- activeSegmentIndex - 1
- );
-
- // Update the dropdown
- updateSegmentDropdown();
- }
-}
-
-function removeActiveSegment() {
- if (!volumeId) {
- return;
- }
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
- //
- if (!activeSegmentation) {
- return;
- }
-
- // Get volume
- const volume = cache.getVolume(activeSegmentation.segmentationId);
-
- // Get scalar data
- // Todo: need to move to the new model with voxel manager
- const scalarData = volume.voxelManager.getCompleteScalarDataArray();
-
- //
- const frameLength = volume.dimensions[0] * volume.dimensions[1];
- const numFrames = volume.dimensions[2];
-
- //
- let index = 0;
-
- //
- const modifiedFrames = new Set();
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
- //
- for (let f = 0; f < numFrames; f++) {
- //
- for (let p = 0; p < frameLength; p++) {
- if (scalarData[index] === activeSegmentIndex) {
- scalarData[index] = 0;
-
- modifiedFrames.add(f);
- }
-
- index++;
- }
- }
-
- //
- const modifiedFramesArray = Array.from(modifiedFrames);
-
- // Event trigger (SEGMENTATION_DATA_MODIFIED)
- csToolsSegmentation.triggerSegmentationEvents.triggerSegmentationDataModified(
- activeSegmentation.segmentationId,
- modifiedFramesArray
- );
-
- // Update the dropdown
- updateSegmentDropdown();
+ renderingEngine.render();
}
// ============================= //
-
-addDropdownToToolbar({
- id: "DICOM_DROPDOWN",
- style: {
- marginRight: "10px"
- },
- options: { map: dicomMap, defaultIndex: 0 },
- onSelectedValueChange: (key, value) => {
- dev.setConfig = value;
- },
- container: group1
-});
-
addButtonToToolbar({
id: "LOAD_DICOM",
title: "Load DICOM",
- style: {
- marginRight: "5px"
+ onClick: async () => {
+ state.referenceImageIds = await createImageIdsAndCacheMetaData(
+ state.devConfig.fetchDicom
+ );
+ await loadDicom(state.referenceImageIds, state);
},
- onClick: fetchDicom,
container: group1
});
addButtonToToolbar({
id: "LOAD_SEGMENTATION",
title: "Load SEG",
- style: {
- marginRight: "5px"
+ onClick: async () => {
+ if (!state.volumeId) {
+ alert("load source dicom first");
+ return;
+ }
+
+ const configSeg = state.devConfig.fetchSegmentation;
+ const client = new api.DICOMwebClient({
+ url: configSeg.wadoRsRoot
+ });
+ const arrayBuffer = await client.retrieveInstance({
+ studyInstanceUID: configSeg.StudyInstanceUID,
+ seriesInstanceUID: configSeg.SeriesInstanceUID,
+ sopInstanceUID: configSeg.SOPInstanceUID
+ });
+
+ await loadSegmentation(arrayBuffer, state);
},
- onClick: fetchSegmentation,
container: group1
});
addUploadToToolbar({
id: "IMPORT_DICOM",
title: "Import DICOM",
- style: {
- marginRight: "5px"
- },
- onChange: readDicom,
+ onChange: (files: FileList) => readDicom(files, state),
container: group2
});
addUploadToToolbar({
id: "IMPORT_SEGMENTATION",
title: "Import SEG",
- style: {
- marginRight: "5px"
- },
- onChange: importSegmentation,
- container: group2
-});
-
-addButtonToToolbar({
- id: "EXPORT_SEGMENTATION",
- title: "Export SEG",
- onClick: exportSegmentation,
- container: group2
-});
-
-addDropdownToToolbar({
- id: "LABELMAP_TOOLS_DROPDOWN",
- style: {
- width: "150px",
- marginRight: "10px"
- },
- options: { map: labelmapTools.toolMap, defaultIndex: 0 },
- onSelectedValueChange: nameAsStringOrNumber => {
- const tool = String(nameAsStringOrNumber);
-
- const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
-
- if (!toolGroup) {
+ onChange: async (files: FileList) => {
+ if (!state.volumeId) {
return;
}
- // Set the currently active tool disabled
- const toolName = toolGroup.getActivePrimaryMouseButtonTool();
-
- if (toolName) {
- toolGroup.setToolDisabled(toolName);
+ for (const file of files) {
+ await readSegmentation(file, state);
}
-
- toolGroup.setToolActive(tool, {
- bindings: [{ mouseButton: MouseBindings.Primary }]
- });
- },
- labelText: "Tools: ",
- container: group3
-});
-
-addBrushSizeSlider({
- toolGroupId: toolGroupId,
- container: group3
-});
-
-addDropdownToToolbar({
- id: "ACTIVE_SEGMENTATION_DROPDOWN",
- style: {
- width: "200px",
- marginRight: "10px"
- },
- options: { values: [], defaultValue: "" },
- placeholder: "No active segmentation...",
- onSelectedValueChange: nameAsStringOrNumber => {
- const segmentationId = String(nameAsStringOrNumber);
-
- const segmentationRepresentations =
- csToolsSegmentation.state.getSegmentationRepresentationsBySegmentationId(
- segmentationId
- );
-
- csToolsSegmentation.activeSegmentation.setActiveSegmentation(
- viewportIds[0],
- segmentationRepresentations[0].representations[0].segmentationId
- );
-
- // Update the dropdown
- updateSegmentationDropdown(segmentationId);
- },
- labelText: "Set Active Segmentation: ",
- container: group4
-});
-
-addButtonToToolbar({
- id: "ADD_ACTIVE_SEGMENTATION",
- style: {
- marginRight: "10px"
- },
- title: "Add Active Segmentation",
- onClick: addActiveSegmentation,
- container: group4
-});
-
-addButtonToToolbar({
- id: "REMOVE_ACTIVE_SEGMENTATION",
- title: "Remove Active Segmentation",
- onClick: removeActiveSegmentation,
- container: group4
-});
-
-addLabelToToolbar({
- id: "CURRENT_ACTIVE_SEGMENT_LABEL",
- title: "Current Active Segment: 1",
- style: {
- marginRight: "10px"
},
- container: group5
-});
-
-addButtonToToolbar({
- id: "PLUS_ACTIVE_SEGMENT",
- attr: {
- title: "Plus Active Segment"
- },
- style: {
- marginRight: "10px"
- },
- title: "+",
- onClick: plusActiveSegment,
- container: group5
+ container: group2
});
addButtonToToolbar({
- id: "MINUS_ACTIVE_SEGMENT",
- attr: {
- title: "Minus Active Segment"
- },
- style: {
- marginRight: "10px"
- },
- title: "-",
- onClick: minusActiveSegment,
- container: group5
+ id: "EXPORT_SEGMENTATION",
+ title: "Export SEG",
+ onClick: () => exportSegmentation(state)
});
-addDropdownToToolbar({
- id: "ACTIVE_SEGMENT_DROPDOWN",
- style: {
- width: "200px",
- marginRight: "10px"
+addToggleButtonToToolbar({
+ id: "SKIP_OVERLAPPING",
+ title: "Override Overlapping Segments",
+ onClick: () => {
+ state.skipOverlapping = !state.skipOverlapping;
},
- options: { values: [], defaultValue: "" },
- placeholder: "No active segment...",
- onSelectedValueChange: nameAsStringOrNumber => {
- const segmentIndex = Number(nameAsStringOrNumber);
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
-
- csToolsSegmentation.segmentIndex.setActiveSegmentIndex(
- activeSegmentation.segmentationId,
- segmentIndex
- );
-
- // Update the dropdown
- updateSegmentDropdown();
- },
- labelText: "Set Active Segment: ",
- container: group5
-});
-
-addButtonToToolbar({
- id: "REMOVE_ACTIVE_SEGMENT",
- title: "Remove Active Segment",
- onClick: removeActiveSegment,
- container: group5
+ container: group1
});
-
// ============================= //
-function restart() {
- // If you import the dicom again, before clearing the cache or starting from scratch
- if (!volumeId) {
- return;
- }
-
- //
- cache.removeVolumeLoadObject(volumeId);
-
- //
- csToolsSegmentation.removeSegmentationRepresentations(viewportIds[0]);
-
- //
- const segmentationIds = getSegmentationIds();
- //
- segmentationIds.forEach(segmentationId => {
- csToolsSegmentation.state.removeSegmentation(segmentationId);
- cache.removeVolumeLoadObject(segmentationId);
- });
-}
-
-function getSegmentationIds() {
- return csToolsSegmentation.state
- .getSegmentations()
- .map(x => x.segmentationId);
-}
-
-async function addSegmentationsToState(segmentationId: string) {
- // Create a segmentation of the same resolution as the source data
- const derivedVolume = volumeLoader.createAndCacheDerivedLabelmapVolume(
- volumeId,
- {
- volumeId: segmentationId
- }
- );
-
- // Add the segmentations to state
- csToolsSegmentation.addSegmentations([
- {
- segmentationId,
- representation: {
- // The type of segmentation
- type: csToolsEnums.SegmentationRepresentations.Labelmap,
- // The actual segmentation data, in the case of labelmap this is a
- // reference to the source volume of the segmentation.
- data: {
- volumeId: segmentationId
- }
- }
- }
- ]);
-
- // Add the segmentation representation to the viewport
- await csToolsSegmentation.addSegmentationRepresentations(viewportIds[0], [
- {
- segmentationId,
- type: csToolsEnums.SegmentationRepresentations.Labelmap
- }
- ]);
-
- await csToolsSegmentation.addSegmentationRepresentations(viewportIds[1], [
- {
- segmentationId,
- type: csToolsEnums.SegmentationRepresentations.Labelmap
- }
- ]);
-
- await csToolsSegmentation.addSegmentationRepresentations(viewportIds[2], [
- {
- segmentationId,
- type: csToolsEnums.SegmentationRepresentations.Labelmap
- }
- ]);
-
- //
- return derivedVolume;
-}
-
-function generateMockMetadata(segmentIndex, color) {
- const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB(
- color.slice(0, 3).map(value => value / 255)
- ).map(value => Math.round(value));
-
- return {
- SegmentedPropertyCategoryCodeSequence: {
- CodeValue: "T-D0050",
- CodingSchemeDesignator: "SRT",
- CodeMeaning: "Tissue"
- },
- SegmentNumber: segmentIndex.toString(),
- SegmentLabel: "Tissue " + segmentIndex.toString(),
- SegmentAlgorithmType: "SEMIAUTOMATIC",
- SegmentAlgorithmName: "Slicer Prototype",
- RecommendedDisplayCIELabValue,
- SegmentedPropertyTypeCodeSequence: {
- CodeValue: "T-D0050",
- CodingSchemeDesignator: "SRT",
- CodeMeaning: "Tissue"
- }
- };
-}
-
-function updateSegmentationDropdown(activeSegmentationId?) {
- const dropdown = document.getElementById(
- "ACTIVE_SEGMENTATION_DROPDOWN"
- ) as HTMLSelectElement;
-
- dropdown.innerHTML = "";
-
- const segmentationIds = getSegmentationIds();
-
- //
- if (segmentationIds.length) {
- segmentationIds.forEach((segmentationId: string) => {
- const option = document.createElement("option");
- option.value = segmentationId;
- option.innerText = segmentationId;
- dropdown.appendChild(option);
- });
-
- if (activeSegmentationId) {
- dropdown.value = activeSegmentationId;
- }
- }
- //
- else {
- const option = document.createElement("option");
- option.setAttribute("disabled", "");
- option.setAttribute("hidden", "");
- option.setAttribute("selected", "");
- option.innerText = "No active segmentation...";
- dropdown.appendChild(option);
- }
-
- //
- updateSegmentDropdown();
-}
-
-function updateSegmentDropdown() {
- const dropdown = document.getElementById(
- "ACTIVE_SEGMENT_DROPDOWN"
- ) as HTMLSelectElement;
-
- dropdown.innerHTML = "";
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
-
- //
- if (!activeSegmentation) {
- const option = document.createElement("option");
- option.setAttribute("disabled", "");
- option.setAttribute("hidden", "");
- option.setAttribute("selected", "");
- option.innerText = "No active segment...";
- dropdown.appendChild(option);
-
- return;
- }
-
- //
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- const segmentIndices =
- csToolsUtilities.segmentation.getUniqueSegmentIndices(
- activeSegmentation.segmentationId
- );
-
- //
- const optionDraw = function () {
- const option = document.createElement("option");
- option.setAttribute("disabled", "");
- option.setAttribute("hidden", "");
- option.setAttribute("selected", "");
- option.innerText = "Draw or set segment index";
- dropdown.appendChild(option);
- };
-
- //
- if (segmentIndices.length) {
- if (!segmentIndices.includes(activeSegmentIndex)) {
- optionDraw();
- }
-
- segmentIndices.forEach((segmentIndex: number) => {
- const option = document.createElement("option");
- option.value = segmentIndex.toString();
- option.innerText = segmentIndex.toString();
- dropdown.appendChild(option);
- });
-
- if (segmentIndices.includes(activeSegmentIndex)) {
- dropdown.value = activeSegmentIndex.toString();
- }
- }
- //
- else {
- optionDraw();
- }
-
- //
- updateSegmentLabel();
-}
-
-function updateSegmentLabel() {
- const label = document.getElementById(
- "CURRENT_ACTIVE_SEGMENT_LABEL"
- ) as HTMLSelectElement;
-
- // Get active segmentation
- const activeSegmentation =
- csToolsSegmentation.activeSegmentation.getActiveSegmentation(
- toolGroupId
- );
-
- const activeSegmentIndex =
- csToolsSegmentation.segmentIndex.getActiveSegmentIndex(
- activeSegmentation.segmentationId
- );
-
- label.innerHTML = "Current Active Segment: " + activeSegmentIndex;
-}
-
-function handleFileSelect(evt) {
- evt.stopPropagation();
- evt.preventDefault();
-
- //
- const files = evt.dataTransfer.files;
-
- //
- readDicom(files);
-}
-
-function handleDragOver(evt) {
- evt.stopPropagation();
- evt.preventDefault();
- evt.dataTransfer.dropEffect = "copy";
-}
-
// ============================= //
/**
* Runs the demo
*/
async function run() {
- // Init Cornerstone and related libraries
await initDemo();
- //
- labelmapTools.toolMap.forEach(x => {
- if (x.configuration?.preview) {
- x.configuration.preview.enabled = false;
- }
+ state.toolGroup = cornerstoneTools.ToolGroupManager.createToolGroup(
+ state.toolGroupId
+ );
+ addManipulationBindings(state.toolGroup, {
+ toolMap: labelmapTools.toolMap
});
- // Define tool groups to add the segmentation display tool to
- toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
- addManipulationBindings(toolGroup, { toolMap: labelmapTools.toolMap });
- //
+ cornerstoneTools.addTool(BrushTool);
- // Instantiate a rendering engine
- renderingEngine = new RenderingEngine(renderingEngineId);
+ state.toolGroup.addToolInstance("CircularBrush", BrushTool.toolName, {
+ activeStrategy: "FILL_INSIDE_CIRCLE"
+ });
+
+ state.toolGroup.setToolActive("CircularBrush", {
+ bindings: [
+ { mouseButton: cornerstoneTools.Enums.MouseBindings.Primary }
+ ]
+ });
+
+ state.renderingEngine = new cornerstone.RenderingEngine(
+ state.renderingEngineId
+ );
- // Create the viewports
const viewportInputArray = [
{
- viewportId: viewportIds[0],
- type: ViewportType.ORTHOGRAPHIC,
+ viewportId: state.viewportIds[0],
+ type: cornerstone.Enums.ViewportType.ORTHOGRAPHIC,
element: element1,
defaultOptions: {
- orientation: csEnums.OrientationAxis.AXIAL,
+ orientation: cornerstone.Enums.OrientationAxis.AXIAL,
background: [0.2, 0, 0.2]
}
},
{
- viewportId: viewportIds[1],
- type: ViewportType.ORTHOGRAPHIC,
+ viewportId: state.viewportIds[1],
+ type: cornerstone.Enums.ViewportType.ORTHOGRAPHIC,
element: element2,
defaultOptions: {
- orientation: csEnums.OrientationAxis.SAGITTAL,
+ orientation: cornerstone.Enums.OrientationAxis.SAGITTAL,
background: [0.2, 0, 0.2]
}
},
{
- viewportId: viewportIds[2],
- type: ViewportType.ORTHOGRAPHIC,
+ viewportId: state.viewportIds[2],
+ type: cornerstone.Enums.ViewportType.ORTHOGRAPHIC,
element: element3,
defaultOptions: {
- orientation: csEnums.OrientationAxis.CORONAL,
+ orientation: cornerstone.Enums.OrientationAxis.CORONAL,
background: [0.2, 0, 0.2]
}
}
];
- //
- renderingEngine.setViewports(viewportInputArray);
-
- //
- eventTarget.addEventListener(
- csToolsEnums.Events.SEGMENTATION_DATA_MODIFIED,
- function () {
- updateSegmentDropdown();
- }
- );
+ state.renderingEngine.setViewports(viewportInputArray);
}
run();
diff --git a/packages/adapters/examples/segmentationVolume/utils.ts b/packages/adapters/examples/segmentationVolume/utils.ts
new file mode 100644
index 0000000000..bb6f5902c7
--- /dev/null
+++ b/packages/adapters/examples/segmentationVolume/utils.ts
@@ -0,0 +1,249 @@
+import * as cornerstone from "@cornerstonejs/core";
+import * as cornerstoneTools from "@cornerstonejs/tools";
+import * as cornerstoneDicomImageLoader from "@cornerstonejs/dicom-image-loader";
+import * as cornerstoneAdapters from "@cornerstonejs/adapters";
+import dcmjs from "dcmjs";
+
+const {
+ cache,
+ imageLoader,
+ metaData,
+ utilities: csUtilities,
+ volumeLoader
+} = cornerstone;
+const { segmentation: csToolsSegmentation } = cornerstoneTools;
+const { wadouri } = cornerstoneDicomImageLoader;
+const { downloadDICOMData } = cornerstoneAdapters.helpers;
+const { Cornerstone3D } = cornerstoneAdapters.adaptersSEG;
+
+export async function readDicom(files: FileList, state) {
+ if (files.length <= 1) {
+ console.error(
+ "Viewport volume does not support just one image, it must be two or more images"
+ );
+ return;
+ }
+
+ for (const file of files) {
+ const imageId = wadouri.fileManager.add(file);
+ await imageLoader.loadAndCacheImage(imageId);
+ state.referenceImageIds.push(imageId);
+ }
+}
+
+export async function readSegmentation(file: File, state) {
+ const imageId = wadouri.fileManager.add(file);
+ const image = await imageLoader.loadAndCacheImage(imageId);
+
+ if (!image) {
+ return;
+ }
+
+ const instance = metaData.get("instance", imageId);
+
+ if (instance.Modality !== "SEG") {
+ console.error("This is not segmentation: " + file.name);
+ return;
+ }
+
+ const arrayBuffer = image.data.byteArray.buffer;
+
+ await loadSegmentation(arrayBuffer, state);
+}
+
+export async function loadSegmentation(arrayBuffer: ArrayBuffer, state) {
+ const { referenceImageIds, skipOverlapping, viewportIds, segmentationId } =
+ state;
+
+ const generateToolState =
+ await Cornerstone3D.Segmentation.generateToolState(
+ referenceImageIds,
+ arrayBuffer,
+ metaData,
+ {
+ skipOverlapping
+ }
+ );
+
+ if (generateToolState.labelmapBufferArray.length !== 1) {
+ alert(
+ "Overlapping segments in your segmentation are not supported yet. You can turn on the skipOverlapping option but it will override the overlapping segments."
+ );
+ return;
+ }
+
+ const derivedSegmentationImages =
+ await imageLoader.createAndCacheDerivedLabelmapImages(
+ referenceImageIds
+ );
+
+ const derivedSegmentationImageIds = derivedSegmentationImages.map(
+ image => image.imageId
+ );
+
+ csToolsSegmentation.addSegmentations([
+ {
+ segmentationId,
+ representation: {
+ type: cornerstoneTools.Enums.SegmentationRepresentations
+ .Labelmap,
+ data: {
+ imageIds: derivedSegmentationImageIds
+ }
+ }
+ }
+ ]);
+
+ const segMap = {
+ [viewportIds[0]]: [{ segmentationId }],
+ [viewportIds[1]]: [{ segmentationId }],
+ [viewportIds[2]]: [{ segmentationId }]
+ };
+
+ await csToolsSegmentation.addLabelmapRepresentationToViewportMap(segMap);
+
+ const volumeScalarData = new Uint8Array(
+ generateToolState.labelmapBufferArray[0]
+ );
+
+ for (let i = 0; i < derivedSegmentationImages.length; i++) {
+ const voxelManager = derivedSegmentationImages[i].voxelManager;
+ const scalarData = voxelManager.getScalarData();
+ scalarData.set(
+ volumeScalarData.slice(
+ i * scalarData.length,
+ (i + 1) * scalarData.length
+ )
+ );
+ voxelManager.setScalarData(scalarData);
+ }
+}
+
+export async function exportSegmentation(state) {
+ const { segmentationId, viewportIds } = state;
+ const segmentationIds = getSegmentationIds();
+ if (!segmentationIds.length) {
+ return;
+ }
+
+ const segmentation =
+ csToolsSegmentation.state.getSegmentation(segmentationId);
+
+ const { imageIds } = segmentation.representationData.Labelmap;
+
+ const segImages = imageIds.map(imageId => cache.getImage(imageId));
+ const referencedImages = segImages.map(image =>
+ cache.getImage(image.referencedImageId)
+ );
+
+ const labelmaps2D = [];
+
+ let z = 0;
+
+ for (const segImage of segImages) {
+ const segmentsOnLabelmap = new Set();
+ const pixelData = segImage.getPixelData();
+ const { rows, columns } = segImage;
+
+ for (let i = 0; i < pixelData.length; i++) {
+ const segment = pixelData[i];
+ if (segment !== 0) {
+ segmentsOnLabelmap.add(segment);
+ }
+ }
+
+ labelmaps2D[z++] = {
+ segmentsOnLabelmap: Array.from(segmentsOnLabelmap),
+ pixelData,
+ rows,
+ columns
+ };
+ }
+
+ const allSegmentsOnLabelmap = labelmaps2D.map(
+ labelmap => labelmap.segmentsOnLabelmap
+ );
+
+ const labelmap3D = {
+ segmentsOnLabelmap: Array.from(new Set(allSegmentsOnLabelmap.flat())),
+ metadata: [],
+ labelmaps2D
+ };
+
+ labelmap3D.segmentsOnLabelmap.forEach(segmentIndex => {
+ const color = csToolsSegmentation.config.color.getSegmentIndexColor(
+ viewportIds[0],
+ segmentationId,
+ segmentIndex
+ );
+ const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB(
+ color.slice(0, 3).map(value => value / 255)
+ ).map(value => Math.round(value));
+
+ const segmentMetadata = {
+ SegmentNumber: segmentIndex.toString(),
+ SegmentLabel: `Segment ${segmentIndex}`,
+ SegmentAlgorithmType: "MANUAL",
+ SegmentAlgorithmName: "OHIF Brush",
+ RecommendedDisplayCIELabValue,
+ SegmentedPropertyCategoryCodeSequence: {
+ CodeValue: "T-D0050",
+ CodingSchemeDesignator: "SRT",
+ CodeMeaning: "Tissue"
+ },
+ SegmentedPropertyTypeCodeSequence: {
+ CodeValue: "T-D0050",
+ CodingSchemeDesignator: "SRT",
+ CodeMeaning: "Tissue"
+ }
+ };
+ labelmap3D.metadata[segmentIndex] = segmentMetadata;
+ });
+
+ const generatedSegmentation =
+ Cornerstone3D.Segmentation.generateSegmentation(
+ referencedImages,
+ labelmap3D,
+ metaData
+ );
+
+ downloadDICOMData(generatedSegmentation.dataset, "mySEG.dcm");
+}
+
+export function restart(state) {
+ const { volumeId } = state;
+
+ if (!volumeId) {
+ return;
+ }
+
+ cache.removeVolumeLoadObject(volumeId);
+
+ csToolsSegmentation.removeAllSegmentationRepresentations();
+
+ const segmentationIds = getSegmentationIds();
+ segmentationIds.forEach(segmentationId => {
+ csToolsSegmentation.state.removeSegmentation(segmentationId);
+ cache.removeVolumeLoadObject(segmentationId);
+ });
+}
+
+export function getSegmentationIds() {
+ return csToolsSegmentation.state
+ .getSegmentations()
+ .map(x => x.segmentationId);
+}
+
+export function handleFileSelect(evt, state) {
+ evt.stopPropagation();
+ evt.preventDefault();
+
+ const files = evt.dataTransfer.files;
+ readDicom(files, state);
+}
+
+export function handleDragOver(evt) {
+ evt.stopPropagation();
+ evt.preventDefault();
+ evt.dataTransfer.dropEffect = "copy";
+}
diff --git a/packages/adapters/src/adapters/Cornerstone/Segmentation_4X.js b/packages/adapters/src/adapters/Cornerstone/Segmentation_4X.js
index 36a92bd71a..97ad936479 100644
--- a/packages/adapters/src/adapters/Cornerstone/Segmentation_4X.js
+++ b/packages/adapters/src/adapters/Cornerstone/Segmentation_4X.js
@@ -228,7 +228,7 @@ function _createSegFromImages(images, isMultiframe, options) {
* generateToolState - Given a set of cornerstoneTools imageIds and a Segmentation buffer,
* derive cornerstoneTools toolState and brush metadata.
*
- * @param {string[]} imageIds - An array of the imageIds.
+ * @param {string[]} referencedImageIds - An array for referenced image imageIds.
* @param {ArrayBuffer} arrayBuffer - The SEG arrayBuffer.
* @param {*} metadataProvider.
* @param {obj} options - Options object.
@@ -240,7 +240,7 @@ function _createSegFromImages(images, isMultiframe, options) {
* (available only for the overlapping case).
*/
async function generateToolState(
- imageIds,
+ referencedImageIds,
arrayBuffer,
metadataProvider,
options
@@ -250,8 +250,8 @@ async function generateToolState(
tolerance = 1e-3,
TypedArrayConstructor = Uint8Array,
maxBytesPerChunk = 199000000,
- eventTarget,
- triggerEvent
+ eventTarget = null,
+ triggerEvent = null
} = options;
const dicomData = DicomMessage.readFile(arrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
@@ -260,12 +260,12 @@ async function generateToolState(
const imagePlaneModule = metadataProvider.get(
"imagePlaneModule",
- imageIds[0]
+ referencedImageIds[0]
);
const generalSeriesModule = metadataProvider.get(
"generalSeriesModule",
- imageIds[0]
+ referencedImageIds[0]
);
const SeriesInstanceUID = generalSeriesModule.seriesInstanceUID;
@@ -326,14 +326,18 @@ async function generateToolState(
const orientation = checkOrientation(
multiframe,
validOrientations,
- [imagePlaneModule.rows, imagePlaneModule.columns, imageIds.length],
+ [
+ imagePlaneModule.rows,
+ imagePlaneModule.columns,
+ referencedImageIds.length
+ ],
tolerance
);
// Pre-compute the sop UID to imageId index map so that in the for loop
// we don't have to call metadataProvider.get() for each imageId over
// and over again.
- const sopUIDImageIdIndexMap = imageIds.reduce((acc, imageId) => {
+ const sopUIDImageIdIndexMap = referencedImageIds.reduce((acc, imageId) => {
const { sopInstanceUID } = metadataProvider.get(
"generalImageModule",
imageId
@@ -347,7 +351,7 @@ async function generateToolState(
overlapping = checkSEGsOverlapping(
pixelDataChunks,
multiframe,
- imageIds,
+ referencedImageIds,
validOrientations,
metadataProvider,
tolerance,
@@ -388,13 +392,15 @@ async function generateToolState(
const segmentsOnFrame = [];
const arrayBufferLength =
- sliceLength * imageIds.length * TypedArrayConstructor.BYTES_PER_ELEMENT;
+ sliceLength *
+ referencedImageIds.length *
+ TypedArrayConstructor.BYTES_PER_ELEMENT;
const labelmapBufferArray = [];
labelmapBufferArray[0] = new ArrayBuffer(arrayBufferLength);
// Pre-compute the indices and metadata so that we don't have to call
// a function for each imageId in the for loop.
- const imageIdMaps = imageIds.reduce(
+ const imageIdMaps = referencedImageIds.reduce(
(acc, curr, index) => {
acc.indices[curr] = index;
acc.metadata[curr] = metadataProvider.get("instance", curr);
@@ -415,7 +421,7 @@ async function generateToolState(
labelmapBufferArray,
pixelDataChunks,
multiframe,
- imageIds,
+ referencedImageIds,
validOrientations,
metadataProvider,
tolerance,
@@ -435,7 +441,7 @@ async function generateToolState(
imageIdIndexBufferIndex,
multiframe,
metadataProvider,
- imageIds
+ referencedImageIds
);
centroidXYZ.set(segmentIndex, centroids);
diff --git a/packages/tools/src/eventListeners/mouse/mouseMoveListener.ts b/packages/tools/src/eventListeners/mouse/mouseMoveListener.ts
index 5463ce4aff..530a16d0a1 100644
--- a/packages/tools/src/eventListeners/mouse/mouseMoveListener.ts
+++ b/packages/tools/src/eventListeners/mouse/mouseMoveListener.ts
@@ -14,6 +14,11 @@ const eventName = Events.MOUSE_MOVE;
function mouseMoveListener(evt: MouseEvent) {
const element = evt.currentTarget;
const enabledElement = getEnabledElement(element);
+
+ if (!enabledElement) {
+ return;
+ }
+
const { renderingEngineId, viewportId } = enabledElement;
const currentPoints = getMouseEventPoints(evt);
diff --git a/packages/tools/src/eventListeners/segmentation/imageChangeEventListener.ts b/packages/tools/src/eventListeners/segmentation/imageChangeEventListener.ts
index 28e67122ae..20f0ac5709 100644
--- a/packages/tools/src/eventListeners/segmentation/imageChangeEventListener.ts
+++ b/packages/tools/src/eventListeners/segmentation/imageChangeEventListener.ts
@@ -17,7 +17,17 @@ import { getLabelmapActorEntry } from '../../stateManagement/segmentation/helper
import { getSegmentationRepresentations } from '../../stateManagement/segmentation/getSegmentationRepresentation';
const enable = function (element: HTMLDivElement): void {
- const { viewport } = getEnabledElement(element);
+ if (!element) {
+ return;
+ }
+
+ const enabledElement = getEnabledElement(element);
+
+ if (!enabledElement) {
+ return;
+ }
+
+ const { viewport } = enabledElement;
if (viewport instanceof BaseVolumeViewport) {
return;
diff --git a/packages/tools/src/utilities/planar/filterAnnotationsForDisplay.ts b/packages/tools/src/utilities/planar/filterAnnotationsForDisplay.ts
index ecb329b0d6..1954bc340c 100644
--- a/packages/tools/src/utilities/planar/filterAnnotationsForDisplay.ts
+++ b/packages/tools/src/utilities/planar/filterAnnotationsForDisplay.ts
@@ -36,6 +36,10 @@ export default function filterAnnotationsForDisplay(
// 1. Get the currently displayed imageId from the StackViewport
const imageId = viewport.getCurrentImageId();
+ if (!imageId) {
+ return [];
+ }
+
// 2. remove the dataLoader scheme since it might be an annotation that was
// created on the volumeViewport initially and has the volumeLoader scheme
// but shares the same imageId