larvitar 0.20.0 → 1.2.2
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/.github/workflows/build-docs.yml +1 -1
- package/.github/workflows/deploy.yml +2 -11
- package/MIGRATION.md +25 -0
- package/README.md +28 -27
- package/imaging/dataDictionary.json +21865 -21865
- package/imaging/{image_anonymization.js → imageAnonymization.js} +1 -1
- package/imaging/{image_colormaps.js → imageColormaps.js} +2 -2
- package/imaging/{image_contours.js → imageContours.js} +1 -2
- package/imaging/{image_io.js → imageIo.js} +18 -15
- package/imaging/{image_layers.js → imageLayers.js} +2 -2
- package/imaging/{image_loading.js → imageLoading.js} +9 -6
- package/imaging/imageParsing.js +301 -0
- package/imaging/{image_presets.js → imagePresets.js} +2 -2
- package/imaging/{image_rendering.js → imageRendering.js} +36 -32
- package/imaging/imageReslice.js +78 -0
- package/imaging/{image_store.js → imageStore.js} +24 -7
- package/imaging/{image_tools.js → imageTools.js} +15 -23
- package/imaging/{image_utils.js → imageUtils.js} +1 -1
- package/imaging/loaders/commonLoader.js +1 -1
- package/imaging/loaders/dicomLoader.js +1 -1
- package/imaging/loaders/fileLoader.js +2 -2
- package/imaging/loaders/multiframeLoader.js +6 -2
- package/imaging/loaders/nrrdLoader.js +11 -7
- package/imaging/tools/{contourTool.js → custom/contourTool.js} +25 -20
- package/imaging/tools/{diameterTool.js → custom/diameterTool.js} +9 -3
- package/imaging/tools/{editMaskTool.js → custom/editMaskTool.js} +7 -1
- package/imaging/tools/{polylineScissorsTool.js → custom/polylineScissorsTool.js} +12 -5
- package/imaging/tools/{seedTool.js → custom/seedTool.js} +3 -3
- package/imaging/tools/{thresholdsBrushTool.js → custom/thresholdsBrushTool.js} +7 -1
- package/imaging/tools/{tools.default.js → default.js} +9 -2
- package/imaging/tools/{tools.interaction.js → interaction.js} +13 -6
- package/imaging/tools/{tools.io.js → io.js} +15 -6
- package/imaging/tools/{tools.main.js → main.js} +14 -13
- package/imaging/tools/polygonSegmentationMixin.js +8 -4
- package/imaging/tools/{tools.segmentation.js → segmentation.js} +171 -58
- package/imaging/tools/segmentations.md +38 -0
- package/imaging/tools/setLabelMap3D.js +248 -0
- package/imaging/tools/{tools.state.js → state.js} +7 -1
- package/imaging/tools/strategies/eraseFreehand.js +8 -9
- package/imaging/tools/strategies/fillFreehand.js +8 -9
- package/index.js +41 -39
- package/modules/vuex/larvitar.js +2 -1
- package/package.json +11 -8
- package/imaging/image_parsing.js +0 -307
- package/imaging/image_reslice.js +0 -80
|
@@ -1,33 +1,23 @@
|
|
|
1
|
+
/** @module imaging/tools/segmentation
|
|
2
|
+
* @desc This file provides functionalities
|
|
3
|
+
* for handling masks and luts
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
// external libraries
|
|
2
7
|
import cornerstone from "cornerstone-core";
|
|
3
8
|
import cornerstoneTools from "cornerstone-tools/dist/cornerstoneTools.js";
|
|
4
|
-
|
|
5
|
-
import { setToolActive, setToolDisabled } from "./tools.main";
|
|
6
|
-
// utils
|
|
7
|
-
import { cloneDeep, extend, values } from "lodash";
|
|
8
|
-
|
|
9
|
+
import { cloneDeep, extend, values, sum } from "lodash";
|
|
9
10
|
const segModule = cornerstoneTools.getModule("segmentation");
|
|
10
|
-
const setters = segModule
|
|
11
|
-
const getters = segModule.getters;
|
|
11
|
+
const { getters, setters } = segModule;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// internal libraries
|
|
14
|
+
import { setToolActive, setToolDisabled } from "./main";
|
|
15
|
+
import { isElement } from "../imageUtils";
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* NOTES ON CS TOOLS SEGMENTATION MODULE
|
|
24
|
-
* The value in the mask (binary) define which color will be used from the LUT map
|
|
25
|
-
* The different masks are 'labelmap', while different values in the same mask are 'segments'
|
|
26
|
-
* Segments get the color from the lutmap (up to 2^16 segments) and can be shown/hidden one by one
|
|
27
|
-
* > setters.toggleSegmentVisibility(htmlelement,segmentvalue,labelmapid)
|
|
28
|
-
* > setters.colorForSegmentIndexOfColorLUT(colorLutIndex, segmentValue, colorRGBAarray)
|
|
29
|
-
* Labelmaps are linked to a colormap and can be active / inactive
|
|
30
|
-
* */
|
|
17
|
+
// custom code
|
|
18
|
+
import { setLabelmap3DForElement } from "./setLabelMap3D";
|
|
19
|
+
// override function
|
|
20
|
+
setters.labelmap3DForElement = setLabelmap3DForElement;
|
|
31
21
|
|
|
32
22
|
// General segmentation cs tools module configuration
|
|
33
23
|
const config = {
|
|
@@ -145,53 +135,153 @@ function generateUniformLUT(hex_color, opacity) {
|
|
|
145
135
|
return lut;
|
|
146
136
|
}
|
|
147
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Set color for label
|
|
140
|
+
* @param {Number} labelId
|
|
141
|
+
* @param {String} color in hex format
|
|
142
|
+
*/
|
|
143
|
+
export function setLabelColor(labelId, color) {
|
|
144
|
+
let volumeId = 0; // TODO MULTIVOLUME
|
|
145
|
+
let rgb = hexToRgb(color);
|
|
146
|
+
let rgba = [...rgb, 128];
|
|
147
|
+
setters.colorForSegmentIndexOfColorLUT(volumeId, labelId, rgba);
|
|
148
|
+
// force render ? depends on image visualization (render all ?)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get color from label
|
|
153
|
+
* @param {Number} labelId
|
|
154
|
+
* @returns {String} Color in hex format
|
|
155
|
+
*/
|
|
156
|
+
export function getLabelColor(labelId) {
|
|
157
|
+
let volumeId = 0; // TODO MULTIVOLUME
|
|
158
|
+
let rgba = getters.colorForSegmentIndexColorLUT(volumeId, labelId);
|
|
159
|
+
return rgbToHex(rgba);
|
|
160
|
+
}
|
|
161
|
+
|
|
148
162
|
/**
|
|
149
163
|
* A function to group all settings to load before masks
|
|
150
|
-
*
|
|
164
|
+
* @param {Object} customConfig - Object containing override values for segmentation module config
|
|
151
165
|
*/
|
|
152
|
-
export function initSegmentationModule() {
|
|
166
|
+
export function initSegmentationModule(customConfig) {
|
|
153
167
|
// set configuration
|
|
154
168
|
segModule.configuration = cloneDeep(config);
|
|
169
|
+
extend(segModule.configuration, customConfig);
|
|
155
170
|
}
|
|
156
171
|
|
|
157
172
|
/**
|
|
158
173
|
* Add segmentation mask to segmentation module
|
|
159
|
-
* @param {
|
|
174
|
+
* @param {Object} maskProps - The mask properties (labelId, color and opacity)
|
|
160
175
|
* @param {TypedArray} - The mask data array
|
|
176
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
177
|
+
* @returns {Promise} - Return a promise which will resolve when segmentation mask is added
|
|
161
178
|
*/
|
|
162
|
-
export function addSegmentationMask(props, data, elementId
|
|
163
|
-
let
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
export function addSegmentationMask(props, data, elementId) {
|
|
180
|
+
let promise = new Promise(async resolve => {
|
|
181
|
+
let element = isElement(elementId)
|
|
182
|
+
? elementId
|
|
183
|
+
: document.getElementById(elementId);
|
|
184
|
+
if (!element) {
|
|
185
|
+
console.error("invalid html element: " + elementId);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const res = await setters.labelmap3DForElement(
|
|
190
|
+
element,
|
|
191
|
+
data.buffer,
|
|
192
|
+
props.labelId
|
|
193
|
+
);
|
|
194
|
+
// if user set a color property, use that color for all segments on the labelmap
|
|
195
|
+
let lut = props.color
|
|
196
|
+
? generateUniformLUT(props.color, props.opacity)
|
|
197
|
+
: generateLUT(props.opacity);
|
|
198
|
+
setters.colorLUT(props.labelId, lut);
|
|
199
|
+
// bind labelmap to colorLUT
|
|
200
|
+
let labelmap3d = getters.labelmap3D(element, props.labelId);
|
|
201
|
+
setters.colorLUTIndexForLabelmap3D(labelmap3d, props.labelId);
|
|
202
|
+
resolve();
|
|
203
|
+
});
|
|
204
|
+
return promise;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Set a new mask slice into the labelmap buffer
|
|
209
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
210
|
+
* @param {Number} sliceIndex - the index of the new mask slice
|
|
211
|
+
* @param {ArrayBuffer} pixelData - the pixelData array
|
|
212
|
+
*/
|
|
213
|
+
export function loadMaskSlice(elementId, sliceIndex, pixelData) {
|
|
214
|
+
// optimization: if pixelData contains no labels, return
|
|
215
|
+
if (sum(pixelData) === 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let element = isElement(elementId)
|
|
220
|
+
? elementId
|
|
221
|
+
: document.getElementById(elementId);
|
|
222
|
+
if (!element) {
|
|
223
|
+
console.error("invalid html element: " + elementId);
|
|
224
|
+
return;
|
|
175
225
|
}
|
|
226
|
+
let volumeId = 0; // TODO for multivolume
|
|
227
|
+
// get labelmap buffer
|
|
228
|
+
let labelmaps2D = getters.labelmap3D(element, volumeId).labelmaps2D;
|
|
229
|
+
|
|
230
|
+
// add if not alresdy present
|
|
231
|
+
if (!labelmaps2D[sliceIndex]) {
|
|
232
|
+
labelmaps2D[sliceIndex] = {};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
labelmaps2D[sliceIndex].pixelData = pixelData;
|
|
236
|
+
setters.updateSegmentsOnLabelmap2D(labelmaps2D[sliceIndex]);
|
|
237
|
+
cornerstone.updateImage(element);
|
|
176
238
|
}
|
|
177
239
|
|
|
178
240
|
/**
|
|
179
241
|
* Activate a specific labelmap through its labelId
|
|
180
242
|
* @param {Number} labelId - The labelmap id to activate
|
|
181
|
-
* @param {String} elementId - The target html element
|
|
243
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
182
244
|
*/
|
|
183
245
|
export function setActiveLabelmap(labelId, elementId) {
|
|
184
|
-
let element =
|
|
246
|
+
let element = isElement(elementId)
|
|
247
|
+
? elementId
|
|
248
|
+
: document.getElementById(elementId);
|
|
249
|
+
if (!element) {
|
|
250
|
+
console.error("invalid html element: " + elementId);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
185
253
|
setters.activeLabelmapIndex(element, labelId);
|
|
186
254
|
}
|
|
187
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Get active labelmap for target element
|
|
258
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
259
|
+
* @returns {Object} The active labelmap object that contains the buffer
|
|
260
|
+
*/
|
|
261
|
+
export function getActiveLabelmapBuffer(elementId) {
|
|
262
|
+
let element = isElement(elementId)
|
|
263
|
+
? elementId
|
|
264
|
+
: document.getElementById(elementId);
|
|
265
|
+
if (!element) {
|
|
266
|
+
console.error("invalid html element: " + elementId);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
return getters.activeLabelmapBuffer(element);
|
|
270
|
+
}
|
|
271
|
+
|
|
188
272
|
/**
|
|
189
273
|
* Activate a specific segment through its index
|
|
190
274
|
* @param {Number} segmentIndex - The segment index to activate
|
|
191
|
-
* @param {String} elementId - The target html element
|
|
275
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
192
276
|
*/
|
|
193
277
|
export function setActiveSegment(segmentIndex, elementId) {
|
|
194
|
-
let element =
|
|
278
|
+
let element = isElement(elementId)
|
|
279
|
+
? elementId
|
|
280
|
+
: document.getElementById(elementId);
|
|
281
|
+
if (!element) {
|
|
282
|
+
console.error("invalid html element: " + elementId);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
195
285
|
setters.activeSegmentIndex(element, segmentIndex);
|
|
196
286
|
}
|
|
197
287
|
|
|
@@ -213,6 +303,24 @@ export function setInactiveLabelOpacity(opacity) {
|
|
|
213
303
|
forceRender();
|
|
214
304
|
}
|
|
215
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Toggle mask visibility
|
|
308
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
309
|
+
* @param {Number} labelId - The id of the mask label
|
|
310
|
+
*/
|
|
311
|
+
export function toggleVisibility(elementId, labelId) {
|
|
312
|
+
let element = isElement(elementId)
|
|
313
|
+
? elementId
|
|
314
|
+
: document.getElementById(elementId);
|
|
315
|
+
if (!element) {
|
|
316
|
+
console.error("invalid html element: " + elementId);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
let volumeId = 0; // TODO MULTIVOLUME
|
|
320
|
+
setters.toggleSegmentVisibility(element, labelId, volumeId);
|
|
321
|
+
cornerstone.updateImage(element);
|
|
322
|
+
}
|
|
323
|
+
|
|
216
324
|
/**
|
|
217
325
|
* Toggle between 'contours mode' and 'filled mode'
|
|
218
326
|
* @param {Bool} toggle - Contour mode enabled if true
|
|
@@ -236,9 +344,7 @@ export function toggleContourMode(toggle) {
|
|
|
236
344
|
|
|
237
345
|
/**
|
|
238
346
|
* Set mask appearance props
|
|
239
|
-
* @param {
|
|
240
|
-
* @param {Integer} mode - [0=filled, 1=contour, 2=hidden]
|
|
241
|
-
* @param {Float} alpha - Opacity value (if mode=0), between 0 and 1
|
|
347
|
+
* @param {Object} maskProps - The mask appearance props (labelId, visualization [0=filled, 1=contour, 2=hidden], opacity (if mode=0), between 0 and 1)
|
|
242
348
|
*/
|
|
243
349
|
export function setMaskProps(props) {
|
|
244
350
|
// Lut index and segment values are hardcoded because they will depend on design choices:
|
|
@@ -299,6 +405,7 @@ export function clearSegmentationState() {
|
|
|
299
405
|
/**
|
|
300
406
|
* Enable brushing
|
|
301
407
|
* @param {Number} dimension - The initial brush radius
|
|
408
|
+
* @param {Array} thresholds - The threshold values (min and max)
|
|
302
409
|
*/
|
|
303
410
|
export function enableBrushTool(dimension, thresholds) {
|
|
304
411
|
segModule.configuration.radius = dimension;
|
|
@@ -324,35 +431,41 @@ export function setBrushProps(props) {
|
|
|
324
431
|
forceRender();
|
|
325
432
|
}
|
|
326
433
|
|
|
327
|
-
/**
|
|
328
|
-
* Retrieve the buffer that represents the current active mask
|
|
329
|
-
*/
|
|
330
|
-
export function getActiveLabelmapBuffer() {
|
|
331
|
-
let element = document.getElementById("axial");
|
|
332
|
-
let object = segModule.getters.activeLabelmapBuffer(element);
|
|
333
|
-
return object.buffer;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
434
|
/**
|
|
337
435
|
* Undo last brush operation (stroke)
|
|
436
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
338
437
|
*/
|
|
339
438
|
export function undoLastStroke(elementId) {
|
|
340
|
-
let element =
|
|
439
|
+
let element = isElement(elementId)
|
|
440
|
+
? elementId
|
|
441
|
+
: document.getElementById(elementId);
|
|
442
|
+
if (!element) {
|
|
443
|
+
console.error("invalid html element: " + elementId);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
341
446
|
let activeLabelMapIndex = segModule.getters.activeLabelmapIndex(element);
|
|
342
447
|
setters.undo(element, activeLabelMapIndex);
|
|
343
448
|
}
|
|
344
449
|
|
|
345
450
|
/**
|
|
346
451
|
* Redo last brush operation (stroke)
|
|
452
|
+
* @param {String} elementId - The target html element Id or its DOM HTMLElement
|
|
347
453
|
*/
|
|
348
454
|
export function redoLastStroke(elementId) {
|
|
349
|
-
let element =
|
|
455
|
+
let element = isElement(elementId)
|
|
456
|
+
? elementId
|
|
457
|
+
: document.getElementById(elementId);
|
|
458
|
+
if (!element) {
|
|
459
|
+
console.error("invalid html element: " + elementId);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
350
462
|
let activeLabelMapIndex = segModule.getters.activeLabelmapIndex(element);
|
|
351
463
|
setters.redo(element, activeLabelMapIndex);
|
|
352
464
|
}
|
|
353
465
|
|
|
354
466
|
/**
|
|
355
467
|
* Delete mask from state
|
|
468
|
+
* @param {Number} labelId - The labelmap id to activate
|
|
356
469
|
*/
|
|
357
470
|
export function deleteMask(labelId) {
|
|
358
471
|
let masks = values(segModule.state.series)[0].labelmaps3D;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Introduction
|
|
2
|
+
|
|
3
|
+
This guide explains the key concepts of Larvitar **segmentation masks management**, based on cornerstone tools's **segmentation module**.
|
|
4
|
+
|
|
5
|
+
# Definitions
|
|
6
|
+
|
|
7
|
+
- _segmentation mask_ or just _mask_: the set of pixels that belongs to a specific structure
|
|
8
|
+
- _volume_: a buffer containing one or more segmentations
|
|
9
|
+
- _label_: the value inside a volume that identifies a specific segmentation
|
|
10
|
+
|
|
11
|
+
# CS tools
|
|
12
|
+
|
|
13
|
+
## Segmentation module structure
|
|
14
|
+
|
|
15
|
+
In cs tools world, the different volumes are defined _labelmaps_, while the different labels in a volume are _segments_. Labelmaps can support up to 2^16 segments.
|
|
16
|
+
The values in the volume (ie, labels) define which color will be used from the LUT map: in fact, segments get the color from the lutmap and can be shown/hidden one by one.
|
|
17
|
+
Each labelmaps can be linked to a different colormap and has a active / inactive property that affects the rendering style (see configuration).
|
|
18
|
+
|
|
19
|
+
> setters.toggleSegmentVisibility(htmlelement,segmentvalue,labelmapid)
|
|
20
|
+
> setters.colorForSegmentIndexOfColorLUT(colorLutIndex, segmentValue, colorRGBAarray)
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
TODO
|
|
25
|
+
|
|
26
|
+
# Larvitar segmentation management
|
|
27
|
+
|
|
28
|
+
TODO
|
|
29
|
+
|
|
30
|
+
# Larvitar segmentation API
|
|
31
|
+
|
|
32
|
+
TODO
|
|
33
|
+
|
|
34
|
+
# Customization
|
|
35
|
+
|
|
36
|
+
Some function in larvitar overrides the default behaviour of cornerstone tools, here is a list of them:
|
|
37
|
+
|
|
38
|
+
- `setters.labelmap3DForElement` is overridden to have a non-blocking behaviour, the custom code is in ./setLabelMap3D.js, same as original code in cs tools repo.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a custom version of setLabelMap3D from cs tools source code
|
|
3
|
+
* This let us implement a non-blocking version of the for loop that loads 3d labelmaps on a volume
|
|
4
|
+
* @ronzim
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// import getElement from "./getElement";
|
|
8
|
+
// import { getToolState } from "../../../stateManagement/toolState.js";
|
|
9
|
+
// import state from "./state";
|
|
10
|
+
// import getSegmentsOnPixelData from "./getSegmentsOnPixeldata";
|
|
11
|
+
// import { triggerLabelmapModifiedEvent } from "../../../util/segmentation";
|
|
12
|
+
// import ARRAY_TYPES from "./arrayTypes";
|
|
13
|
+
// import { getModule } from "../../index.js";
|
|
14
|
+
|
|
15
|
+
const ARRAY_TYPES = {
|
|
16
|
+
UINT_16_ARRAY: 0,
|
|
17
|
+
FLOAT_32_ARRAY: 1
|
|
18
|
+
};
|
|
19
|
+
const { UINT_16_ARRAY, FLOAT_32_ARRAY } = ARRAY_TYPES;
|
|
20
|
+
|
|
21
|
+
import cornerstoneTools from "cornerstone-tools/dist/cornerstoneTools.js";
|
|
22
|
+
const { triggerLabelmapModifiedEvent } = cornerstoneTools.importInternal(
|
|
23
|
+
"util/segmentationUtils"
|
|
24
|
+
);
|
|
25
|
+
const getModule = cornerstoneTools.getModule;
|
|
26
|
+
const getToolState = cornerstoneTools.getToolState;
|
|
27
|
+
const storeGetters = cornerstoneTools.store.getters;
|
|
28
|
+
const state = getModule("segmentation").state;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* These function are reported since they are not exposed by cs tools
|
|
32
|
+
*
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// from getSegmentsOnPixelData.js
|
|
36
|
+
function getSegmentsOnPixelData(pixelData) {
|
|
37
|
+
return [...new Set(pixelData)];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// from getElement.js
|
|
41
|
+
function getElement(elementOrEnabledElementUID) {
|
|
42
|
+
if (elementOrEnabledElementUID instanceof HTMLElement) {
|
|
43
|
+
return elementOrEnabledElementUID;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return storeGetters.enabledElementByUID(elementOrEnabledElementUID);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Takes a 16-bit encoded `ArrayBuffer` and stores it as a `Labelmap3D` for the
|
|
51
|
+
* `BrushStackState` associated with the element.
|
|
52
|
+
*
|
|
53
|
+
* @param {HTMLElement|string} elementOrEnabledElementUID The cornerstone
|
|
54
|
+
* enabled element or its UUID.
|
|
55
|
+
* @param {ArrayBuffer} buffer
|
|
56
|
+
* @param {number} labelmapIndex The index to store the labelmap under.
|
|
57
|
+
* @param {Object[]} metadata = [] Any metadata about the segments.
|
|
58
|
+
* @param {number[][]} [segmentsOnLabelmapArray] An array of array of segments on each imageIdIndex.
|
|
59
|
+
* If not present, is calculated.
|
|
60
|
+
* @param {colorLUTIndex} [colorLUTIndex = 0] The index of the colorLUT to use to render the segmentation.
|
|
61
|
+
* @returns {null}
|
|
62
|
+
*/
|
|
63
|
+
async function setLabelmap3DForElement(
|
|
64
|
+
elementOrEnabledElementUID,
|
|
65
|
+
buffer,
|
|
66
|
+
labelmapIndex,
|
|
67
|
+
metadata = [],
|
|
68
|
+
segmentsOnLabelmapArray,
|
|
69
|
+
colorLUTIndex = 0
|
|
70
|
+
) {
|
|
71
|
+
const element = getElement(elementOrEnabledElementUID);
|
|
72
|
+
|
|
73
|
+
if (!element) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stackState = getToolState(element, "stack");
|
|
78
|
+
const numberOfFrames = stackState.data[0].imageIds.length;
|
|
79
|
+
const firstImageId = stackState.data[0].imageIds[0];
|
|
80
|
+
|
|
81
|
+
const res = await setLabelmap3DByFirstImageId(
|
|
82
|
+
firstImageId,
|
|
83
|
+
buffer,
|
|
84
|
+
labelmapIndex,
|
|
85
|
+
metadata,
|
|
86
|
+
numberOfFrames,
|
|
87
|
+
segmentsOnLabelmapArray,
|
|
88
|
+
colorLUTIndex
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
triggerLabelmapModifiedEvent(element, labelmapIndex);
|
|
92
|
+
|
|
93
|
+
return res;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Takes an 16-bit encoded `ArrayBuffer` and stores it as a `Labelmap3D` for
|
|
98
|
+
* the `BrushStackState` associated with the firstImageId.
|
|
99
|
+
*
|
|
100
|
+
* @param {HTMLElement|string} firstImageId The firstImageId of the series to
|
|
101
|
+
* store the segmentation on.
|
|
102
|
+
* @param {ArrayBuffer} buffer
|
|
103
|
+
* @param {number} labelmapIndex The index to store the labelmap under.
|
|
104
|
+
* @param {Object[]} metadata = [] Any metadata about the segments.
|
|
105
|
+
* @param {number} numberOfFrames The number of frames, required to set up the
|
|
106
|
+
* relevant labelmap2D views.
|
|
107
|
+
* @param {number[][]} [segmentsOnLabelmapArray] An array of array of segments on each imageIdIndex.
|
|
108
|
+
* If not present, is calculated.
|
|
109
|
+
* @param {colorLUTIndex} [colorLUTIndex = 0] The index of the colorLUT to use to render the segmentation.
|
|
110
|
+
* @returns {null}
|
|
111
|
+
*/
|
|
112
|
+
function setLabelmap3DByFirstImageId(
|
|
113
|
+
firstImageId,
|
|
114
|
+
buffer,
|
|
115
|
+
labelmapIndex,
|
|
116
|
+
metadata = [],
|
|
117
|
+
numberOfFrames,
|
|
118
|
+
segmentsOnLabelmapArray,
|
|
119
|
+
colorLUTIndex = 0
|
|
120
|
+
) {
|
|
121
|
+
const { configuration } = getModule("segmentation");
|
|
122
|
+
|
|
123
|
+
let brushStackState = state.series[firstImageId];
|
|
124
|
+
|
|
125
|
+
if (!brushStackState) {
|
|
126
|
+
state.series[firstImageId] = {
|
|
127
|
+
activeLabelmapIndex: labelmapIndex,
|
|
128
|
+
labelmaps3D: []
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
brushStackState = state.series[firstImageId];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
brushStackState.labelmaps3D[labelmapIndex] = {
|
|
135
|
+
buffer,
|
|
136
|
+
labelmaps2D: [],
|
|
137
|
+
metadata,
|
|
138
|
+
activeSegmentIndex: 1,
|
|
139
|
+
colorLUTIndex,
|
|
140
|
+
segmentsHidden: [],
|
|
141
|
+
undo: [],
|
|
142
|
+
redo: []
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const labelmaps2D = brushStackState.labelmaps3D[labelmapIndex].labelmaps2D;
|
|
146
|
+
const slicelengthInBytes = buffer.byteLength / numberOfFrames;
|
|
147
|
+
|
|
148
|
+
/* non-blocking implementation by @ronzim */
|
|
149
|
+
|
|
150
|
+
return new Promise(resolve => {
|
|
151
|
+
function setSingleSlice(i, numberOfFrames) {
|
|
152
|
+
var pixelData = void 0;
|
|
153
|
+
|
|
154
|
+
switch (configuration.arrayType) {
|
|
155
|
+
case UINT_16_ARRAY:
|
|
156
|
+
pixelData = new Uint16Array(
|
|
157
|
+
buffer,
|
|
158
|
+
slicelengthInBytes * i, // 2 bytes/voxel
|
|
159
|
+
slicelengthInBytes / 2
|
|
160
|
+
);
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case FLOAT_32_ARRAY:
|
|
164
|
+
pixelData = new Float32Array(
|
|
165
|
+
buffer,
|
|
166
|
+
slicelengthInBytes * i,
|
|
167
|
+
slicelengthInBytes / 4
|
|
168
|
+
);
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
default:
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Unsupported Array Type ".concat(configuration.arrayType)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
var segmentsOnLabelmap = segmentsOnLabelmapArray
|
|
178
|
+
? segmentsOnLabelmapArray[i]
|
|
179
|
+
: getSegmentsOnPixelData(pixelData);
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
segmentsOnLabelmap &&
|
|
183
|
+
segmentsOnLabelmap.some(function (segment) {
|
|
184
|
+
return segment;
|
|
185
|
+
})
|
|
186
|
+
) {
|
|
187
|
+
labelmaps2D[i] = {
|
|
188
|
+
pixelData: pixelData,
|
|
189
|
+
segmentsOnLabelmap: segmentsOnLabelmap
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
++i;
|
|
194
|
+
|
|
195
|
+
if (i < numberOfFrames) {
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
setSingleSlice(i, numberOfFrames);
|
|
198
|
+
}, 0);
|
|
199
|
+
} else {
|
|
200
|
+
resolve("OK");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
setSingleSlice(0, numberOfFrames);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
/* original implementation
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < numberOfFrames; i++) {
|
|
210
|
+
let pixelData;
|
|
211
|
+
|
|
212
|
+
switch (configuration.arrayType) {
|
|
213
|
+
case UINT_16_ARRAY:
|
|
214
|
+
pixelData = new Uint16Array(
|
|
215
|
+
buffer,
|
|
216
|
+
slicelengthInBytes * i, // 2 bytes/voxel
|
|
217
|
+
slicelengthInBytes / 2
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case FLOAT_32_ARRAY:
|
|
223
|
+
pixelData = new Float32Array(
|
|
224
|
+
buffer,
|
|
225
|
+
slicelengthInBytes * i,
|
|
226
|
+
slicelengthInBytes / 4
|
|
227
|
+
);
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
default:
|
|
231
|
+
throw new Error(`Unsupported Array Type ${configuration.arrayType}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const segmentsOnLabelmap = segmentsOnLabelmapArray
|
|
235
|
+
? segmentsOnLabelmapArray[i]
|
|
236
|
+
: getSegmentsOnPixelData(pixelData);
|
|
237
|
+
|
|
238
|
+
if (segmentsOnLabelmap && segmentsOnLabelmap.some(segment => segment)) {
|
|
239
|
+
labelmaps2D[i] = {
|
|
240
|
+
pixelData,
|
|
241
|
+
segmentsOnLabelmap
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
*/
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { setLabelmap3DByFirstImageId, setLabelmap3DForElement };
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
/** @module imaging/tools/state
|
|
2
|
+
* @desc This file provides functionalities
|
|
3
|
+
* for handling tools' state
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
// external libraries
|
|
2
7
|
import cornerstone from "cornerstone-core";
|
|
3
8
|
import cornerstoneTools from "cornerstone-tools";
|
|
4
|
-
|
|
5
9
|
import { each } from "lodash";
|
|
10
|
+
|
|
11
|
+
// internal libraries
|
|
6
12
|
import { state_example } from "./cstools_state_example.js";
|
|
7
13
|
|
|
8
14
|
/**
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
eraseInsideShape,
|
|
6
|
-
eraseOutsideShape
|
|
7
|
-
} = cornerstoneTools.importInternal("util/segmentationUtils");
|
|
1
|
+
/** @module imaging/strategies/eraseFreehand
|
|
2
|
+
* @desc This file provides functionalities for
|
|
3
|
+
* erasing pixels
|
|
4
|
+
*/
|
|
8
5
|
|
|
6
|
+
// external libraries
|
|
7
|
+
import cornerstoneTools from "cornerstone-tools";
|
|
8
|
+
const { getBoundingBoxAroundPolygon, eraseInsideShape, eraseOutsideShape } =
|
|
9
|
+
cornerstoneTools.importInternal("util/segmentationUtils");
|
|
9
10
|
const isPointInPolygon = cornerstoneTools.importInternal(
|
|
10
11
|
"util/isPointInPolygon"
|
|
11
12
|
);
|
|
12
|
-
|
|
13
13
|
const getLogger = cornerstoneTools.importInternal("util/getLogger");
|
|
14
|
-
|
|
15
14
|
const logger = getLogger("util:segmentation:operations:eraseInsideFreehand");
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
fillInsideShape,
|
|
6
|
-
fillOutsideShape
|
|
7
|
-
} = cornerstoneTools.importInternal("util/segmentationUtils");
|
|
1
|
+
/** @module imaging/strategies/fillFreehand
|
|
2
|
+
* @desc This file provides functionalities for
|
|
3
|
+
* filling pixels
|
|
4
|
+
*/
|
|
8
5
|
|
|
6
|
+
// external libraries
|
|
7
|
+
import cornerstoneTools from "cornerstone-tools";
|
|
8
|
+
const { getBoundingBoxAroundPolygon, fillInsideShape, fillOutsideShape } =
|
|
9
|
+
cornerstoneTools.importInternal("util/segmentationUtils");
|
|
9
10
|
const isPointInPolygon = cornerstoneTools.importInternal(
|
|
10
11
|
"util/isPointInPolygon"
|
|
11
12
|
);
|
|
12
|
-
|
|
13
13
|
const getLogger = cornerstoneTools.importInternal("util/getLogger");
|
|
14
|
-
|
|
15
14
|
const logger = getLogger("util:segmentation:operations:fillInsideFreehand");
|
|
16
15
|
|
|
17
16
|
/**
|