Skip to content

Commit

Permalink
feat(adapters): default to creation of labelmap images in 2D instead …
Browse files Browse the repository at this point in the history
…of a volume (#1692)
  • Loading branch information
pedrokohler authored Dec 16, 2024
1 parent a70a7d7 commit 960b75a
Show file tree
Hide file tree
Showing 6 changed files with 465 additions and 47 deletions.
22 changes: 4 additions & 18 deletions packages/adapters/examples/segmentationVolume/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,15 @@ export async function loadSegmentation(arrayBuffer: ArrayBuffer, state) {
const { referenceImageIds, skipOverlapping, segmentationId } = state;

const generateToolState =
await Cornerstone3D.Segmentation.generateToolState(
await Cornerstone3D.Segmentation.createFromDICOMSegBuffer(
referenceImageIds,
arrayBuffer,
metaData,
{
metadataProvider: 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;
}

await createSegmentation(state);

const segmentation =
Expand All @@ -94,19 +87,12 @@ export async function loadSegmentation(arrayBuffer: ArrayBuffer, state) {
cache.getImage(imageId)
);

const volumeScalarData = new Uint8Array(
generateToolState.labelmapBufferArray[0]
);
const labelmapImagesNonOverlapping = generateToolState.labelMapImages[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
)
);
scalarData.set(labelmapImagesNonOverlapping[i].getPixelData());
voxelManager.setScalarData(scalarData);
}
}
Expand Down
9 changes: 8 additions & 1 deletion packages/adapters/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ const pkg = JSON.parse(readFileSync("package.json", { encoding: "utf8" }));
export default [
// ESM configuration
{
external: ["dcmjs", "gl-matrix", "ndarray", "@cornerstonejs/tools"],
external: [
"dcmjs",
"gl-matrix",
"ndarray",
"@cornerstonejs/tools",
"@cornerstonejs/core",
"@kitware/vtk.js"
],
input: pkg.src || "src/index.ts",
output: [
{
Expand Down
51 changes: 29 additions & 22 deletions packages/adapters/src/adapters/Cornerstone/Segmentation_4X.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ const generateSegmentationDefaultOptions = {
* @param {Object} userOptions Options to pass to the segmentation derivation and `fillSegmentation`.
* @returns {Blob}
*/
function generateSegmentation(images, inputLabelmaps3D, userOptions = {}) {
export function generateSegmentation(
images,
inputLabelmaps3D,
userOptions = {}
) {
const isMultiframe = images[0].imageId.includes("?frame");
const segmentation = _createSegFromImages(
images,
Expand All @@ -67,7 +71,11 @@ function generateSegmentation(images, inputLabelmaps3D, userOptions = {}) {
*
* @returns {object} The filled segmentation object.
*/
function fillSegmentation(segmentation, inputLabelmaps3D, userOptions = {}) {
export function fillSegmentation(
segmentation,
inputLabelmaps3D,
userOptions = {}
) {
const options = Object.assign(
{},
generateSegmentationDefaultOptions,
Expand Down Expand Up @@ -194,7 +202,7 @@ function fillSegmentation(segmentation, inputLabelmaps3D, userOptions = {}) {
return segmentation;
}

function _getLabelmapsFromReferencedFrameIndicies(
export function _getLabelmapsFromReferencedFrameIndicies(
labelmap3D,
referencedFrameIndicies
) {
Expand All @@ -218,7 +226,7 @@ function _getLabelmapsFromReferencedFrameIndicies(
* @param {Boolean} isMultiframe Whether the images are multiframe.
* @returns {Object} The Seg derived dataSet.
*/
function _createSegFromImages(images, isMultiframe, options) {
export function _createSegFromImages(images, isMultiframe, options) {
const multiframe = getDatasetsFromImages(images, isMultiframe);

return new SegmentationDerivation([multiframe], options);
Expand All @@ -239,7 +247,7 @@ function _createSegFromImages(images, isMultiframe, options) {
* @return {[][][]} 3D list containing the track of segments per frame for each labelMap
* (available only for the overlapping case).
*/
async function generateToolState(
export async function generateToolState(
referencedImageIds,
arrayBuffer,
metadataProvider,
Expand Down Expand Up @@ -639,7 +647,7 @@ async function generateToolState(
*
* @returns {String} Returns the imageId
*/
function findReferenceSourceImageId(
export function findReferenceSourceImageId(
multiframe,
frameSegment,
imageIds,
Expand Down Expand Up @@ -736,7 +744,7 @@ function findReferenceSourceImageId(
* @returns {boolean} Returns a flag if segmentations overlapping
*/

function checkSEGsOverlapping(
export function checkSEGsOverlapping(
pixelData,
multiframe,
imageIds,
Expand Down Expand Up @@ -867,7 +875,7 @@ function checkSEGsOverlapping(
return false;
}

function insertOverlappingPixelDataPlanar(
export function insertOverlappingPixelDataPlanar(
segmentsOnFrame,
segmentsOnFrameArray,
labelmapBufferArray,
Expand Down Expand Up @@ -1070,7 +1078,7 @@ function insertOverlappingPixelDataPlanar(
}
}

const getSegmentIndex = (multiframe, frame) => {
export const getSegmentIndex = (multiframe, frame) => {
const { PerFrameFunctionalGroupsSequence, SharedFunctionalGroupsSequence } =
multiframe;
const PerFrameFunctionalGroups = PerFrameFunctionalGroupsSequence[frame];
Expand All @@ -1084,7 +1092,7 @@ const getSegmentIndex = (multiframe, frame) => {
: undefined;
};

function insertPixelDataPlanar(
export function insertPixelDataPlanar(
segmentsOnFrame,
segmentsOnFrameArray,
labelmapBufferArray,
Expand Down Expand Up @@ -1277,7 +1285,7 @@ function insertPixelDataPlanar(
* @param {Object} options Options for the unpacking.
* @return {Uint8Array} The unpacked pixelData.
*/
function unpackPixelData(multiframe, options) {
export function unpackPixelData(multiframe, options) {
const segType = multiframe.SegmentationType;

let data;
Expand Down Expand Up @@ -1317,7 +1325,7 @@ function unpackPixelData(multiframe, options) {
return pixelData;
}

function getUnpackedChunks(data, maxBytesPerChunk) {
export function getUnpackedChunks(data, maxBytesPerChunk) {
var bitArray = new Uint8Array(data);
var chunks = [];

Expand Down Expand Up @@ -1348,7 +1356,7 @@ function getUnpackedChunks(data, maxBytesPerChunk) {
* @param {Object} sopUIDImageIdIndexMap A map of SOPInstanceUIDs to imageIds.
* @return {String} The corresponding imageId.
*/
function getImageIdOfSourceImageBySourceImageSequence(
export function getImageIdOfSourceImageBySourceImageSequence(
SourceImageSequence,
sopUIDImageIdIndexMap
) {
Expand Down Expand Up @@ -1376,7 +1384,7 @@ function getImageIdOfSourceImageBySourceImageSequence(
*
* @return {String} The corresponding imageId.
*/
function getImageIdOfSourceImagebyGeometry(
export function getImageIdOfSourceImagebyGeometry(
ReferencedSeriesInstanceUID,
FrameOfReferenceUID,
PerFrameFunctionalGroup,
Expand Down Expand Up @@ -1437,7 +1445,7 @@ function getImageIdOfSourceImagebyGeometry(
* @param {Object} sopUIDImageIdIndexMap A map of SOPInstanceUIDs to imageIds.
* @return {String} The imageId that corresponds to the sopInstanceUid.
*/
function getImageIdOfReferencedFrame(
export function getImageIdOfReferencedFrame(
sopInstanceUid,
frameNumber,
sopUIDImageIdIndexMap
Expand All @@ -1459,7 +1467,7 @@ function getImageIdOfReferencedFrame(
* @param {Number[6]} iop The row (0..2) an column (3..5) direction cosines.
* @return {Number[8][6]} An array of valid orientations.
*/
function getValidOrientations(iop) {
export function getValidOrientations(iop) {
const orientations = [];

// [0, 1, 2]: 0, 0hf, 0vf
Expand Down Expand Up @@ -1491,7 +1499,7 @@ function getValidOrientations(iop) {
* @param {Number} tolerance.
* @return {Ndarray} The aligned pixelData.
*/
function alignPixelDataWithSourceData(
export function alignPixelDataWithSourceData(
pixelData2D,
iop,
orientations,
Expand Down Expand Up @@ -1538,7 +1546,7 @@ function alignPixelDataWithSourceData(
}
}

function getSegmentMetadata(multiframe, seriesInstanceUid) {
export function getSegmentMetadata(multiframe, seriesInstanceUid) {
const segmentSequence = multiframe.SegmentSequence;
let data = [];

Expand All @@ -1564,7 +1572,7 @@ function getSegmentMetadata(multiframe, seriesInstanceUid) {
* @param {number} length - The number of bytes to read.
* @returns {Uint8Array} A new Uint8Array containing the requested bytes.
*/
function readFromUnpackedChunks(chunks, offset, length) {
export function readFromUnpackedChunks(chunks, offset, length) {
const mapping = getUnpackedOffsetAndLength(chunks, offset, length);

// If all the data is in one chunk, we can just slice that chunk
Expand Down Expand Up @@ -1602,7 +1610,7 @@ function readFromUnpackedChunks(chunks, offset, length) {
}
}

function getUnpackedOffsetAndLength(chunks, offset, length) {
export function getUnpackedOffsetAndLength(chunks, offset, length) {
var totalBytes = chunks.reduce((total, chunk) => total + chunk.length, 0);

if (offset < 0 || offset + length > totalBytes) {
Expand Down Expand Up @@ -1631,7 +1639,7 @@ function getUnpackedOffsetAndLength(chunks, offset, length) {
};
}

function calculateCentroid(
export function calculateCentroid(
imageIdIndexBufferIndex,
multiframe,
metadataProvider,
Expand Down Expand Up @@ -1732,4 +1740,3 @@ const Segmentation = {
};

export default Segmentation;
export { fillSegmentation, generateSegmentation, generateToolState };
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { generateToolState as generateToolStateCornerstoneLegacy } from "../../Cornerstone/Segmentation";

import { createLabelmapsFromBufferInternal } from "./labelmapImagesFromBuffer";
/**
* generateToolState - Given a set of cornerstoneTools imageIds and a Segmentation buffer,
* derive cornerstoneTools toolState and brush metadata.
Expand All @@ -19,15 +19,51 @@ function generateToolState(
arrayBuffer,
metadataProvider,
skipOverlapping = false,
tolerance = 1e-3
tolerance = 1e-3,
cs3dVersion = 4
) {
return generateToolStateCornerstoneLegacy(
imageIds,
arrayBuffer,
metadataProvider,
skipOverlapping,
tolerance
tolerance,
cs3dVersion
);
}

/**
* Creates a segmentation tool state from a set of image IDs and a segmentation buffer.
*
* @param referencedImageIds - An array of referenced image IDs e.g., CT, MR etc.
* @param arrayBuffer - The DICOM SEG array buffer containing segmentation data.
* @param metadataProvider - The metadata provider to retrieve necessary metadata.
* @param options - Optional parameters to customize the segmentation processing.
*
* @returns An object containing:
* - `labelMapImages`: Array of label map images for each label map.
* - `segMetadata`: Metadata related to the segmentation segments.
* - `segmentsOnFrame`: 2D array tracking segments per frame.
* - `segmentsOnFrameArray`: 3D array tracking segments per frame for each label map.
* - `centroids`: Map of centroid coordinates for each segment.
* - `overlappingSegments`: Boolean indicating if segments are overlapping.
*
* @throws Will throw an error if unsupported transfer syntax is encountered or if segmentation frames are out of plane.
*/
function createFromDICOMSegBuffer(
referencedImageIds,
arrayBuffer,
{ metadataProvider, skipOverlapping = false, tolerance = 1e-3 }
) {
return createLabelmapsFromBufferInternal(
referencedImageIds,
arrayBuffer,
metadataProvider,
{
skipOverlapping,
tolerance
}
);
}

export { generateToolState };
export { generateToolState, createFromDICOMSegBuffer };
12 changes: 10 additions & 2 deletions packages/adapters/src/adapters/Cornerstone3D/Segmentation/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { generateSegmentation } from "./generateSegmentation";
import { generateLabelMaps2DFrom3D } from "./generateLabelMaps2DFrom3D";
import { generateToolState } from "./generateToolState";
import {
generateToolState,
createFromDICOMSegBuffer
} from "./generateToolState";

export { generateLabelMaps2DFrom3D, generateSegmentation, generateToolState };
export {
generateLabelMaps2DFrom3D,
generateSegmentation,
generateToolState,
createFromDICOMSegBuffer
};
Loading

0 comments on commit 960b75a

Please sign in to comment.