@cornerstonejs/adapters 2.0.0-beta.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +493 -0
- package/dist/esm/adapters/Cornerstone/Angle.d.ts +42 -0
- package/dist/esm/adapters/Cornerstone/Angle.js +93 -0
- package/dist/esm/adapters/Cornerstone/ArrowAnnotate.d.ts +49 -0
- package/dist/esm/adapters/Cornerstone/ArrowAnnotate.js +97 -0
- package/dist/esm/adapters/Cornerstone/Bidirectional.d.ts +89 -0
- package/dist/esm/adapters/Cornerstone/Bidirectional.js +170 -0
- package/dist/esm/adapters/Cornerstone/CircleRoi.d.ts +58 -0
- package/dist/esm/adapters/Cornerstone/CircleRoi.js +115 -0
- package/dist/esm/adapters/Cornerstone/CobbAngle.d.ts +49 -0
- package/dist/esm/adapters/Cornerstone/CobbAngle.js +98 -0
- package/dist/esm/adapters/Cornerstone/EllipticalRoi.d.ts +57 -0
- package/dist/esm/adapters/Cornerstone/EllipticalRoi.js +178 -0
- package/dist/esm/adapters/Cornerstone/FreehandRoi.d.ts +42 -0
- package/dist/esm/adapters/Cornerstone/FreehandRoi.js +89 -0
- package/dist/esm/adapters/Cornerstone/Length.d.ts +39 -0
- package/dist/esm/adapters/Cornerstone/Length.js +82 -0
- package/dist/esm/adapters/Cornerstone/MeasurementReport.d.ts +29 -0
- package/dist/esm/adapters/Cornerstone/MeasurementReport.js +276 -0
- package/dist/esm/adapters/Cornerstone/ParametricMap.d.ts +7 -0
- package/dist/esm/adapters/Cornerstone/ParametricMap.js +210 -0
- package/dist/esm/adapters/Cornerstone/RectangleRoi.d.ts +44 -0
- package/dist/esm/adapters/Cornerstone/RectangleRoi.js +97 -0
- package/dist/esm/adapters/Cornerstone/Segmentation.d.ts +7 -0
- package/dist/esm/adapters/Cornerstone/Segmentation.js +73 -0
- package/dist/esm/adapters/Cornerstone/Segmentation_3X.d.ts +13 -0
- package/dist/esm/adapters/Cornerstone/Segmentation_3X.js +411 -0
- package/dist/esm/adapters/Cornerstone/Segmentation_4X.d.ts +13 -0
- package/dist/esm/adapters/Cornerstone/Segmentation_4X.js +1152 -0
- package/dist/esm/adapters/Cornerstone/cornerstone4Tag.d.ts +2 -0
- package/dist/esm/adapters/Cornerstone/cornerstone4Tag.js +3 -0
- package/dist/esm/adapters/Cornerstone/index.d.ts +34 -0
- package/dist/esm/adapters/Cornerstone/index.js +33 -0
- package/dist/esm/adapters/Cornerstone3D/Angle.js +109 -0
- package/dist/esm/adapters/Cornerstone3D/ArrowAnnotate.d.ts +35 -0
- package/dist/esm/adapters/Cornerstone3D/ArrowAnnotate.js +123 -0
- package/dist/esm/adapters/Cornerstone3D/Bidirectional.js +147 -0
- package/dist/{types → esm}/adapters/Cornerstone3D/CircleROI.d.ts +0 -7
- package/dist/esm/adapters/Cornerstone3D/CircleROI.js +96 -0
- package/dist/esm/adapters/Cornerstone3D/CobbAngle.js +113 -0
- package/dist/esm/adapters/Cornerstone3D/CodingScheme.d.ts +9 -0
- package/dist/esm/adapters/Cornerstone3D/CodingScheme.js +15 -0
- package/dist/esm/adapters/Cornerstone3D/EllipticalROI.js +160 -0
- package/dist/esm/adapters/Cornerstone3D/Length.d.ts +41 -0
- package/dist/esm/adapters/Cornerstone3D/Length.js +101 -0
- package/dist/{types → esm}/adapters/Cornerstone3D/MeasurementReport.d.ts +1 -8
- package/dist/esm/adapters/Cornerstone3D/MeasurementReport.js +281 -0
- package/dist/esm/adapters/Cornerstone3D/ParametricMap/generateToolState.d.ts +3 -0
- package/dist/esm/adapters/Cornerstone3D/ParametricMap/generateToolState.js +11 -0
- package/dist/esm/adapters/Cornerstone3D/ParametricMap/index.d.ts +1 -0
- package/dist/esm/adapters/Cornerstone3D/ParametricMap/index.js +1 -0
- package/dist/{types → esm}/adapters/Cornerstone3D/PlanarFreehandROI.d.ts +0 -2
- package/dist/esm/adapters/Cornerstone3D/PlanarFreehandROI.js +126 -0
- package/dist/esm/adapters/Cornerstone3D/Probe.d.ts +33 -0
- package/dist/esm/adapters/Cornerstone3D/Probe.js +87 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/RTSS.d.ts +67 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/RTSS.js +175 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/index.js +6 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getPatientModule.d.ts +13 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getPatientModule.js +22 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getRTROIObservationsSequence.d.ts +6 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getRTROIObservationsSequence.js +10 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getRTSeriesModule.d.ts +4 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getRTSeriesModule.js +9 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getReferencedFrameOfReferenceSequence.d.ts +11 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getReferencedFrameOfReferenceSequence.js +22 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getReferencedSeriesSequence.d.ts +4 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getReferencedSeriesSequence.js +27 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getStructureSetModule.d.ts +7 -0
- package/dist/esm/adapters/Cornerstone3D/RTStruct/utilities/getStructureSetModule.js +12 -0
- package/dist/esm/adapters/Cornerstone3D/RectangleROI.js +94 -0
- package/dist/{types → esm}/adapters/Cornerstone3D/Segmentation/generateLabelMaps2DFrom3D.d.ts +0 -6
- package/dist/esm/adapters/Cornerstone3D/Segmentation/generateLabelMaps2DFrom3D.js +34 -0
- package/dist/esm/adapters/Cornerstone3D/Segmentation/generateSegmentation.d.ts +2 -0
- package/dist/esm/adapters/Cornerstone3D/Segmentation/generateSegmentation.js +29 -0
- package/dist/esm/adapters/Cornerstone3D/Segmentation/generateToolState.d.ts +2 -0
- package/dist/esm/adapters/Cornerstone3D/Segmentation/generateToolState.js +9 -0
- package/dist/esm/adapters/Cornerstone3D/Segmentation/index.js +3 -0
- package/dist/esm/adapters/Cornerstone3D/UltrasoundDirectional.js +93 -0
- package/dist/esm/adapters/Cornerstone3D/cornerstone3DTag.d.ts +2 -0
- package/dist/esm/adapters/Cornerstone3D/cornerstone3DTag.js +3 -0
- package/dist/{types → esm}/adapters/Cornerstone3D/index.d.ts +18 -6
- package/dist/esm/adapters/Cornerstone3D/index.js +45 -0
- package/dist/esm/adapters/Cornerstone3D/isValidCornerstoneTrackingIdentifier.js +18 -0
- package/dist/esm/adapters/VTKjs/Segmentation.d.ts +3 -0
- package/dist/esm/adapters/VTKjs/Segmentation.js +186 -0
- package/dist/esm/adapters/VTKjs/index.d.ts +5 -0
- package/dist/esm/adapters/VTKjs/index.js +7 -0
- package/dist/{types → esm}/adapters/enums/Events.d.ts +0 -6
- package/dist/esm/adapters/enums/Events.js +7 -0
- package/dist/esm/adapters/enums/index.js +1 -0
- package/dist/esm/adapters/helpers/checkIfPerpendicular.d.ts +1 -0
- package/dist/esm/adapters/helpers/checkIfPerpendicular.js +7 -0
- package/dist/esm/adapters/helpers/checkOrientation.d.ts +1 -0
- package/dist/esm/adapters/helpers/checkOrientation.js +22 -0
- package/dist/esm/adapters/helpers/codeMeaningEquals.d.ts +2 -0
- package/dist/esm/adapters/helpers/codeMeaningEquals.js +7 -0
- package/dist/esm/adapters/helpers/compareArrays.d.ts +1 -0
- package/dist/esm/adapters/helpers/compareArrays.js +16 -0
- package/dist/esm/adapters/helpers/downloadDICOMData.d.ts +5 -0
- package/dist/esm/adapters/helpers/downloadDICOMData.js +26 -0
- package/dist/esm/adapters/helpers/getDatasetsFromImages.d.ts +1 -0
- package/dist/esm/adapters/helpers/getDatasetsFromImages.js +33 -0
- package/dist/esm/adapters/helpers/graphicTypeEquals.d.ts +2 -0
- package/dist/esm/adapters/helpers/graphicTypeEquals.js +7 -0
- package/dist/esm/adapters/helpers/index.js +4 -0
- package/dist/esm/adapters/helpers/toArray.js +5 -0
- package/dist/esm/adapters/index.d.ts +66 -0
- package/dist/esm/adapters/index.js +24 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +5 -0
- package/dist/umd/adapters.umd.js +5769 -0
- package/dist/umd/adapters.umd.js.map +1 -0
- package/package.json +47 -11
- package/dist/adapters.es.js +0 -5585
- package/dist/adapters.es.js.map +0 -1
- package/dist/types/adapters/Cornerstone/index.d.ts +0 -16
- package/dist/types/adapters/Cornerstone3D/RTStruct/RTSS.d.ts +0 -26
- package/dist/types/adapters/Cornerstone3D/Segmentation/generateSegmentation.d.ts +0 -8
- package/dist/types/adapters/Cornerstone3D/Segmentation/generateToolState.d.ts +0 -16
- package/dist/types/adapters/VTKjs/index.d.ts +0 -4
- package/dist/types/adapters/helpers/codeMeaningEquals.d.ts +0 -9
- package/dist/types/adapters/helpers/downloadDICOMData.d.ts +0 -10
- package/dist/types/adapters/helpers/graphicTypeEquals.d.ts +0 -7
- package/dist/types/adapters/index.d.ts +0 -49
- package/dist/types/index.d.ts +0 -2
- /package/dist/{types → esm}/adapters/Cornerstone3D/Angle.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/Bidirectional.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/CobbAngle.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/EllipticalROI.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/RTStruct/index.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/RectangleROI.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/Segmentation/index.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/UltrasoundDirectional.d.ts +0 -0
- /package/dist/{types → esm}/adapters/Cornerstone3D/isValidCornerstoneTrackingIdentifier.d.ts +0 -0
- /package/dist/{types → esm}/adapters/enums/index.d.ts +0 -0
- /package/dist/{types → esm}/adapters/helpers/index.d.ts +0 -0
- /package/dist/{types → esm}/adapters/helpers/toArray.d.ts +0 -0
|
@@ -0,0 +1,1152 @@
|
|
|
1
|
+
import { asyncToGenerator as _asyncToGenerator, regeneratorRuntime as _regeneratorRuntime, toConsumableArray as _toConsumableArray, createForOfIteratorHelper as _createForOfIteratorHelper, slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
2
|
+
import { utilities, data, normalizers, derivations, log } from 'dcmjs';
|
|
3
|
+
import ndarray from 'ndarray';
|
|
4
|
+
import getDatasetsFromImages from '../helpers/getDatasetsFromImages.js';
|
|
5
|
+
import checkOrientation from '../helpers/checkOrientation.js';
|
|
6
|
+
import compareArrays from '../helpers/compareArrays.js';
|
|
7
|
+
import Events from '../enums/Events.js';
|
|
8
|
+
|
|
9
|
+
var _utilities$orientatio = utilities.orientation,
|
|
10
|
+
rotateDirectionCosinesInPlane = _utilities$orientatio.rotateDirectionCosinesInPlane,
|
|
11
|
+
flipIOP = _utilities$orientatio.flipImageOrientationPatient,
|
|
12
|
+
flipMatrix2D = _utilities$orientatio.flipMatrix2D,
|
|
13
|
+
rotateMatrix902D = _utilities$orientatio.rotateMatrix902D;
|
|
14
|
+
var BitArray = data.BitArray,
|
|
15
|
+
DicomMessage = data.DicomMessage,
|
|
16
|
+
DicomMetaDictionary = data.DicomMetaDictionary;
|
|
17
|
+
var Normalizer = normalizers.Normalizer;
|
|
18
|
+
var SegmentationDerivation = derivations.Segmentation;
|
|
19
|
+
var _utilities$compressio = utilities.compression,
|
|
20
|
+
encode = _utilities$compressio.encode,
|
|
21
|
+
decode = _utilities$compressio.decode;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @typedef {Object} BrushData
|
|
26
|
+
* @property {Object} toolState - The cornerstoneTools global toolState.
|
|
27
|
+
* @property {Object[]} segments - The cornerstoneTools segment metadata that corresponds to the
|
|
28
|
+
* seriesInstanceUid.
|
|
29
|
+
*/
|
|
30
|
+
var generateSegmentationDefaultOptions = {
|
|
31
|
+
includeSliceSpacing: true,
|
|
32
|
+
rleEncode: false
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* generateSegmentation - Generates cornerstoneTools brush data, given a stack of
|
|
37
|
+
* imageIds, images and the cornerstoneTools brushData.
|
|
38
|
+
*
|
|
39
|
+
* @param {object[]} images An array of cornerstone images that contain the source
|
|
40
|
+
* data under `image.data.byteArray.buffer`.
|
|
41
|
+
* @param {Object|Object[]} inputLabelmaps3D The cornerstone `Labelmap3D` object, or an array of objects.
|
|
42
|
+
* @param {Object} userOptions Options to pass to the segmentation derivation and `fillSegmentation`.
|
|
43
|
+
* @returns {Blob}
|
|
44
|
+
*/
|
|
45
|
+
function generateSegmentation(images, inputLabelmaps3D) {
|
|
46
|
+
var userOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
47
|
+
var isMultiframe = images[0].imageId.includes("?frame");
|
|
48
|
+
var segmentation = _createSegFromImages(images, isMultiframe, userOptions);
|
|
49
|
+
return fillSegmentation(segmentation, inputLabelmaps3D, userOptions);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Fills a given segmentation object with data from the input labelmaps3D
|
|
54
|
+
*
|
|
55
|
+
* @param segmentation - The segmentation object to be filled.
|
|
56
|
+
* @param inputLabelmaps3D - An array of 3D labelmaps, or a single 3D labelmap.
|
|
57
|
+
* @param userOptions - Optional configuration settings. Will override the default options.
|
|
58
|
+
*
|
|
59
|
+
* @returns {object} The filled segmentation object.
|
|
60
|
+
*/
|
|
61
|
+
function fillSegmentation(segmentation, inputLabelmaps3D) {
|
|
62
|
+
var userOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
63
|
+
var options = Object.assign({}, generateSegmentationDefaultOptions, userOptions);
|
|
64
|
+
|
|
65
|
+
// Use another variable so we don't redefine labelmaps3D.
|
|
66
|
+
var labelmaps3D = Array.isArray(inputLabelmaps3D) ? inputLabelmaps3D : [inputLabelmaps3D];
|
|
67
|
+
var numberOfFrames = 0;
|
|
68
|
+
var referencedFramesPerLabelmap = [];
|
|
69
|
+
var _loop = function _loop() {
|
|
70
|
+
var labelmap3D = labelmaps3D[labelmapIndex];
|
|
71
|
+
var labelmaps2D = labelmap3D.labelmaps2D,
|
|
72
|
+
metadata = labelmap3D.metadata;
|
|
73
|
+
var referencedFramesPerSegment = [];
|
|
74
|
+
for (var i = 1; i < metadata.length; i++) {
|
|
75
|
+
if (metadata[i]) {
|
|
76
|
+
referencedFramesPerSegment[i] = [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
var _loop2 = function _loop2(_i) {
|
|
80
|
+
var labelmap2D = labelmaps2D[_i];
|
|
81
|
+
if (labelmaps2D[_i]) {
|
|
82
|
+
var segmentsOnLabelmap = labelmap2D.segmentsOnLabelmap;
|
|
83
|
+
segmentsOnLabelmap.forEach(function (segmentIndex) {
|
|
84
|
+
if (segmentIndex !== 0) {
|
|
85
|
+
referencedFramesPerSegment[segmentIndex].push(_i);
|
|
86
|
+
numberOfFrames++;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
for (var _i = 0; _i < labelmaps2D.length; _i++) {
|
|
92
|
+
_loop2(_i);
|
|
93
|
+
}
|
|
94
|
+
referencedFramesPerLabelmap[labelmapIndex] = referencedFramesPerSegment;
|
|
95
|
+
};
|
|
96
|
+
for (var labelmapIndex = 0; labelmapIndex < labelmaps3D.length; labelmapIndex++) {
|
|
97
|
+
_loop();
|
|
98
|
+
}
|
|
99
|
+
segmentation.setNumberOfFrames(numberOfFrames);
|
|
100
|
+
for (var _labelmapIndex = 0; _labelmapIndex < labelmaps3D.length; _labelmapIndex++) {
|
|
101
|
+
var referencedFramesPerSegment = referencedFramesPerLabelmap[_labelmapIndex];
|
|
102
|
+
var labelmap3D = labelmaps3D[_labelmapIndex];
|
|
103
|
+
var metadata = labelmap3D.metadata;
|
|
104
|
+
for (var segmentIndex = 1; segmentIndex < referencedFramesPerSegment.length; segmentIndex++) {
|
|
105
|
+
var referencedFrameIndicies = referencedFramesPerSegment[segmentIndex];
|
|
106
|
+
if (referencedFrameIndicies) {
|
|
107
|
+
// Frame numbers start from 1.
|
|
108
|
+
var referencedFrameNumbers = referencedFrameIndicies.map(function (element) {
|
|
109
|
+
return element + 1;
|
|
110
|
+
});
|
|
111
|
+
var segmentMetadata = metadata[segmentIndex];
|
|
112
|
+
var labelmaps = _getLabelmapsFromReferencedFrameIndicies(labelmap3D, referencedFrameIndicies);
|
|
113
|
+
segmentation.addSegmentFromLabelmap(segmentMetadata, labelmaps, segmentIndex, referencedFrameNumbers);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (options.rleEncode) {
|
|
118
|
+
var rleEncodedFrames = encode(segmentation.dataset.PixelData, numberOfFrames, segmentation.dataset.Rows, segmentation.dataset.Columns);
|
|
119
|
+
|
|
120
|
+
// Must use fractional now to RLE encode, as the DICOM standard only allows BitStored && BitsAllocated
|
|
121
|
+
// to be 1 for BINARY. This is not ideal and there should be a better format for compression in this manner
|
|
122
|
+
// added to the standard.
|
|
123
|
+
segmentation.assignToDataset({
|
|
124
|
+
BitsAllocated: "8",
|
|
125
|
+
BitsStored: "8",
|
|
126
|
+
HighBit: "7",
|
|
127
|
+
SegmentationType: "FRACTIONAL",
|
|
128
|
+
SegmentationFractionalType: "PROBABILITY",
|
|
129
|
+
MaximumFractionalValue: "255"
|
|
130
|
+
});
|
|
131
|
+
segmentation.dataset._meta.TransferSyntaxUID = {
|
|
132
|
+
Value: ["1.2.840.10008.1.2.5"],
|
|
133
|
+
vr: "UI"
|
|
134
|
+
};
|
|
135
|
+
segmentation.dataset.SpecificCharacterSet = "ISO_IR 192";
|
|
136
|
+
segmentation.dataset._vrMap.PixelData = "OB";
|
|
137
|
+
segmentation.dataset.PixelData = rleEncodedFrames;
|
|
138
|
+
} else {
|
|
139
|
+
// If no rleEncoding, at least bitpack the data.
|
|
140
|
+
segmentation.bitPackPixelData();
|
|
141
|
+
}
|
|
142
|
+
return segmentation;
|
|
143
|
+
}
|
|
144
|
+
function _getLabelmapsFromReferencedFrameIndicies(labelmap3D, referencedFrameIndicies) {
|
|
145
|
+
var labelmaps2D = labelmap3D.labelmaps2D;
|
|
146
|
+
var labelmaps = [];
|
|
147
|
+
for (var i = 0; i < referencedFrameIndicies.length; i++) {
|
|
148
|
+
var frame = referencedFrameIndicies[i];
|
|
149
|
+
labelmaps.push(labelmaps2D[frame].pixelData);
|
|
150
|
+
}
|
|
151
|
+
return labelmaps;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* _createSegFromImages - description
|
|
156
|
+
*
|
|
157
|
+
* @param {Object[]} images An array of the cornerstone image objects.
|
|
158
|
+
* @param {Boolean} isMultiframe Whether the images are multiframe.
|
|
159
|
+
* @returns {Object} The Seg derived dataSet.
|
|
160
|
+
*/
|
|
161
|
+
function _createSegFromImages(images, isMultiframe, options) {
|
|
162
|
+
var multiframe = getDatasetsFromImages(images, isMultiframe);
|
|
163
|
+
return new SegmentationDerivation([multiframe], options);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* generateToolState - Given a set of cornerstoneTools imageIds and a Segmentation buffer,
|
|
168
|
+
* derive cornerstoneTools toolState and brush metadata.
|
|
169
|
+
*
|
|
170
|
+
* @param {string[]} imageIds - An array of the imageIds.
|
|
171
|
+
* @param {ArrayBuffer} arrayBuffer - The SEG arrayBuffer.
|
|
172
|
+
* @param {*} metadataProvider.
|
|
173
|
+
* @param {obj} options - Options object.
|
|
174
|
+
*
|
|
175
|
+
* @return {[]ArrayBuffer}a list of array buffer for each labelMap
|
|
176
|
+
* @return {Object} an object from which the segment metadata can be derived
|
|
177
|
+
* @return {[][][]} 2D list containing the track of segments per frame
|
|
178
|
+
* @return {[][][]} 3D list containing the track of segments per frame for each labelMap
|
|
179
|
+
* (available only for the overlapping case).
|
|
180
|
+
*/
|
|
181
|
+
function generateToolState(_x, _x2, _x3, _x4) {
|
|
182
|
+
return _generateToolState.apply(this, arguments);
|
|
183
|
+
} // function insertPixelDataPerpendicular(
|
|
184
|
+
// segmentsOnFrame,
|
|
185
|
+
// labelmapBuffer,
|
|
186
|
+
// pixelData,
|
|
187
|
+
// multiframe,
|
|
188
|
+
// imageIds,
|
|
189
|
+
// validOrientations,
|
|
190
|
+
// metadataProvider
|
|
191
|
+
// ) {
|
|
192
|
+
// const {
|
|
193
|
+
// SharedFunctionalGroupsSequence,
|
|
194
|
+
// PerFrameFunctionalGroupsSequence,
|
|
195
|
+
// Rows,
|
|
196
|
+
// Columns
|
|
197
|
+
// } = multiframe;
|
|
198
|
+
// const firstImagePlaneModule = metadataProvider.get(
|
|
199
|
+
// "imagePlaneModule",
|
|
200
|
+
// imageIds[0]
|
|
201
|
+
// );
|
|
202
|
+
// const lastImagePlaneModule = metadataProvider.get(
|
|
203
|
+
// "imagePlaneModule",
|
|
204
|
+
// imageIds[imageIds.length - 1]
|
|
205
|
+
// );
|
|
206
|
+
// console.log(firstImagePlaneModule);
|
|
207
|
+
// console.log(lastImagePlaneModule);
|
|
208
|
+
// const corners = [
|
|
209
|
+
// ...getCorners(firstImagePlaneModule),
|
|
210
|
+
// ...getCorners(lastImagePlaneModule)
|
|
211
|
+
// ];
|
|
212
|
+
// console.log(`corners:`);
|
|
213
|
+
// console.log(corners);
|
|
214
|
+
// const indexToWorld = mat4.create();
|
|
215
|
+
// const ippFirstFrame = firstImagePlaneModule.imagePositionPatient;
|
|
216
|
+
// const rowCosines = Array.isArray(firstImagePlaneModule.rowCosines)
|
|
217
|
+
// ? [...firstImagePlaneModule.rowCosines]
|
|
218
|
+
// : [
|
|
219
|
+
// firstImagePlaneModule.rowCosines.x,
|
|
220
|
+
// firstImagePlaneModule.rowCosines.y,
|
|
221
|
+
// firstImagePlaneModule.rowCosines.z
|
|
222
|
+
// ];
|
|
223
|
+
// const columnCosines = Array.isArray(firstImagePlaneModule.columnCosines)
|
|
224
|
+
// ? [...firstImagePlaneModule.columnCosines]
|
|
225
|
+
// : [
|
|
226
|
+
// firstImagePlaneModule.columnCosines.x,
|
|
227
|
+
// firstImagePlaneModule.columnCosines.y,
|
|
228
|
+
// firstImagePlaneModule.columnCosines.z
|
|
229
|
+
// ];
|
|
230
|
+
// const { pixelSpacing } = firstImagePlaneModule;
|
|
231
|
+
// mat4.set(
|
|
232
|
+
// indexToWorld,
|
|
233
|
+
// // Column 1
|
|
234
|
+
// 0,
|
|
235
|
+
// 0,
|
|
236
|
+
// 0,
|
|
237
|
+
// ippFirstFrame[0],
|
|
238
|
+
// // Column 2
|
|
239
|
+
// 0,
|
|
240
|
+
// 0,
|
|
241
|
+
// 0,
|
|
242
|
+
// ippFirstFrame[1],
|
|
243
|
+
// // Column 3
|
|
244
|
+
// 0,
|
|
245
|
+
// 0,
|
|
246
|
+
// 0,
|
|
247
|
+
// ippFirstFrame[2],
|
|
248
|
+
// // Column 4
|
|
249
|
+
// 0,
|
|
250
|
+
// 0,
|
|
251
|
+
// 0,
|
|
252
|
+
// 1
|
|
253
|
+
// );
|
|
254
|
+
// // TODO -> Get origin and (x,y,z) increments to build a translation matrix:
|
|
255
|
+
// // TODO -> Equation C.7.6.2.1-1
|
|
256
|
+
// // | cx*di rx* Xx 0 | |x|
|
|
257
|
+
// // | cy*di ry Xy 0 | |y|
|
|
258
|
+
// // | cz*di rz Xz 0 | |z|
|
|
259
|
+
// // | tx ty tz 1 | |1|
|
|
260
|
+
// // const [
|
|
261
|
+
// // 0, 0 , 0 , 0,
|
|
262
|
+
// // 0, 0 , 0 , 0,
|
|
263
|
+
// // 0, 0 , 0 , 0,
|
|
264
|
+
// // ipp[0], ipp[1] , ipp[2] , 1,
|
|
265
|
+
// // ]
|
|
266
|
+
// // Each frame:
|
|
267
|
+
// // Find which corner the first voxel lines up with (one of 8 corners.)
|
|
268
|
+
// // Find how i,j,k orient with respect to source volume.
|
|
269
|
+
// // Go through each frame, find location in source to start, and whether to increment +/ix,+/-y,+/-z
|
|
270
|
+
// // through each voxel.
|
|
271
|
+
// // [1,0,0,0,1,0]
|
|
272
|
+
// // const [
|
|
273
|
+
// // ]
|
|
274
|
+
// // Invert transformation matrix to get worldToIndex
|
|
275
|
+
// // Apply world to index on each point to fill up the matrix.
|
|
276
|
+
// // const sharedImageOrientationPatient = SharedFunctionalGroupsSequence.PlaneOrientationSequence
|
|
277
|
+
// // ? SharedFunctionalGroupsSequence.PlaneOrientationSequence
|
|
278
|
+
// // .ImageOrientationPatient
|
|
279
|
+
// // : undefined;
|
|
280
|
+
// // const sliceLength = Columns * Rows;
|
|
281
|
+
// }
|
|
282
|
+
// function getCorners(imagePlaneModule) {
|
|
283
|
+
// // console.log(imagePlaneModule);
|
|
284
|
+
// const {
|
|
285
|
+
// rows,
|
|
286
|
+
// columns,
|
|
287
|
+
// rowCosines,
|
|
288
|
+
// columnCosines,
|
|
289
|
+
// imagePositionPatient: ipp,
|
|
290
|
+
// rowPixelSpacing,
|
|
291
|
+
// columnPixelSpacing
|
|
292
|
+
// } = imagePlaneModule;
|
|
293
|
+
// const rowLength = columns * columnPixelSpacing;
|
|
294
|
+
// const columnLength = rows * rowPixelSpacing;
|
|
295
|
+
// const entireRowVector = [
|
|
296
|
+
// rowLength * columnCosines[0],
|
|
297
|
+
// rowLength * columnCosines[1],
|
|
298
|
+
// rowLength * columnCosines[2]
|
|
299
|
+
// ];
|
|
300
|
+
// const entireColumnVector = [
|
|
301
|
+
// columnLength * rowCosines[0],
|
|
302
|
+
// columnLength * rowCosines[1],
|
|
303
|
+
// columnLength * rowCosines[2]
|
|
304
|
+
// ];
|
|
305
|
+
// const topLeft = [ipp[0], ipp[1], ipp[2]];
|
|
306
|
+
// const topRight = [
|
|
307
|
+
// topLeft[0] + entireRowVector[0],
|
|
308
|
+
// topLeft[1] + entireRowVector[1],
|
|
309
|
+
// topLeft[2] + entireRowVector[2]
|
|
310
|
+
// ];
|
|
311
|
+
// const bottomLeft = [
|
|
312
|
+
// topLeft[0] + entireColumnVector[0],
|
|
313
|
+
// topLeft[1] + entireColumnVector[1],
|
|
314
|
+
// topLeft[2] + entireColumnVector[2]
|
|
315
|
+
// ];
|
|
316
|
+
// const bottomRight = [
|
|
317
|
+
// bottomLeft[0] + entireRowVector[0],
|
|
318
|
+
// bottomLeft[1] + entireRowVector[1],
|
|
319
|
+
// bottomLeft[2] + entireRowVector[2]
|
|
320
|
+
// ];
|
|
321
|
+
// return [topLeft, topRight, bottomLeft, bottomRight];
|
|
322
|
+
// }
|
|
323
|
+
/**
|
|
324
|
+
* Find the reference frame of the segmentation frame in the source data.
|
|
325
|
+
*
|
|
326
|
+
* @param {Object} multiframe dicom metadata
|
|
327
|
+
* @param {Int} frameSegment frame dicom index
|
|
328
|
+
* @param {String[]} imageIds A list of imageIds.
|
|
329
|
+
* @param {Object} sopUIDImageIdIndexMap A map of SOPInstanceUID to imageId
|
|
330
|
+
* @param {Float} tolerance The tolerance parameter
|
|
331
|
+
*
|
|
332
|
+
* @returns {String} Returns the imageId
|
|
333
|
+
*/
|
|
334
|
+
function _generateToolState() {
|
|
335
|
+
_generateToolState = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(imageIds, arrayBuffer, metadataProvider, options) {
|
|
336
|
+
var _options$skipOverlapp, skipOverlapping, _options$tolerance, tolerance, _options$TypedArrayCo, TypedArrayConstructor, _options$maxBytesPerC, maxBytesPerChunk, eventTarget, triggerEvent, dicomData, dataset, multiframe, imagePlaneModule, generalSeriesModule, SeriesInstanceUID, ImageOrientationPatient, validOrientations, sliceLength, segMetadata, TransferSyntaxUID, pixelData, pixelDataChunks, rleEncodedFrames, orientation, sopUIDImageIdIndexMap, overlapping, insertFunction, segmentsOnFrameArray, segmentsOnFrame, arrayBufferLength, labelmapBufferArray, imageIdMaps, segmentsPixelIndices, overlappingSegments, centroidXYZ;
|
|
337
|
+
return _regeneratorRuntime().wrap(function _callee$(_context) {
|
|
338
|
+
while (1) switch (_context.prev = _context.next) {
|
|
339
|
+
case 0:
|
|
340
|
+
_options$skipOverlapp = options.skipOverlapping, skipOverlapping = _options$skipOverlapp === void 0 ? false : _options$skipOverlapp, _options$tolerance = options.tolerance, tolerance = _options$tolerance === void 0 ? 1e-3 : _options$tolerance, _options$TypedArrayCo = options.TypedArrayConstructor, TypedArrayConstructor = _options$TypedArrayCo === void 0 ? Uint8Array : _options$TypedArrayCo, _options$maxBytesPerC = options.maxBytesPerChunk, maxBytesPerChunk = _options$maxBytesPerC === void 0 ? 199000000 : _options$maxBytesPerC, eventTarget = options.eventTarget, triggerEvent = options.triggerEvent;
|
|
341
|
+
dicomData = DicomMessage.readFile(arrayBuffer);
|
|
342
|
+
dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
|
343
|
+
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
|
344
|
+
multiframe = Normalizer.normalizeToDataset([dataset]);
|
|
345
|
+
imagePlaneModule = metadataProvider.get("imagePlaneModule", imageIds[0]);
|
|
346
|
+
generalSeriesModule = metadataProvider.get("generalSeriesModule", imageIds[0]);
|
|
347
|
+
SeriesInstanceUID = generalSeriesModule.seriesInstanceUID;
|
|
348
|
+
if (!imagePlaneModule) {
|
|
349
|
+
console.warn("Insufficient metadata, imagePlaneModule missing.");
|
|
350
|
+
}
|
|
351
|
+
ImageOrientationPatient = Array.isArray(imagePlaneModule.rowCosines) ? [].concat(_toConsumableArray(imagePlaneModule.rowCosines), _toConsumableArray(imagePlaneModule.columnCosines)) : [imagePlaneModule.rowCosines.x, imagePlaneModule.rowCosines.y, imagePlaneModule.rowCosines.z, imagePlaneModule.columnCosines.x, imagePlaneModule.columnCosines.y, imagePlaneModule.columnCosines.z]; // Get IOP from ref series, compute supported orientations:
|
|
352
|
+
validOrientations = getValidOrientations(ImageOrientationPatient);
|
|
353
|
+
sliceLength = multiframe.Columns * multiframe.Rows;
|
|
354
|
+
segMetadata = getSegmentMetadata(multiframe, SeriesInstanceUID);
|
|
355
|
+
TransferSyntaxUID = multiframe._meta.TransferSyntaxUID.Value[0];
|
|
356
|
+
if (!(TransferSyntaxUID === "1.2.840.10008.1.2.5")) {
|
|
357
|
+
_context.next = 23;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
rleEncodedFrames = Array.isArray(multiframe.PixelData) ? multiframe.PixelData : [multiframe.PixelData];
|
|
361
|
+
pixelData = decode(rleEncodedFrames, multiframe.Rows, multiframe.Columns);
|
|
362
|
+
if (!(multiframe.BitsStored === 1)) {
|
|
363
|
+
_context.next = 20;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
console.warn("No implementation for rle + bitbacking.");
|
|
367
|
+
return _context.abrupt("return");
|
|
368
|
+
case 20:
|
|
369
|
+
// Todo: need to test this with rle data
|
|
370
|
+
pixelDataChunks = [pixelData];
|
|
371
|
+
_context.next = 26;
|
|
372
|
+
break;
|
|
373
|
+
case 23:
|
|
374
|
+
pixelDataChunks = unpackPixelData(multiframe, {
|
|
375
|
+
maxBytesPerChunk: maxBytesPerChunk
|
|
376
|
+
});
|
|
377
|
+
if (pixelDataChunks) {
|
|
378
|
+
_context.next = 26;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
throw new Error("Fractional segmentations are not yet supported");
|
|
382
|
+
case 26:
|
|
383
|
+
orientation = checkOrientation(multiframe, validOrientations, [imagePlaneModule.rows, imagePlaneModule.columns, imageIds.length], tolerance); // Pre-compute the sop UID to imageId index map so that in the for loop
|
|
384
|
+
// we don't have to call metadataProvider.get() for each imageId over
|
|
385
|
+
// and over again.
|
|
386
|
+
sopUIDImageIdIndexMap = imageIds.reduce(function (acc, imageId) {
|
|
387
|
+
var _metadataProvider$get = metadataProvider.get("generalImageModule", imageId),
|
|
388
|
+
sopInstanceUID = _metadataProvider$get.sopInstanceUID;
|
|
389
|
+
acc[sopInstanceUID] = imageId;
|
|
390
|
+
return acc;
|
|
391
|
+
}, {});
|
|
392
|
+
overlapping = false;
|
|
393
|
+
if (!skipOverlapping) {
|
|
394
|
+
overlapping = checkSEGsOverlapping(pixelDataChunks, multiframe, imageIds, validOrientations, metadataProvider, tolerance, TypedArrayConstructor, sopUIDImageIdIndexMap);
|
|
395
|
+
}
|
|
396
|
+
_context.t0 = orientation;
|
|
397
|
+
_context.next = _context.t0 === "Planar" ? 33 : _context.t0 === "Perpendicular" ? 35 : _context.t0 === "Oblique" ? 36 : 37;
|
|
398
|
+
break;
|
|
399
|
+
case 33:
|
|
400
|
+
if (overlapping) {
|
|
401
|
+
insertFunction = insertOverlappingPixelDataPlanar;
|
|
402
|
+
} else {
|
|
403
|
+
insertFunction = insertPixelDataPlanar;
|
|
404
|
+
}
|
|
405
|
+
return _context.abrupt("break", 37);
|
|
406
|
+
case 35:
|
|
407
|
+
throw new Error("Segmentations orthogonal to the acquisition plane of the source data are not yet supported.");
|
|
408
|
+
case 36:
|
|
409
|
+
throw new Error("Segmentations oblique to the acquisition plane of the source data are not yet supported.");
|
|
410
|
+
case 37:
|
|
411
|
+
/* if SEGs are overlapping:
|
|
412
|
+
1) the labelmapBuffer will contain M volumes which have non-overlapping segments;
|
|
413
|
+
2) segmentsOnFrame will have M * numberOfFrames values to track in which labelMap are the segments;
|
|
414
|
+
3) insertFunction will return the number of LabelMaps
|
|
415
|
+
4) generateToolState return is an array*/
|
|
416
|
+
segmentsOnFrameArray = [];
|
|
417
|
+
segmentsOnFrameArray[0] = [];
|
|
418
|
+
segmentsOnFrame = [];
|
|
419
|
+
arrayBufferLength = sliceLength * imageIds.length * TypedArrayConstructor.BYTES_PER_ELEMENT;
|
|
420
|
+
labelmapBufferArray = [];
|
|
421
|
+
labelmapBufferArray[0] = new ArrayBuffer(arrayBufferLength);
|
|
422
|
+
|
|
423
|
+
// Pre-compute the indices and metadata so that we don't have to call
|
|
424
|
+
// a function for each imageId in the for loop.
|
|
425
|
+
imageIdMaps = imageIds.reduce(function (acc, curr, index) {
|
|
426
|
+
acc.indices[curr] = index;
|
|
427
|
+
acc.metadata[curr] = metadataProvider.get("instance", curr);
|
|
428
|
+
return acc;
|
|
429
|
+
}, {
|
|
430
|
+
indices: {},
|
|
431
|
+
metadata: {}
|
|
432
|
+
}); // This is the centroid calculation for each segment Index, the data structure
|
|
433
|
+
// is a Map with key = segmentIndex and value = {imageIdIndex: centroid, ...}
|
|
434
|
+
// later on we will use this data structure to calculate the centroid of the
|
|
435
|
+
// segment in the labelmapBuffer
|
|
436
|
+
segmentsPixelIndices = new Map();
|
|
437
|
+
_context.next = 47;
|
|
438
|
+
return insertFunction(segmentsOnFrame, segmentsOnFrameArray, labelmapBufferArray, pixelDataChunks, multiframe, imageIds, validOrientations, metadataProvider, tolerance, TypedArrayConstructor, segmentsPixelIndices, sopUIDImageIdIndexMap, imageIdMaps, eventTarget, triggerEvent);
|
|
439
|
+
case 47:
|
|
440
|
+
overlappingSegments = _context.sent;
|
|
441
|
+
// calculate the centroid of each segment
|
|
442
|
+
centroidXYZ = new Map();
|
|
443
|
+
segmentsPixelIndices.forEach(function (imageIdIndexBufferIndex, segmentIndex) {
|
|
444
|
+
var centroids = calculateCentroid(imageIdIndexBufferIndex, multiframe, metadataProvider, imageIds);
|
|
445
|
+
centroidXYZ.set(segmentIndex, centroids);
|
|
446
|
+
});
|
|
447
|
+
return _context.abrupt("return", {
|
|
448
|
+
labelmapBufferArray: labelmapBufferArray,
|
|
449
|
+
segMetadata: segMetadata,
|
|
450
|
+
segmentsOnFrame: segmentsOnFrame,
|
|
451
|
+
segmentsOnFrameArray: segmentsOnFrameArray,
|
|
452
|
+
centroids: centroidXYZ,
|
|
453
|
+
overlappingSegments: overlappingSegments
|
|
454
|
+
});
|
|
455
|
+
case 51:
|
|
456
|
+
case "end":
|
|
457
|
+
return _context.stop();
|
|
458
|
+
}
|
|
459
|
+
}, _callee);
|
|
460
|
+
}));
|
|
461
|
+
return _generateToolState.apply(this, arguments);
|
|
462
|
+
}
|
|
463
|
+
function findReferenceSourceImageId(multiframe, frameSegment, imageIds, metadataProvider, tolerance, sopUIDImageIdIndexMap) {
|
|
464
|
+
var imageId = undefined;
|
|
465
|
+
if (!multiframe) {
|
|
466
|
+
return imageId;
|
|
467
|
+
}
|
|
468
|
+
var FrameOfReferenceUID = multiframe.FrameOfReferenceUID,
|
|
469
|
+
PerFrameFunctionalGroupsSequence = multiframe.PerFrameFunctionalGroupsSequence,
|
|
470
|
+
SourceImageSequence = multiframe.SourceImageSequence,
|
|
471
|
+
ReferencedSeriesSequence = multiframe.ReferencedSeriesSequence;
|
|
472
|
+
if (!PerFrameFunctionalGroupsSequence || PerFrameFunctionalGroupsSequence.length === 0) {
|
|
473
|
+
return imageId;
|
|
474
|
+
}
|
|
475
|
+
var PerFrameFunctionalGroup = PerFrameFunctionalGroupsSequence[frameSegment];
|
|
476
|
+
if (!PerFrameFunctionalGroup) {
|
|
477
|
+
return imageId;
|
|
478
|
+
}
|
|
479
|
+
var frameSourceImageSequence = undefined;
|
|
480
|
+
if (PerFrameFunctionalGroup.DerivationImageSequence) {
|
|
481
|
+
var DerivationImageSequence = PerFrameFunctionalGroup.DerivationImageSequence;
|
|
482
|
+
if (Array.isArray(DerivationImageSequence)) {
|
|
483
|
+
if (DerivationImageSequence.length !== 0) {
|
|
484
|
+
DerivationImageSequence = DerivationImageSequence[0];
|
|
485
|
+
} else {
|
|
486
|
+
DerivationImageSequence = undefined;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (DerivationImageSequence) {
|
|
490
|
+
frameSourceImageSequence = DerivationImageSequence.SourceImageSequence;
|
|
491
|
+
if (Array.isArray(frameSourceImageSequence)) {
|
|
492
|
+
if (frameSourceImageSequence.length !== 0) {
|
|
493
|
+
frameSourceImageSequence = frameSourceImageSequence[0];
|
|
494
|
+
} else {
|
|
495
|
+
frameSourceImageSequence = undefined;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
} else if (SourceImageSequence && SourceImageSequence.length !== 0) {
|
|
500
|
+
console.warn("DerivationImageSequence not present, using SourceImageSequence assuming SEG has the same geometry as the source image.");
|
|
501
|
+
frameSourceImageSequence = SourceImageSequence[frameSegment];
|
|
502
|
+
}
|
|
503
|
+
if (frameSourceImageSequence) {
|
|
504
|
+
imageId = getImageIdOfSourceImageBySourceImageSequence(frameSourceImageSequence, sopUIDImageIdIndexMap);
|
|
505
|
+
}
|
|
506
|
+
if (imageId === undefined && ReferencedSeriesSequence) {
|
|
507
|
+
var referencedSeriesSequence = Array.isArray(ReferencedSeriesSequence) ? ReferencedSeriesSequence[0] : ReferencedSeriesSequence;
|
|
508
|
+
var ReferencedSeriesInstanceUID = referencedSeriesSequence.SeriesInstanceUID;
|
|
509
|
+
imageId = getImageIdOfSourceImagebyGeometry(ReferencedSeriesInstanceUID, FrameOfReferenceUID, PerFrameFunctionalGroup, imageIds, metadataProvider, tolerance);
|
|
510
|
+
}
|
|
511
|
+
return imageId;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Checks if there is any overlapping segmentations.
|
|
516
|
+
* @returns {boolean} Returns a flag if segmentations overlapping
|
|
517
|
+
*/
|
|
518
|
+
|
|
519
|
+
function checkSEGsOverlapping(pixelData, multiframe, imageIds, validOrientations, metadataProvider, tolerance, TypedArrayConstructor, sopUIDImageIdIndexMap) {
|
|
520
|
+
var SharedFunctionalGroupsSequence = multiframe.SharedFunctionalGroupsSequence,
|
|
521
|
+
PerFrameFunctionalGroupsSequence = multiframe.PerFrameFunctionalGroupsSequence,
|
|
522
|
+
SegmentSequence = multiframe.SegmentSequence,
|
|
523
|
+
Rows = multiframe.Rows,
|
|
524
|
+
Columns = multiframe.Columns;
|
|
525
|
+
var numberOfSegs = SegmentSequence.length;
|
|
526
|
+
if (numberOfSegs < 2) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
var sharedImageOrientationPatient = SharedFunctionalGroupsSequence.PlaneOrientationSequence ? SharedFunctionalGroupsSequence.PlaneOrientationSequence.ImageOrientationPatient : undefined;
|
|
530
|
+
var sliceLength = Columns * Rows;
|
|
531
|
+
var groupsLen = PerFrameFunctionalGroupsSequence.length;
|
|
532
|
+
|
|
533
|
+
/** sort groupsLen to have all the segments for each frame in an array
|
|
534
|
+
* frame 2 : 1, 2
|
|
535
|
+
* frame 4 : 1, 3
|
|
536
|
+
* frame 5 : 4
|
|
537
|
+
*/
|
|
538
|
+
|
|
539
|
+
var frameSegmentsMapping = new Map();
|
|
540
|
+
var _loop3 = function _loop3() {
|
|
541
|
+
var segmentIndex = getSegmentIndex(multiframe, frameSegment);
|
|
542
|
+
if (segmentIndex === undefined) {
|
|
543
|
+
console.warn("Could not retrieve the segment index for frame segment " + frameSegment + ", skipping this frame.");
|
|
544
|
+
return 0; // continue
|
|
545
|
+
}
|
|
546
|
+
var imageId = findReferenceSourceImageId(multiframe, frameSegment, imageIds, metadataProvider, tolerance, sopUIDImageIdIndexMap);
|
|
547
|
+
if (!imageId) {
|
|
548
|
+
console.warn("Image not present in stack, can't import frame : " + frameSegment + ".");
|
|
549
|
+
return 0; // continue
|
|
550
|
+
}
|
|
551
|
+
var imageIdIndex = imageIds.findIndex(function (element) {
|
|
552
|
+
return element === imageId;
|
|
553
|
+
});
|
|
554
|
+
if (frameSegmentsMapping.has(imageIdIndex)) {
|
|
555
|
+
var segmentArray = frameSegmentsMapping.get(imageIdIndex);
|
|
556
|
+
if (!segmentArray.includes(frameSegment)) {
|
|
557
|
+
segmentArray.push(frameSegment);
|
|
558
|
+
frameSegmentsMapping.set(imageIdIndex, segmentArray);
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
frameSegmentsMapping.set(imageIdIndex, [frameSegment]);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
_ret;
|
|
565
|
+
for (var frameSegment = 0; frameSegment < groupsLen; ++frameSegment) {
|
|
566
|
+
_ret = _loop3();
|
|
567
|
+
if (_ret === 0) continue;
|
|
568
|
+
}
|
|
569
|
+
var _iterator = _createForOfIteratorHelper(frameSegmentsMapping.entries()),
|
|
570
|
+
_step;
|
|
571
|
+
try {
|
|
572
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
573
|
+
var _step$value = _slicedToArray(_step.value, 2),
|
|
574
|
+
role = _step$value[1];
|
|
575
|
+
var temp2DArray = new TypedArrayConstructor(sliceLength).fill(0);
|
|
576
|
+
for (var i = 0; i < role.length; ++i) {
|
|
577
|
+
var _frameSegment = role[i];
|
|
578
|
+
var PerFrameFunctionalGroups = PerFrameFunctionalGroupsSequence[_frameSegment];
|
|
579
|
+
var ImageOrientationPatientI = sharedImageOrientationPatient || PerFrameFunctionalGroups.PlaneOrientationSequence.ImageOrientationPatient;
|
|
580
|
+
var view = readFromUnpackedChunks(pixelData, _frameSegment * sliceLength, sliceLength);
|
|
581
|
+
var pixelDataI2D = ndarray(view, [Rows, Columns]);
|
|
582
|
+
var alignedPixelDataI = alignPixelDataWithSourceData(pixelDataI2D, ImageOrientationPatientI, validOrientations, tolerance);
|
|
583
|
+
if (!alignedPixelDataI) {
|
|
584
|
+
console.warn("Individual SEG frames are out of plane with respect to the first SEG frame, this is not yet supported, skipping this frame.");
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
var data = alignedPixelDataI.data;
|
|
588
|
+
for (var j = 0, len = data.length; j < len; ++j) {
|
|
589
|
+
if (data[j] !== 0) {
|
|
590
|
+
temp2DArray[j]++;
|
|
591
|
+
if (temp2DArray[j] > 1) {
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
} catch (err) {
|
|
599
|
+
_iterator.e(err);
|
|
600
|
+
} finally {
|
|
601
|
+
_iterator.f();
|
|
602
|
+
}
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
function insertOverlappingPixelDataPlanar(segmentsOnFrame, segmentsOnFrameArray, labelmapBufferArray, pixelData, multiframe, imageIds, validOrientations, metadataProvider, tolerance, TypedArrayConstructor, segmentsPixelIndices, sopUIDImageIdIndexMap) {
|
|
606
|
+
var SharedFunctionalGroupsSequence = multiframe.SharedFunctionalGroupsSequence,
|
|
607
|
+
PerFrameFunctionalGroupsSequence = multiframe.PerFrameFunctionalGroupsSequence,
|
|
608
|
+
Rows = multiframe.Rows,
|
|
609
|
+
Columns = multiframe.Columns;
|
|
610
|
+
var sharedImageOrientationPatient = SharedFunctionalGroupsSequence.PlaneOrientationSequence ? SharedFunctionalGroupsSequence.PlaneOrientationSequence.ImageOrientationPatient : undefined;
|
|
611
|
+
var sliceLength = Columns * Rows;
|
|
612
|
+
var arrayBufferLength = sliceLength * imageIds.length * TypedArrayConstructor.BYTES_PER_ELEMENT;
|
|
613
|
+
// indicate the number of labelMaps
|
|
614
|
+
var M = 1;
|
|
615
|
+
|
|
616
|
+
// indicate the current labelMap array index;
|
|
617
|
+
var m = 0;
|
|
618
|
+
|
|
619
|
+
// temp array for checking overlaps
|
|
620
|
+
var tempBuffer = labelmapBufferArray[m].slice(0);
|
|
621
|
+
|
|
622
|
+
// temp list for checking overlaps
|
|
623
|
+
var tempSegmentsOnFrame = structuredClone(segmentsOnFrameArray[m]);
|
|
624
|
+
|
|
625
|
+
/** split overlapping SEGs algorithm for each segment:
|
|
626
|
+
* A) copy the labelmapBuffer in the array with index 0
|
|
627
|
+
* B) add the segment pixel per pixel on the copied buffer from (A)
|
|
628
|
+
* C) if no overlap, copy the results back on the orignal array from (A)
|
|
629
|
+
* D) if overlap, repeat increasing the index m up to M (if out of memory, add new buffer in the array and M++);
|
|
630
|
+
*/
|
|
631
|
+
|
|
632
|
+
var numberOfSegs = multiframe.SegmentSequence.length;
|
|
633
|
+
for (var segmentIndexToProcess = 1; segmentIndexToProcess <= numberOfSegs; ++segmentIndexToProcess) {
|
|
634
|
+
var _loop4 = function _loop4(_i2) {
|
|
635
|
+
var PerFrameFunctionalGroups = PerFrameFunctionalGroupsSequence[_i2];
|
|
636
|
+
var segmentIndex = getSegmentIndex(multiframe, _i2);
|
|
637
|
+
if (segmentIndex === undefined) {
|
|
638
|
+
throw new Error("Could not retrieve the segment index. Aborting segmentation loading.");
|
|
639
|
+
}
|
|
640
|
+
if (segmentIndex !== segmentIndexToProcess) {
|
|
641
|
+
i = _i2;
|
|
642
|
+
return 0; // continue
|
|
643
|
+
}
|
|
644
|
+
var ImageOrientationPatientI = sharedImageOrientationPatient || PerFrameFunctionalGroups.PlaneOrientationSequence.ImageOrientationPatient;
|
|
645
|
+
|
|
646
|
+
// Since we moved to the chunks approach, we need to read the data
|
|
647
|
+
// and handle scenarios where the portion of data is in one chunk
|
|
648
|
+
// and the other portion is in another chunk
|
|
649
|
+
var view = readFromUnpackedChunks(pixelData, _i2 * sliceLength, sliceLength);
|
|
650
|
+
var pixelDataI2D = ndarray(view, [Rows, Columns]);
|
|
651
|
+
var alignedPixelDataI = alignPixelDataWithSourceData(pixelDataI2D, ImageOrientationPatientI, validOrientations, tolerance);
|
|
652
|
+
if (!alignedPixelDataI) {
|
|
653
|
+
throw new Error("Individual SEG frames are out of plane with respect to the first SEG frame. " + "This is not yet supported. Aborting segmentation loading.");
|
|
654
|
+
}
|
|
655
|
+
var imageId = findReferenceSourceImageId(multiframe, _i2, imageIds, metadataProvider, tolerance, sopUIDImageIdIndexMap);
|
|
656
|
+
if (!imageId) {
|
|
657
|
+
console.warn("Image not present in stack, can't import frame : " + _i2 + ".");
|
|
658
|
+
i = _i2;
|
|
659
|
+
return 0; // continue
|
|
660
|
+
}
|
|
661
|
+
var sourceImageMetadata = metadataProvider.get("instance", imageId);
|
|
662
|
+
if (Rows !== sourceImageMetadata.Rows || Columns !== sourceImageMetadata.Columns) {
|
|
663
|
+
throw new Error("Individual SEG frames have different geometry dimensions (Rows and Columns) " + "respect to the source image reference frame. This is not yet supported. " + "Aborting segmentation loading. ");
|
|
664
|
+
}
|
|
665
|
+
var imageIdIndex = imageIds.findIndex(function (element) {
|
|
666
|
+
return element === imageId;
|
|
667
|
+
});
|
|
668
|
+
var byteOffset = sliceLength * imageIdIndex * TypedArrayConstructor.BYTES_PER_ELEMENT;
|
|
669
|
+
var labelmap2DView = new TypedArrayConstructor(tempBuffer, byteOffset, sliceLength);
|
|
670
|
+
var data = alignedPixelDataI.data;
|
|
671
|
+
var segmentOnFrame = false;
|
|
672
|
+
for (var j = 0, len = alignedPixelDataI.data.length; j < len; ++j) {
|
|
673
|
+
if (data[j]) {
|
|
674
|
+
if (labelmap2DView[j] !== 0) {
|
|
675
|
+
m++;
|
|
676
|
+
if (m >= M) {
|
|
677
|
+
labelmapBufferArray[m] = new ArrayBuffer(arrayBufferLength);
|
|
678
|
+
segmentsOnFrameArray[m] = [];
|
|
679
|
+
M++;
|
|
680
|
+
}
|
|
681
|
+
tempBuffer = labelmapBufferArray[m].slice(0);
|
|
682
|
+
tempSegmentsOnFrame = structuredClone(segmentsOnFrameArray[m]);
|
|
683
|
+
_i2 = 0;
|
|
684
|
+
break;
|
|
685
|
+
} else {
|
|
686
|
+
labelmap2DView[j] = segmentIndex;
|
|
687
|
+
segmentOnFrame = true;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (segmentOnFrame) {
|
|
692
|
+
if (!tempSegmentsOnFrame[imageIdIndex]) {
|
|
693
|
+
tempSegmentsOnFrame[imageIdIndex] = [];
|
|
694
|
+
}
|
|
695
|
+
tempSegmentsOnFrame[imageIdIndex].push(segmentIndex);
|
|
696
|
+
if (!segmentsOnFrame[imageIdIndex]) {
|
|
697
|
+
segmentsOnFrame[imageIdIndex] = [];
|
|
698
|
+
}
|
|
699
|
+
segmentsOnFrame[imageIdIndex].push(segmentIndex);
|
|
700
|
+
}
|
|
701
|
+
i = _i2;
|
|
702
|
+
},
|
|
703
|
+
_ret2;
|
|
704
|
+
for (var i = 0, groupsLen = PerFrameFunctionalGroupsSequence.length; i < groupsLen; ++i) {
|
|
705
|
+
_ret2 = _loop4(i);
|
|
706
|
+
if (_ret2 === 0) continue;
|
|
707
|
+
}
|
|
708
|
+
labelmapBufferArray[m] = tempBuffer.slice(0);
|
|
709
|
+
segmentsOnFrameArray[m] = structuredClone(tempSegmentsOnFrame);
|
|
710
|
+
|
|
711
|
+
// reset temp variables/buffers for new segment
|
|
712
|
+
m = 0;
|
|
713
|
+
tempBuffer = labelmapBufferArray[m].slice(0);
|
|
714
|
+
tempSegmentsOnFrame = structuredClone(segmentsOnFrameArray[m]);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
var getSegmentIndex = function getSegmentIndex(multiframe, frame) {
|
|
718
|
+
var PerFrameFunctionalGroupsSequence = multiframe.PerFrameFunctionalGroupsSequence,
|
|
719
|
+
SharedFunctionalGroupsSequence = multiframe.SharedFunctionalGroupsSequence;
|
|
720
|
+
var PerFrameFunctionalGroups = PerFrameFunctionalGroupsSequence[frame];
|
|
721
|
+
return PerFrameFunctionalGroups && PerFrameFunctionalGroups.SegmentIdentificationSequence ? PerFrameFunctionalGroups.SegmentIdentificationSequence.ReferencedSegmentNumber : SharedFunctionalGroupsSequence.SegmentIdentificationSequence ? SharedFunctionalGroupsSequence.SegmentIdentificationSequence.ReferencedSegmentNumber : undefined;
|
|
722
|
+
};
|
|
723
|
+
function insertPixelDataPlanar(segmentsOnFrame, segmentsOnFrameArray, labelmapBufferArray, pixelData, multiframe, imageIds, validOrientations, metadataProvider, tolerance, TypedArrayConstructor, segmentsPixelIndices, sopUIDImageIdIndexMap, imageIdMaps, eventTarget, triggerEvent) {
|
|
724
|
+
var SharedFunctionalGroupsSequence = multiframe.SharedFunctionalGroupsSequence,
|
|
725
|
+
PerFrameFunctionalGroupsSequence = multiframe.PerFrameFunctionalGroupsSequence,
|
|
726
|
+
Rows = multiframe.Rows,
|
|
727
|
+
Columns = multiframe.Columns;
|
|
728
|
+
var sharedImageOrientationPatient = SharedFunctionalGroupsSequence.PlaneOrientationSequence ? SharedFunctionalGroupsSequence.PlaneOrientationSequence.ImageOrientationPatient : undefined;
|
|
729
|
+
var sliceLength = Columns * Rows;
|
|
730
|
+
var i = 0;
|
|
731
|
+
var groupsLen = PerFrameFunctionalGroupsSequence.length;
|
|
732
|
+
var chunkSize = Math.ceil(groupsLen / 10); // 10% of total length
|
|
733
|
+
|
|
734
|
+
var shouldTriggerEvent = triggerEvent && eventTarget;
|
|
735
|
+
var overlapping = false;
|
|
736
|
+
// Below, we chunk the processing of the frames to avoid blocking the main thread
|
|
737
|
+
// if the segmentation is large. We also use a promise to allow the caller to
|
|
738
|
+
// wait for the processing to finish.
|
|
739
|
+
return new Promise(function (resolve) {
|
|
740
|
+
function processInChunks() {
|
|
741
|
+
// process one chunk
|
|
742
|
+
for (var end = Math.min(i + chunkSize, groupsLen); i < end; ++i) {
|
|
743
|
+
var PerFrameFunctionalGroups = PerFrameFunctionalGroupsSequence[i];
|
|
744
|
+
var ImageOrientationPatientI = sharedImageOrientationPatient || PerFrameFunctionalGroups.PlaneOrientationSequence.ImageOrientationPatient;
|
|
745
|
+
var view = readFromUnpackedChunks(pixelData, i * sliceLength, sliceLength);
|
|
746
|
+
var pixelDataI2D = ndarray(view, [Rows, Columns]);
|
|
747
|
+
var alignedPixelDataI = alignPixelDataWithSourceData(pixelDataI2D, ImageOrientationPatientI, validOrientations, tolerance);
|
|
748
|
+
if (!alignedPixelDataI) {
|
|
749
|
+
throw new Error("Individual SEG frames are out of plane with respect to the first SEG frame. " + "This is not yet supported. Aborting segmentation loading.");
|
|
750
|
+
}
|
|
751
|
+
var segmentIndex = getSegmentIndex(multiframe, i);
|
|
752
|
+
if (segmentIndex === undefined) {
|
|
753
|
+
throw new Error("Could not retrieve the segment index. Aborting segmentation loading.");
|
|
754
|
+
}
|
|
755
|
+
if (!segmentsPixelIndices.has(segmentIndex)) {
|
|
756
|
+
segmentsPixelIndices.set(segmentIndex, {});
|
|
757
|
+
}
|
|
758
|
+
var imageId = findReferenceSourceImageId(multiframe, i, imageIds, metadataProvider, tolerance, sopUIDImageIdIndexMap);
|
|
759
|
+
if (!imageId) {
|
|
760
|
+
console.warn("Image not present in stack, can't import frame : " + i + ".");
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
var sourceImageMetadata = imageIdMaps.metadata[imageId];
|
|
764
|
+
if (Rows !== sourceImageMetadata.Rows || Columns !== sourceImageMetadata.Columns) {
|
|
765
|
+
throw new Error("Individual SEG frames have different geometry dimensions (Rows and Columns) " + "respect to the source image reference frame. This is not yet supported. " + "Aborting segmentation loading. ");
|
|
766
|
+
}
|
|
767
|
+
var imageIdIndex = imageIdMaps.indices[imageId];
|
|
768
|
+
var byteOffset = sliceLength * imageIdIndex * TypedArrayConstructor.BYTES_PER_ELEMENT;
|
|
769
|
+
var labelmap2DView = new TypedArrayConstructor(labelmapBufferArray[0], byteOffset, sliceLength);
|
|
770
|
+
var data = alignedPixelDataI.data;
|
|
771
|
+
var indexCache = [];
|
|
772
|
+
for (var j = 0, len = alignedPixelDataI.data.length; j < len; ++j) {
|
|
773
|
+
if (data[j]) {
|
|
774
|
+
for (var x = j; x < len; ++x) {
|
|
775
|
+
if (data[x]) {
|
|
776
|
+
if (!overlapping && labelmap2DView[x] !== 0) {
|
|
777
|
+
overlapping = true;
|
|
778
|
+
}
|
|
779
|
+
labelmap2DView[x] = segmentIndex;
|
|
780
|
+
indexCache.push(x);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (!segmentsOnFrame[imageIdIndex]) {
|
|
784
|
+
segmentsOnFrame[imageIdIndex] = [];
|
|
785
|
+
}
|
|
786
|
+
segmentsOnFrame[imageIdIndex].push(segmentIndex);
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
var segmentIndexObject = segmentsPixelIndices.get(segmentIndex);
|
|
791
|
+
segmentIndexObject[imageIdIndex] = indexCache;
|
|
792
|
+
segmentsPixelIndices.set(segmentIndex, segmentIndexObject);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// trigger an event after each chunk
|
|
796
|
+
if (shouldTriggerEvent) {
|
|
797
|
+
var percentComplete = Math.round(i / groupsLen * 100);
|
|
798
|
+
triggerEvent(eventTarget, Events.SEGMENTATION_LOAD_PROGRESS, {
|
|
799
|
+
percentComplete: percentComplete
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// schedule next chunk
|
|
804
|
+
if (i < groupsLen) {
|
|
805
|
+
setTimeout(processInChunks, 0);
|
|
806
|
+
} else {
|
|
807
|
+
// resolve the Promise when all chunks have been processed
|
|
808
|
+
resolve(overlapping);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
processInChunks();
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* unpackPixelData - Unpacks bit packed pixelData if the Segmentation is BINARY.
|
|
817
|
+
*
|
|
818
|
+
* @param {Object} multiframe The multiframe dataset.
|
|
819
|
+
* @param {Object} options Options for the unpacking.
|
|
820
|
+
* @return {Uint8Array} The unpacked pixelData.
|
|
821
|
+
*/
|
|
822
|
+
function unpackPixelData(multiframe, options) {
|
|
823
|
+
var segType = multiframe.SegmentationType;
|
|
824
|
+
var data;
|
|
825
|
+
if (Array.isArray(multiframe.PixelData)) {
|
|
826
|
+
data = multiframe.PixelData[0];
|
|
827
|
+
} else {
|
|
828
|
+
data = multiframe.PixelData;
|
|
829
|
+
}
|
|
830
|
+
if (data === undefined) {
|
|
831
|
+
log.error("This segmentation pixelData is undefined.");
|
|
832
|
+
}
|
|
833
|
+
if (segType === "BINARY") {
|
|
834
|
+
// For extreme big data, we can't unpack the data at once and we need to
|
|
835
|
+
// chunk it and unpack each chunk separately.
|
|
836
|
+
// MAX 2GB is the limit right now to allocate a buffer
|
|
837
|
+
return getUnpackedChunks(data, options.maxBytesPerChunk);
|
|
838
|
+
}
|
|
839
|
+
var pixelData = new Uint8Array(data);
|
|
840
|
+
var max = multiframe.MaximumFractionalValue;
|
|
841
|
+
var onlyMaxAndZero = pixelData.find(function (element) {
|
|
842
|
+
return element !== 0 && element !== max;
|
|
843
|
+
}) === undefined;
|
|
844
|
+
if (!onlyMaxAndZero) {
|
|
845
|
+
// This is a fractional segmentation, which is not currently supported.
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
log.warn("This segmentation object is actually binary... processing as such.");
|
|
849
|
+
return pixelData;
|
|
850
|
+
}
|
|
851
|
+
function getUnpackedChunks(data, maxBytesPerChunk) {
|
|
852
|
+
var bitArray = new Uint8Array(data);
|
|
853
|
+
var chunks = [];
|
|
854
|
+
var maxBitsPerChunk = maxBytesPerChunk * 8;
|
|
855
|
+
var numberOfChunks = Math.ceil(bitArray.length * 8 / maxBitsPerChunk);
|
|
856
|
+
for (var i = 0; i < numberOfChunks; i++) {
|
|
857
|
+
var startBit = i * maxBitsPerChunk;
|
|
858
|
+
var endBit = Math.min(startBit + maxBitsPerChunk, bitArray.length * 8);
|
|
859
|
+
var startByte = Math.floor(startBit / 8);
|
|
860
|
+
var endByte = Math.ceil(endBit / 8);
|
|
861
|
+
var chunk = bitArray.slice(startByte, endByte);
|
|
862
|
+
var unpackedChunk = BitArray.unpack(chunk);
|
|
863
|
+
chunks.push(unpackedChunk);
|
|
864
|
+
}
|
|
865
|
+
return chunks;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* getImageIdOfSourceImageBySourceImageSequence - Returns the Cornerstone imageId of the source image.
|
|
870
|
+
*
|
|
871
|
+
* @param {Object} SourceImageSequence Sequence describing the source image.
|
|
872
|
+
* @param {String[]} imageIds A list of imageIds.
|
|
873
|
+
* @param {Object} sopUIDImageIdIndexMap A map of SOPInstanceUIDs to imageIds.
|
|
874
|
+
* @return {String} The corresponding imageId.
|
|
875
|
+
*/
|
|
876
|
+
function getImageIdOfSourceImageBySourceImageSequence(SourceImageSequence, sopUIDImageIdIndexMap) {
|
|
877
|
+
var ReferencedSOPInstanceUID = SourceImageSequence.ReferencedSOPInstanceUID,
|
|
878
|
+
ReferencedFrameNumber = SourceImageSequence.ReferencedFrameNumber;
|
|
879
|
+
return ReferencedFrameNumber ? getImageIdOfReferencedFrame(ReferencedSOPInstanceUID, ReferencedFrameNumber, sopUIDImageIdIndexMap) : sopUIDImageIdIndexMap[ReferencedSOPInstanceUID];
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* getImageIdOfSourceImagebyGeometry - Returns the Cornerstone imageId of the source image.
|
|
884
|
+
*
|
|
885
|
+
* @param {String} ReferencedSeriesInstanceUID Referenced series of the source image.
|
|
886
|
+
* @param {String} FrameOfReferenceUID Frame of reference.
|
|
887
|
+
* @param {Object} PerFrameFunctionalGroup Sequence describing segmentation reference attributes per frame.
|
|
888
|
+
* @param {String[]} imageIds A list of imageIds.
|
|
889
|
+
* @param {Object} sopUIDImageIdIndexMap A map of SOPInstanceUIDs to imageIds.
|
|
890
|
+
* @param {Float} tolerance The tolerance parameter
|
|
891
|
+
*
|
|
892
|
+
* @return {String} The corresponding imageId.
|
|
893
|
+
*/
|
|
894
|
+
function getImageIdOfSourceImagebyGeometry(ReferencedSeriesInstanceUID, FrameOfReferenceUID, PerFrameFunctionalGroup, imageIds, metadataProvider, tolerance) {
|
|
895
|
+
if (ReferencedSeriesInstanceUID === undefined || PerFrameFunctionalGroup.PlanePositionSequence === undefined || PerFrameFunctionalGroup.PlanePositionSequence[0] === undefined || PerFrameFunctionalGroup.PlanePositionSequence[0].ImagePositionPatient === undefined) {
|
|
896
|
+
return undefined;
|
|
897
|
+
}
|
|
898
|
+
for (var imageIdsIndexc = 0; imageIdsIndexc < imageIds.length; ++imageIdsIndexc) {
|
|
899
|
+
var sourceImageMetadata = metadataProvider.get("instance", imageIds[imageIdsIndexc]);
|
|
900
|
+
if (sourceImageMetadata === undefined || sourceImageMetadata.ImagePositionPatient === undefined || sourceImageMetadata.FrameOfReferenceUID !== FrameOfReferenceUID || sourceImageMetadata.SeriesInstanceUID !== ReferencedSeriesInstanceUID) {
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (compareArrays(PerFrameFunctionalGroup.PlanePositionSequence[0].ImagePositionPatient, sourceImageMetadata.ImagePositionPatient, tolerance)) {
|
|
904
|
+
return imageIds[imageIdsIndexc];
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* getImageIdOfReferencedFrame - Returns the imageId corresponding to the
|
|
911
|
+
* specified sopInstanceUid and frameNumber for multi-frame images.
|
|
912
|
+
*
|
|
913
|
+
* @param {String} sopInstanceUid The sopInstanceUid of the desired image.
|
|
914
|
+
* @param {Number} frameNumber The frame number.
|
|
915
|
+
* @param {String} imageIds The list of imageIds.
|
|
916
|
+
* @param {Object} sopUIDImageIdIndexMap A map of SOPInstanceUIDs to imageIds.
|
|
917
|
+
* @return {String} The imageId that corresponds to the sopInstanceUid.
|
|
918
|
+
*/
|
|
919
|
+
function getImageIdOfReferencedFrame(sopInstanceUid, frameNumber, sopUIDImageIdIndexMap) {
|
|
920
|
+
var imageId = sopUIDImageIdIndexMap[sopInstanceUid];
|
|
921
|
+
if (!imageId) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
var imageIdFrameNumber = Number(imageId.split("frame=")[1]);
|
|
925
|
+
return imageIdFrameNumber === frameNumber - 1 ? imageId : undefined;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* getValidOrientations - returns an array of valid orientations.
|
|
930
|
+
*
|
|
931
|
+
* @param {Number[6]} iop The row (0..2) an column (3..5) direction cosines.
|
|
932
|
+
* @return {Number[8][6]} An array of valid orientations.
|
|
933
|
+
*/
|
|
934
|
+
function getValidOrientations(iop) {
|
|
935
|
+
var orientations = [];
|
|
936
|
+
|
|
937
|
+
// [0, 1, 2]: 0, 0hf, 0vf
|
|
938
|
+
// [3, 4, 5]: 90, 90hf, 90vf
|
|
939
|
+
// [6, 7]: 180, 270
|
|
940
|
+
|
|
941
|
+
orientations[0] = iop;
|
|
942
|
+
orientations[1] = flipIOP.h(iop);
|
|
943
|
+
orientations[2] = flipIOP.v(iop);
|
|
944
|
+
var iop90 = rotateDirectionCosinesInPlane(iop, Math.PI / 2);
|
|
945
|
+
orientations[3] = iop90;
|
|
946
|
+
orientations[4] = flipIOP.h(iop90);
|
|
947
|
+
orientations[5] = flipIOP.v(iop90);
|
|
948
|
+
orientations[6] = rotateDirectionCosinesInPlane(iop, Math.PI);
|
|
949
|
+
orientations[7] = rotateDirectionCosinesInPlane(iop, 1.5 * Math.PI);
|
|
950
|
+
return orientations;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* alignPixelDataWithSourceData -
|
|
955
|
+
*
|
|
956
|
+
* @param {Ndarray} pixelData2D - The data to align.
|
|
957
|
+
* @param {Number[6]} iop - The orientation of the image slice.
|
|
958
|
+
* @param {Number[8][6]} orientations - An array of valid imageOrientationPatient values.
|
|
959
|
+
* @param {Number} tolerance.
|
|
960
|
+
* @return {Ndarray} The aligned pixelData.
|
|
961
|
+
*/
|
|
962
|
+
function alignPixelDataWithSourceData(pixelData2D, iop, orientations, tolerance) {
|
|
963
|
+
if (compareArrays(iop, orientations[0], tolerance)) {
|
|
964
|
+
return pixelData2D;
|
|
965
|
+
} else if (compareArrays(iop, orientations[1], tolerance)) {
|
|
966
|
+
// Flipped vertically.
|
|
967
|
+
|
|
968
|
+
// Undo Flip
|
|
969
|
+
return flipMatrix2D.v(pixelData2D);
|
|
970
|
+
} else if (compareArrays(iop, orientations[2], tolerance)) {
|
|
971
|
+
// Flipped horizontally.
|
|
972
|
+
|
|
973
|
+
// Unfo flip
|
|
974
|
+
return flipMatrix2D.h(pixelData2D);
|
|
975
|
+
} else if (compareArrays(iop, orientations[3], tolerance)) {
|
|
976
|
+
//Rotated 90 degrees
|
|
977
|
+
|
|
978
|
+
// Rotate back
|
|
979
|
+
return rotateMatrix902D(pixelData2D);
|
|
980
|
+
} else if (compareArrays(iop, orientations[4], tolerance)) {
|
|
981
|
+
//Rotated 90 degrees and fliped horizontally.
|
|
982
|
+
|
|
983
|
+
// Undo flip and rotate back.
|
|
984
|
+
return rotateMatrix902D(flipMatrix2D.h(pixelData2D));
|
|
985
|
+
} else if (compareArrays(iop, orientations[5], tolerance)) {
|
|
986
|
+
// Rotated 90 degrees and fliped vertically
|
|
987
|
+
|
|
988
|
+
// Unfo flip and rotate back.
|
|
989
|
+
return rotateMatrix902D(flipMatrix2D.v(pixelData2D));
|
|
990
|
+
} else if (compareArrays(iop, orientations[6], tolerance)) {
|
|
991
|
+
// Rotated 180 degrees. // TODO -> Do this more effeciently, there is a 1:1 mapping like 90 degree rotation.
|
|
992
|
+
|
|
993
|
+
return rotateMatrix902D(rotateMatrix902D(pixelData2D));
|
|
994
|
+
} else if (compareArrays(iop, orientations[7], tolerance)) {
|
|
995
|
+
// Rotated 270 degrees
|
|
996
|
+
|
|
997
|
+
// Rotate back.
|
|
998
|
+
return rotateMatrix902D(rotateMatrix902D(rotateMatrix902D(pixelData2D)));
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function getSegmentMetadata(multiframe, seriesInstanceUid) {
|
|
1002
|
+
var segmentSequence = multiframe.SegmentSequence;
|
|
1003
|
+
var data = [];
|
|
1004
|
+
if (Array.isArray(segmentSequence)) {
|
|
1005
|
+
data = [undefined].concat(_toConsumableArray(segmentSequence));
|
|
1006
|
+
} else {
|
|
1007
|
+
// Only one segment, will be stored as an object.
|
|
1008
|
+
data = [undefined, segmentSequence];
|
|
1009
|
+
}
|
|
1010
|
+
return {
|
|
1011
|
+
seriesInstanceUid: seriesInstanceUid,
|
|
1012
|
+
data: data
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Reads a range of bytes from an array of ArrayBuffer chunks and
|
|
1018
|
+
* aggregate them into a new Uint8Array.
|
|
1019
|
+
*
|
|
1020
|
+
* @param {ArrayBuffer[]} chunks - An array of ArrayBuffer chunks.
|
|
1021
|
+
* @param {number} offset - The offset of the first byte to read.
|
|
1022
|
+
* @param {number} length - The number of bytes to read.
|
|
1023
|
+
* @returns {Uint8Array} A new Uint8Array containing the requested bytes.
|
|
1024
|
+
*/
|
|
1025
|
+
function readFromUnpackedChunks(chunks, offset, length) {
|
|
1026
|
+
var mapping = getUnpackedOffsetAndLength(chunks, offset, length);
|
|
1027
|
+
|
|
1028
|
+
// If all the data is in one chunk, we can just slice that chunk
|
|
1029
|
+
if (mapping.start.chunkIndex === mapping.end.chunkIndex) {
|
|
1030
|
+
return new Uint8Array(chunks[mapping.start.chunkIndex].buffer, mapping.start.offset, length);
|
|
1031
|
+
} else {
|
|
1032
|
+
// If the data spans multiple chunks, we need to create a new Uint8Array and copy the data from each chunk
|
|
1033
|
+
var result = new Uint8Array(length);
|
|
1034
|
+
var resultOffset = 0;
|
|
1035
|
+
for (var i = mapping.start.chunkIndex; i <= mapping.end.chunkIndex; i++) {
|
|
1036
|
+
var start = i === mapping.start.chunkIndex ? mapping.start.offset : 0;
|
|
1037
|
+
var end = i === mapping.end.chunkIndex ? mapping.end.offset : chunks[i].length;
|
|
1038
|
+
result.set(new Uint8Array(chunks[i].buffer, start, end - start), resultOffset);
|
|
1039
|
+
resultOffset += end - start;
|
|
1040
|
+
}
|
|
1041
|
+
return result;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function getUnpackedOffsetAndLength(chunks, offset, length) {
|
|
1045
|
+
var totalBytes = chunks.reduce(function (total, chunk) {
|
|
1046
|
+
return total + chunk.length;
|
|
1047
|
+
}, 0);
|
|
1048
|
+
if (offset < 0 || offset + length > totalBytes) {
|
|
1049
|
+
throw new Error("Offset and length out of bounds");
|
|
1050
|
+
}
|
|
1051
|
+
var startChunkIndex = 0;
|
|
1052
|
+
var startOffsetInChunk = offset;
|
|
1053
|
+
while (startOffsetInChunk >= chunks[startChunkIndex].length) {
|
|
1054
|
+
startOffsetInChunk -= chunks[startChunkIndex].length;
|
|
1055
|
+
startChunkIndex++;
|
|
1056
|
+
}
|
|
1057
|
+
var endChunkIndex = startChunkIndex;
|
|
1058
|
+
var endOffsetInChunk = startOffsetInChunk + length;
|
|
1059
|
+
while (endOffsetInChunk > chunks[endChunkIndex].length) {
|
|
1060
|
+
endOffsetInChunk -= chunks[endChunkIndex].length;
|
|
1061
|
+
endChunkIndex++;
|
|
1062
|
+
}
|
|
1063
|
+
return {
|
|
1064
|
+
start: {
|
|
1065
|
+
chunkIndex: startChunkIndex,
|
|
1066
|
+
offset: startOffsetInChunk
|
|
1067
|
+
},
|
|
1068
|
+
end: {
|
|
1069
|
+
chunkIndex: endChunkIndex,
|
|
1070
|
+
offset: endOffsetInChunk
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function calculateCentroid(imageIdIndexBufferIndex, multiframe, metadataProvider, imageIds) {
|
|
1075
|
+
var xAcc = 0;
|
|
1076
|
+
var yAcc = 0;
|
|
1077
|
+
var zAcc = 0;
|
|
1078
|
+
var worldXAcc = 0;
|
|
1079
|
+
var worldYAcc = 0;
|
|
1080
|
+
var worldZAcc = 0;
|
|
1081
|
+
var count = 0;
|
|
1082
|
+
for (var _i3 = 0, _Object$entries = Object.entries(imageIdIndexBufferIndex); _i3 < _Object$entries.length; _i3++) {
|
|
1083
|
+
var _Object$entries$_i = _slicedToArray(_Object$entries[_i3], 2),
|
|
1084
|
+
imageIdIndex = _Object$entries$_i[0],
|
|
1085
|
+
bufferIndices = _Object$entries$_i[1];
|
|
1086
|
+
var z = Number(imageIdIndex);
|
|
1087
|
+
if (!bufferIndices || bufferIndices.length === 0) {
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// Get metadata for this slice
|
|
1092
|
+
var imageId = imageIds[z];
|
|
1093
|
+
var imagePlaneModule = metadataProvider.get("imagePlaneModule", imageId);
|
|
1094
|
+
if (!imagePlaneModule) {
|
|
1095
|
+
console.debug("Missing imagePlaneModule metadata for centroid calculation");
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
var imagePositionPatient = imagePlaneModule.imagePositionPatient,
|
|
1099
|
+
rowCosines = imagePlaneModule.rowCosines,
|
|
1100
|
+
columnCosines = imagePlaneModule.columnCosines,
|
|
1101
|
+
rowPixelSpacing = imagePlaneModule.rowPixelSpacing,
|
|
1102
|
+
columnPixelSpacing = imagePlaneModule.columnPixelSpacing;
|
|
1103
|
+
var _iterator2 = _createForOfIteratorHelper(bufferIndices),
|
|
1104
|
+
_step2;
|
|
1105
|
+
try {
|
|
1106
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
1107
|
+
var bufferIndex = _step2.value;
|
|
1108
|
+
var y = Math.floor(bufferIndex / multiframe.Rows);
|
|
1109
|
+
var x = bufferIndex % multiframe.Rows;
|
|
1110
|
+
|
|
1111
|
+
// Image coordinates
|
|
1112
|
+
xAcc += x;
|
|
1113
|
+
yAcc += y;
|
|
1114
|
+
zAcc += z;
|
|
1115
|
+
|
|
1116
|
+
// Calculate world coordinates
|
|
1117
|
+
// P(world) = P(image) * IOP * spacing + IPP
|
|
1118
|
+
var worldX = imagePositionPatient[0] + x * rowCosines[0] * columnPixelSpacing + y * columnCosines[0] * rowPixelSpacing;
|
|
1119
|
+
var worldY = imagePositionPatient[1] + x * rowCosines[1] * columnPixelSpacing + y * columnCosines[1] * rowPixelSpacing;
|
|
1120
|
+
var worldZ = imagePositionPatient[2] + x * rowCosines[2] * columnPixelSpacing + y * columnCosines[2] * rowPixelSpacing;
|
|
1121
|
+
worldXAcc += worldX;
|
|
1122
|
+
worldYAcc += worldY;
|
|
1123
|
+
worldZAcc += worldZ;
|
|
1124
|
+
count++;
|
|
1125
|
+
}
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
_iterator2.e(err);
|
|
1128
|
+
} finally {
|
|
1129
|
+
_iterator2.f();
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
return {
|
|
1133
|
+
image: {
|
|
1134
|
+
x: Math.floor(xAcc / count),
|
|
1135
|
+
y: Math.floor(yAcc / count),
|
|
1136
|
+
z: Math.floor(zAcc / count)
|
|
1137
|
+
},
|
|
1138
|
+
world: {
|
|
1139
|
+
x: worldXAcc / count,
|
|
1140
|
+
y: worldYAcc / count,
|
|
1141
|
+
z: worldZAcc / count
|
|
1142
|
+
},
|
|
1143
|
+
count: count
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
var Segmentation = {
|
|
1147
|
+
generateSegmentation: generateSegmentation,
|
|
1148
|
+
generateToolState: generateToolState,
|
|
1149
|
+
fillSegmentation: fillSegmentation
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
export { Segmentation as default, fillSegmentation, generateSegmentation, generateToolState };
|