Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(adapters): default to creation of labelmap images in 2D instead of a volume #1692

Merged
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
Loading