From b94f07f95d67d45857ec3a88f45e32d5b108b0a8 Mon Sep 17 00:00:00 2001 From: Davide Punzo Date: Wed, 14 Sep 2022 13:55:48 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20IDC1346,=20convert=20frac?= =?UTF-8?q?tional=20segs=20into=20binary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adapters/Cornerstone/Segmentation_4X.js | 77 +++++++++++++++++---- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/src/adapters/Cornerstone/Segmentation_4X.js b/src/adapters/Cornerstone/Segmentation_4X.js index 5e46c40e..83d052c9 100644 --- a/src/adapters/Cornerstone/Segmentation_4X.js +++ b/src/adapters/Cornerstone/Segmentation_4X.js @@ -264,6 +264,8 @@ function _createSegFromImages(images, isMultiframe, options) { * @param {*} metadataProvider. * @param {bool} skipOverlapping - skip checks for overlapping segs, default value false. * @param {number} tolerance - default value 1.e-3. + * @param {bool} binarizeFractionalSegs - binarize fractional segmentation by thresholding - default value true. + * @param {number} fractionalSegsThreshold - thresholding parameter for fractional segmentations - default value 0.8 (80% respect to the maximum value). * * @return {[]ArrayBuffer}a list of array buffer for each labelMap * @return {Object} an object from which the segment metadata can be derived @@ -276,7 +278,9 @@ function generateToolState( arrayBuffer, metadataProvider, skipOverlapping = false, - tolerance = 1e-3 + tolerance = 1e-3, + binarizeFractionalSegs = true, + fractionalSegsThreshold = 0.8 ) { const dicomData = DicomMessage.readFile(arrayBuffer); const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict); @@ -337,7 +341,11 @@ function generateToolState( return; } } else { - pixelData = unpackPixelData(multiframe); + pixelData = unpackPixelData( + multiframe, + binarizeFractionalSegs, + fractionalSegsThreshold + ); if (!pixelData) { throw new Error("Fractional segmentations are not yet supported"); @@ -1218,9 +1226,15 @@ function checkIfPerpendicular(iop1, iop2, tolerance) { * unpackPixelData - Unpacks bitpacked pixelData if the Segmentation is BINARY. * * @param {Object} multiframe The multiframe dataset. + * @param {bool} binarizeFractionalSegs - binarize fractional segmentation by thresholding. + * @param {number} fractionalSegsThreshold - thresholding parameter for fractional segmentations. * @return {Uint8Array} The unpacked pixelData. */ -function unpackPixelData(multiframe) { +function unpackPixelData( + multiframe, + binarizeFractionalSegs, + fractionalSegsThreshold +) { const segType = multiframe.SegmentationType; let data; @@ -1240,21 +1254,56 @@ function unpackPixelData(multiframe) { const pixelData = new Uint8Array(data); - const max = multiframe.MaximumFractionalValue; - const onlyMaxAndZero = - pixelData.find(element => element !== 0 && element !== max) === - undefined; + // IDC: we encountered segmentations defined as fractional with a constant value which is not the MaximumFractionalValue. + // Here we add the following check: if the labelmap has a constant value (independently by the value itself), it is considered a binary segmentation + const firstNonZeroIndex = pixelData.findIndex(element => element > 0); + const firstNonZeroValue = pixelData[firstNonZeroIndex]; + const onlyOneValueAndZero = + pixelData.find( + element => element !== 0 && element !== firstNonZeroValue + ) === undefined; + + if (onlyOneValueAndZero) { + log.warn( + "This segmentation object is actually binary... processing as such." + ); - if (!onlyMaxAndZero) { - // This is a fractional segmentation, which is not currently supported. - return; + return pixelData; } - log.warn( - "This segmentation object is actually binary... processing as such." - ); + // This is a fractional segmentation, which is not currently supported. + if (binarizeFractionalSegs) { + console.warn( + "This segmentation object is fractional. Fractional segmentations are not supported. Computing binary labelMap by thresholding (80% from the maximum value)." + ); + // IDC: binarize fractional segmentation if requested + + // we calculcate the maximum since unfortunatly it looks like + // (at least for IDC datasets) the maximum value is the array + // is not the DICOM attirbute MaximumFractionalValue + let max = -Number.MAX_VALUE; + pixelData.forEach(element => { + if (element > max) { + max = element; + } + }); + max = Math.min(max, multiframe.MaximumFractionalValue); + + // use as threshold: max * factor (default 80%) + const thershold = max * fractionalSegsThreshold; + + for (let i = 0; i < pixelData.length; i++) { + if (pixelData[i] < thershold) { + pixelData[i] = 0; + } else { + pixelData[i] = 1; + } + } + + return pixelData; + } - return pixelData; + return; } /**