larvitar 1.5.13 → 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/.vscode/settings.json +4 -0
- package/README.md +78 -48
- package/bundler/webpack.common.js +27 -0
- package/bundler/webpack.dev.js +23 -0
- package/bundler/webpack.prod.js +19 -0
- package/decs.d.ts +12 -0
- package/dist/imaging/MetaDataReadable.d.ts +39 -0
- package/dist/imaging/MetaDataTypes.d.ts +3488 -0
- package/dist/imaging/imageAnonymization.d.ts +12 -0
- package/dist/imaging/imageColormaps.d.ts +47 -0
- package/dist/imaging/imageContours.d.ts +18 -0
- package/dist/imaging/imageIo.d.ts +42 -0
- package/dist/imaging/imageLayers.d.ts +56 -0
- package/dist/imaging/imageLoading.d.ts +65 -0
- package/dist/imaging/imageParsing.d.ts +46 -0
- package/dist/imaging/imagePresets.d.ts +43 -0
- package/dist/imaging/imageRendering.d.ts +238 -0
- package/dist/imaging/imageReslice.d.ts +14 -0
- package/dist/imaging/imageStore.d.ts +121 -0
- package/dist/imaging/imageTags.d.ts +22 -0
- package/dist/imaging/imageTools.d.ts +20 -0
- package/dist/imaging/imageUtils.d.ts +165 -0
- package/dist/imaging/loaders/commonLoader.d.ts +103 -0
- package/dist/imaging/loaders/dicomLoader.d.ts +29 -0
- package/dist/imaging/loaders/fileLoader.d.ts +33 -0
- package/dist/imaging/loaders/multiframeLoader.d.ts +37 -0
- package/dist/imaging/loaders/nrrdLoader.d.ts +112 -0
- package/dist/imaging/loaders/resliceLoader.d.ts +15 -0
- package/dist/imaging/monitors/memory.d.ts +41 -0
- package/dist/imaging/monitors/performance.d.ts +23 -0
- package/dist/imaging/parsers/ecg.d.ts +15 -0
- package/dist/imaging/parsers/nrrd.d.ts +3 -0
- package/dist/imaging/tools/custom/4dSliceScrollTool.d.ts +12 -0
- package/dist/imaging/tools/custom/contourTool.d.ts +409 -0
- package/dist/imaging/tools/custom/diameterTool.d.ts +18 -0
- package/dist/imaging/tools/custom/editMaskTool.d.ts +22 -0
- package/dist/imaging/tools/custom/ellipticalRoiOverlayTool.d.ts +45 -0
- package/dist/imaging/tools/custom/polygonSegmentationMixin.d.ts +54 -0
- package/dist/imaging/tools/custom/polylineScissorsTool.d.ts +11 -0
- package/dist/imaging/tools/custom/rectangleRoiOverlayTool.d.ts +45 -0
- package/dist/imaging/tools/custom/seedTool.d.ts +0 -0
- package/dist/imaging/tools/custom/setLabelMap3D.d.ts +39 -0
- package/dist/imaging/tools/custom/thresholdsBrushTool.d.ts +19 -0
- package/dist/imaging/tools/default.d.ts +53 -0
- package/dist/imaging/tools/interaction.d.ts +30 -0
- package/dist/imaging/tools/io.d.ts +38 -0
- package/dist/imaging/tools/main.d.ts +81 -0
- package/dist/imaging/tools/segmentation.d.ts +125 -0
- package/dist/imaging/tools/state.d.ts +17 -0
- package/dist/imaging/tools/strategies/eraseFreehand.d.ts +16 -0
- package/dist/imaging/tools/strategies/fillFreehand.d.ts +16 -0
- package/dist/imaging/tools/strategies/index.d.ts +2 -0
- package/dist/index.d.ts +34 -0
- package/dist/larvitar.js +89801 -0
- package/dist/larvitar.js.map +1 -0
- package/imaging/MetaDataReadable.ts +40 -0
- package/imaging/MetaDataTypes.ts +3490 -0
- package/imaging/dataDictionary.json +5328 -5328
- package/imaging/{imageAnonymization.js → imageAnonymization.ts} +41 -13
- package/imaging/{imageColormaps.js → imageColormaps.ts} +48 -30
- package/imaging/{imageContours.js → imageContours.ts} +24 -22
- package/imaging/{imageIo.js → imageIo.ts} +89 -52
- package/imaging/{imageLayers.js → imageLayers.ts} +31 -14
- package/imaging/{imageLoading.js → imageLoading.ts} +108 -45
- package/imaging/{imageParsing.js → imageParsing.ts} +158 -80
- package/imaging/{imagePresets.js → imagePresets.ts} +44 -11
- package/imaging/imageRendering.ts +1091 -0
- package/imaging/{imageReslice.js → imageReslice.ts} +18 -9
- package/imaging/imageStore.ts +487 -0
- package/imaging/imageTags.ts +609 -0
- package/imaging/imageTools.js +2 -1
- package/imaging/{imageUtils.js → imageUtils.ts} +211 -701
- package/imaging/loaders/{commonLoader.js → commonLoader.ts} +73 -24
- package/imaging/loaders/{dicomLoader.js → dicomLoader.ts} +25 -5
- package/imaging/loaders/{fileLoader.js → fileLoader.ts} +5 -5
- package/imaging/loaders/{multiframeLoader.js → multiframeLoader.ts} +145 -90
- package/imaging/loaders/{nrrdLoader.js → nrrdLoader.ts} +230 -64
- package/imaging/loaders/{resliceLoader.js → resliceLoader.ts} +51 -20
- package/imaging/monitors/{memory.js → memory.ts} +54 -8
- package/imaging/monitors/performance.ts +34 -0
- package/imaging/parsers/ecg.ts +51 -0
- package/imaging/tools/README.md +27 -0
- package/imaging/tools/custom/4dSliceScrollTool.js +47 -46
- package/imaging/tools/custom/ellipticalRoiOverlayTool.js +534 -0
- package/imaging/tools/custom/polylineScissorsTool.js +1 -1
- package/imaging/tools/custom/rectangleRoiOverlayTool.js +564 -0
- package/imaging/tools/{setLabelMap3D.js → custom/setLabelMap3D.ts} +19 -25
- package/imaging/tools/{default.js → default.ts} +114 -30
- package/imaging/tools/{interaction.js → interaction.ts} +42 -23
- package/imaging/tools/{io.js → io.ts} +47 -31
- package/imaging/tools/{main.js → main.ts} +105 -40
- package/imaging/tools/{segmentation.js → segmentation.ts} +95 -68
- package/imaging/tools/{state.js → state.ts} +7 -12
- package/imaging/tools/types.d.ts +243 -0
- package/imaging/types.d.ts +197 -0
- package/{index.js → index.ts} +43 -14
- package/jsdoc.json +1 -1
- package/package.json +32 -14
- package/tsconfig.json +102 -0
- package/imaging/imageRendering.js +0 -860
- package/imaging/imageStore.js +0 -322
- package/modules/vuex/larvitar.js +0 -187
- /package/imaging/tools/{polygonSegmentationMixin.js → custom/polygonSegmentationMixin.js} +0 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import cornerstoneTools from "cornerstone-tools";
|
|
2
|
+
|
|
3
|
+
const external = cornerstoneTools.external;
|
|
4
|
+
const BaseAnnotationTool = cornerstoneTools.importInternal(
|
|
5
|
+
"base/BaseAnnotationTool"
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
// State
|
|
9
|
+
const getToolState = cornerstoneTools.getToolState;
|
|
10
|
+
const toolStyle = cornerstoneTools.toolStyle;
|
|
11
|
+
const toolColors = cornerstoneTools.toolColors;
|
|
12
|
+
|
|
13
|
+
// Drawing
|
|
14
|
+
const getNewContext = cornerstoneTools.importInternal("drawing/getNewContext");
|
|
15
|
+
const draw = cornerstoneTools.importInternal("drawing/draw");
|
|
16
|
+
const drawHandles = cornerstoneTools.importInternal("drawing/drawHandles");
|
|
17
|
+
const drawRect = cornerstoneTools.importInternal("drawing/drawRect");
|
|
18
|
+
const drawLinkedTextBox = cornerstoneTools.importInternal(
|
|
19
|
+
"drawing/drawLinkedTextBox"
|
|
20
|
+
);
|
|
21
|
+
const setShadow = cornerstoneTools.importInternal("drawing/setShadow");
|
|
22
|
+
|
|
23
|
+
// Util
|
|
24
|
+
const calculateSUV = cornerstoneTools.importInternal("util/calculateSUV");
|
|
25
|
+
const getROITextBoxCoords = cornerstoneTools.importInternal(
|
|
26
|
+
"util/getROITextBoxCoords"
|
|
27
|
+
);
|
|
28
|
+
const numbersWithCommas = cornerstoneTools.importInternal(
|
|
29
|
+
"util/numbersWithCommas"
|
|
30
|
+
);
|
|
31
|
+
const throttle = cornerstoneTools.importInternal("util/throttle");
|
|
32
|
+
const { rectangleRoiCursor } = cornerstoneTools.importInternal("tools/cursors");
|
|
33
|
+
const getLogger = cornerstoneTools.importInternal("util/getLogger");
|
|
34
|
+
const getPixelSpacing = cornerstoneTools.importInternal("util/getPixelSpacing");
|
|
35
|
+
const getModule = cornerstoneTools.getModule;
|
|
36
|
+
|
|
37
|
+
const logger = getLogger("tools:annotation:RectangleRoiTool");
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @public
|
|
41
|
+
* @class RectangleRoiTool
|
|
42
|
+
* @memberof Tools.Annotation
|
|
43
|
+
* @classdesc Tool for drawing rectangular regions of interest, and measuring
|
|
44
|
+
* the statistics of the enclosed pixels.
|
|
45
|
+
* @extends Tools.Base.BaseAnnotationTool
|
|
46
|
+
*/
|
|
47
|
+
export default class RectangleRoiOverlayTool extends BaseAnnotationTool {
|
|
48
|
+
constructor(props = {}) {
|
|
49
|
+
const defaultProps = {
|
|
50
|
+
name: "RectangleRoiOverlay",
|
|
51
|
+
supportedInteractionTypes: ["Mouse", "Touch"],
|
|
52
|
+
configuration: {
|
|
53
|
+
drawHandles: true,
|
|
54
|
+
drawHandlesOnHover: false,
|
|
55
|
+
hideHandlesIfMoving: false,
|
|
56
|
+
renderDashed: false
|
|
57
|
+
// showMinMax: false,
|
|
58
|
+
// showHounsfieldUnits: true,
|
|
59
|
+
},
|
|
60
|
+
svgCursor: rectangleRoiCursor
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
super(props, defaultProps);
|
|
64
|
+
|
|
65
|
+
this.throttledUpdateCachedStats = throttle(this.updateCachedStats, 110);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
createNewMeasurement(eventData) {
|
|
69
|
+
const goodEventData =
|
|
70
|
+
eventData && eventData.currentPoints && eventData.currentPoints.image;
|
|
71
|
+
|
|
72
|
+
if (!goodEventData) {
|
|
73
|
+
logger.error(
|
|
74
|
+
`required eventData not supplied to tool ${this.name}'s createNewMeasurement`
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
computeMeasurements: this.options.computeMeasurements,
|
|
82
|
+
visible: true,
|
|
83
|
+
active: true,
|
|
84
|
+
color: undefined,
|
|
85
|
+
invalidated: true,
|
|
86
|
+
handles: {
|
|
87
|
+
start: {
|
|
88
|
+
x: eventData.currentPoints.image.x,
|
|
89
|
+
y: eventData.currentPoints.image.y,
|
|
90
|
+
highlight: true,
|
|
91
|
+
active: false
|
|
92
|
+
},
|
|
93
|
+
end: {
|
|
94
|
+
x: eventData.currentPoints.image.x,
|
|
95
|
+
y: eventData.currentPoints.image.y,
|
|
96
|
+
highlight: true,
|
|
97
|
+
active: true
|
|
98
|
+
},
|
|
99
|
+
initialRotation: eventData.viewport.rotation,
|
|
100
|
+
textBox: {
|
|
101
|
+
active: false,
|
|
102
|
+
hasMoved: false,
|
|
103
|
+
movesIndependently: false,
|
|
104
|
+
drawnIndependently: true,
|
|
105
|
+
allowedOutsideImage: true,
|
|
106
|
+
hasBoundingBox: true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pointNearTool(element, data, coords, interactionType) {
|
|
113
|
+
const hasStartAndEndHandles =
|
|
114
|
+
data && data.handles && data.handles.start && data.handles.end;
|
|
115
|
+
const validParameters = hasStartAndEndHandles;
|
|
116
|
+
|
|
117
|
+
if (!validParameters) {
|
|
118
|
+
logger.warn(
|
|
119
|
+
`invalid parameters supplied to tool ${this.name}'s pointNearTool`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!validParameters || data.visible === false) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const distance = interactionType === "mouse" ? 15 : 25;
|
|
128
|
+
const startCanvas = external.cornerstone.pixelToCanvas(
|
|
129
|
+
element,
|
|
130
|
+
data.handles.start
|
|
131
|
+
);
|
|
132
|
+
const endCanvas = external.cornerstone.pixelToCanvas(
|
|
133
|
+
element,
|
|
134
|
+
data.handles.end
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const rect = {
|
|
138
|
+
left: Math.min(startCanvas.x, endCanvas.x),
|
|
139
|
+
top: Math.min(startCanvas.y, endCanvas.y),
|
|
140
|
+
width: Math.abs(startCanvas.x - endCanvas.x),
|
|
141
|
+
height: Math.abs(startCanvas.y - endCanvas.y)
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const distanceToPoint = external.cornerstoneMath.rect.distanceToPoint(
|
|
145
|
+
rect,
|
|
146
|
+
coords
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return distanceToPoint < distance;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
updateCachedStats(image, element, data) {
|
|
153
|
+
if (data.computeMeasurements) {
|
|
154
|
+
const seriesModule =
|
|
155
|
+
external.cornerstone.metaData.get(
|
|
156
|
+
"generalSeriesModule",
|
|
157
|
+
image.imageId
|
|
158
|
+
) || {};
|
|
159
|
+
const modality = seriesModule.modality;
|
|
160
|
+
const pixelSpacing = getPixelSpacing(image);
|
|
161
|
+
|
|
162
|
+
const stats = _calculateStats(
|
|
163
|
+
image,
|
|
164
|
+
element,
|
|
165
|
+
data.handles,
|
|
166
|
+
modality,
|
|
167
|
+
pixelSpacing
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
data.cachedStats = stats;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
data.invalidated = false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
renderToolData(evt) {
|
|
177
|
+
const toolData = getToolState(evt.currentTarget, this.name);
|
|
178
|
+
|
|
179
|
+
if (!toolData) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const eventData = evt.detail;
|
|
184
|
+
const { image, element } = eventData;
|
|
185
|
+
const lineWidth = toolStyle.getToolWidth();
|
|
186
|
+
const lineDash = getModule("globalConfiguration").configuration.lineDash;
|
|
187
|
+
const {
|
|
188
|
+
handleRadius,
|
|
189
|
+
drawHandlesOnHover,
|
|
190
|
+
hideHandlesIfMoving,
|
|
191
|
+
renderDashed
|
|
192
|
+
} = this.configuration;
|
|
193
|
+
const context = getNewContext(eventData.canvasContext.canvas);
|
|
194
|
+
const { rowPixelSpacing, colPixelSpacing } = getPixelSpacing(image);
|
|
195
|
+
|
|
196
|
+
// Meta
|
|
197
|
+
const seriesModule =
|
|
198
|
+
external.cornerstone.metaData.get("generalSeriesModule", image.imageId) ||
|
|
199
|
+
{};
|
|
200
|
+
|
|
201
|
+
// Pixel Spacing
|
|
202
|
+
const modality = seriesModule.modality;
|
|
203
|
+
const hasPixelSpacing = rowPixelSpacing && colPixelSpacing;
|
|
204
|
+
|
|
205
|
+
draw(context, context => {
|
|
206
|
+
// If we have tool data for this element - iterate over each set and draw it
|
|
207
|
+
for (let i = 0; i < toolData.data.length; i++) {
|
|
208
|
+
const data = toolData.data[i];
|
|
209
|
+
|
|
210
|
+
if (data.visible === false) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Configure
|
|
215
|
+
const color = toolColors.getColorIfActive(data);
|
|
216
|
+
const handleOptions = {
|
|
217
|
+
color,
|
|
218
|
+
handleRadius,
|
|
219
|
+
drawHandlesIfActive: drawHandlesOnHover,
|
|
220
|
+
hideHandlesIfMoving
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
setShadow(context, this.configuration);
|
|
224
|
+
|
|
225
|
+
const rectOptions = { color };
|
|
226
|
+
|
|
227
|
+
if (renderDashed) {
|
|
228
|
+
rectOptions.lineDash = lineDash;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Draw
|
|
232
|
+
drawRect(
|
|
233
|
+
context,
|
|
234
|
+
element,
|
|
235
|
+
data.handles.start,
|
|
236
|
+
data.handles.end,
|
|
237
|
+
rectOptions,
|
|
238
|
+
"pixel",
|
|
239
|
+
data.handles.initialRotation
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (this.configuration.drawHandles) {
|
|
243
|
+
drawHandles(context, eventData, data.handles, handleOptions);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (data.computeMeasurements) {
|
|
247
|
+
// Update textbox stats
|
|
248
|
+
if (data.invalidated === true) {
|
|
249
|
+
if (data.cachedStats) {
|
|
250
|
+
this.throttledUpdateCachedStats(image, element, data);
|
|
251
|
+
} else {
|
|
252
|
+
this.updateCachedStats(image, element, data);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Default to textbox on right side of ROI
|
|
257
|
+
if (!data.handles.textBox.hasMoved) {
|
|
258
|
+
const defaultCoords = getROITextBoxCoords(
|
|
259
|
+
eventData.viewport,
|
|
260
|
+
data.handles
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
Object.assign(data.handles.textBox, defaultCoords);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const textBoxAnchorPoints = handles =>
|
|
267
|
+
_findTextBoxAnchorPoints(handles.start, handles.end);
|
|
268
|
+
const textBoxContent = _createTextBoxContent(
|
|
269
|
+
context,
|
|
270
|
+
image.color,
|
|
271
|
+
data.cachedStats,
|
|
272
|
+
modality,
|
|
273
|
+
hasPixelSpacing,
|
|
274
|
+
this.configuration
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
data.unit = _getUnit(
|
|
278
|
+
modality,
|
|
279
|
+
this.configuration.showHounsfieldUnits
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
drawLinkedTextBox(
|
|
283
|
+
context,
|
|
284
|
+
element,
|
|
285
|
+
data.handles.textBox,
|
|
286
|
+
textBoxContent,
|
|
287
|
+
data.handles,
|
|
288
|
+
textBoxAnchorPoints,
|
|
289
|
+
color,
|
|
290
|
+
lineWidth,
|
|
291
|
+
10,
|
|
292
|
+
true
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* TODO: This is the same method (+ GetPixels) for the other ROIs
|
|
302
|
+
* TODO: The pixel filtering is the unique bit
|
|
303
|
+
*
|
|
304
|
+
* @param {*} startHandle
|
|
305
|
+
* @param {*} endHandle
|
|
306
|
+
* @returns {{ left: number, top: number, width: number, height: number}}
|
|
307
|
+
*/
|
|
308
|
+
function _getRectangleImageCoordinates(startHandle, endHandle) {
|
|
309
|
+
return {
|
|
310
|
+
left: Math.min(startHandle.x, endHandle.x),
|
|
311
|
+
top: Math.min(startHandle.y, endHandle.y),
|
|
312
|
+
width: Math.abs(startHandle.x - endHandle.x),
|
|
313
|
+
height: Math.abs(startHandle.y - endHandle.y)
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
*
|
|
319
|
+
*
|
|
320
|
+
* @param {*} image
|
|
321
|
+
* @param {*} element
|
|
322
|
+
* @param {*} handles
|
|
323
|
+
* @param {*} modality
|
|
324
|
+
* @param {*} pixelSpacing
|
|
325
|
+
* @returns {Object} The Stats object
|
|
326
|
+
*/
|
|
327
|
+
function _calculateStats(image, element, handles, modality, pixelSpacing) {
|
|
328
|
+
// Retrieve the bounds of the rectangle in image coordinates
|
|
329
|
+
const roiCoordinates = _getRectangleImageCoordinates(
|
|
330
|
+
handles.start,
|
|
331
|
+
handles.end
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Retrieve the array of pixels that the rectangle bounds cover
|
|
335
|
+
const pixels = external.cornerstone.getPixels(
|
|
336
|
+
element,
|
|
337
|
+
roiCoordinates.left,
|
|
338
|
+
roiCoordinates.top,
|
|
339
|
+
roiCoordinates.width,
|
|
340
|
+
roiCoordinates.height
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Calculate the mean & standard deviation from the pixels and the rectangle details
|
|
344
|
+
const roiMeanStdDev = _calculateRectangleStats(pixels, roiCoordinates);
|
|
345
|
+
|
|
346
|
+
let meanStdDevSUV;
|
|
347
|
+
|
|
348
|
+
if (modality === "PT") {
|
|
349
|
+
meanStdDevSUV = {
|
|
350
|
+
mean: calculateSUV(image, roiMeanStdDev.mean, true) || 0,
|
|
351
|
+
stdDev: calculateSUV(image, roiMeanStdDev.stdDev, true) || 0
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Calculate the image area from the rectangle dimensions and pixel spacing
|
|
356
|
+
const area =
|
|
357
|
+
roiCoordinates.width *
|
|
358
|
+
(pixelSpacing.colPixelSpacing || 1) *
|
|
359
|
+
(roiCoordinates.height * (pixelSpacing.rowPixelSpacing || 1));
|
|
360
|
+
|
|
361
|
+
const perimeter =
|
|
362
|
+
roiCoordinates.width * 2 * (pixelSpacing.colPixelSpacing || 1) +
|
|
363
|
+
roiCoordinates.height * 2 * (pixelSpacing.rowPixelSpacing || 1);
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
area: area || 0,
|
|
367
|
+
perimeter,
|
|
368
|
+
count: roiMeanStdDev.count || 0,
|
|
369
|
+
mean: roiMeanStdDev.mean || 0,
|
|
370
|
+
variance: roiMeanStdDev.variance || 0,
|
|
371
|
+
stdDev: roiMeanStdDev.stdDev || 0,
|
|
372
|
+
min: roiMeanStdDev.min || 0,
|
|
373
|
+
max: roiMeanStdDev.max || 0,
|
|
374
|
+
meanStdDevSUV
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
*
|
|
380
|
+
*
|
|
381
|
+
* @param {*} sp
|
|
382
|
+
* @param {*} rectangle
|
|
383
|
+
* @returns {{ count, number, mean: number, variance: number, stdDev: number, min: number, max: number }}
|
|
384
|
+
*/
|
|
385
|
+
function _calculateRectangleStats(sp, rectangle) {
|
|
386
|
+
let sum = 0;
|
|
387
|
+
let sumSquared = 0;
|
|
388
|
+
let count = 0;
|
|
389
|
+
let index = 0;
|
|
390
|
+
let min = sp ? sp[0] : null;
|
|
391
|
+
let max = sp ? sp[0] : null;
|
|
392
|
+
|
|
393
|
+
for (let y = rectangle.top; y < rectangle.top + rectangle.height; y++) {
|
|
394
|
+
for (let x = rectangle.left; x < rectangle.left + rectangle.width; x++) {
|
|
395
|
+
sum += sp[index];
|
|
396
|
+
sumSquared += sp[index] * sp[index];
|
|
397
|
+
min = Math.min(min, sp[index]);
|
|
398
|
+
max = Math.max(max, sp[index]);
|
|
399
|
+
count++; // TODO: Wouldn't this just be sp.length?
|
|
400
|
+
index++;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (count === 0) {
|
|
405
|
+
return {
|
|
406
|
+
count,
|
|
407
|
+
mean: 0.0,
|
|
408
|
+
variance: 0.0,
|
|
409
|
+
stdDev: 0.0,
|
|
410
|
+
min: 0.0,
|
|
411
|
+
max: 0.0
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const mean = sum / count;
|
|
416
|
+
const variance = sumSquared / count - mean * mean;
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
count,
|
|
420
|
+
mean,
|
|
421
|
+
variance,
|
|
422
|
+
stdDev: Math.sqrt(variance),
|
|
423
|
+
min,
|
|
424
|
+
max
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
*
|
|
430
|
+
*
|
|
431
|
+
* @param {*} startHandle
|
|
432
|
+
* @param {*} endHandle
|
|
433
|
+
* @returns {Array.<{x: number, y: number}>}
|
|
434
|
+
*/
|
|
435
|
+
function _findTextBoxAnchorPoints(startHandle, endHandle) {
|
|
436
|
+
const { left, top, width, height } = _getRectangleImageCoordinates(
|
|
437
|
+
startHandle,
|
|
438
|
+
endHandle
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
return [
|
|
442
|
+
{
|
|
443
|
+
// Top middle point of rectangle
|
|
444
|
+
x: left + width / 2,
|
|
445
|
+
y: top
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
// Left middle point of rectangle
|
|
449
|
+
x: left,
|
|
450
|
+
y: top + height / 2
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
// Bottom middle point of rectangle
|
|
454
|
+
x: left + width / 2,
|
|
455
|
+
y: top + height
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
// Right middle point of rectangle
|
|
459
|
+
x: left + width,
|
|
460
|
+
y: top + height / 2
|
|
461
|
+
}
|
|
462
|
+
];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
*
|
|
467
|
+
*
|
|
468
|
+
* @param {*} area
|
|
469
|
+
* @param {*} hasPixelSpacing
|
|
470
|
+
* @returns {string} The formatted label for showing area
|
|
471
|
+
*/
|
|
472
|
+
function _formatArea(area, hasPixelSpacing) {
|
|
473
|
+
// This uses Char code 178 for a superscript 2
|
|
474
|
+
const suffix = hasPixelSpacing
|
|
475
|
+
? ` mm${String.fromCharCode(178)}`
|
|
476
|
+
: ` px${String.fromCharCode(178)}`;
|
|
477
|
+
|
|
478
|
+
return `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function _getUnit(modality, showHounsfieldUnits) {
|
|
482
|
+
return modality === "CT" && showHounsfieldUnits !== false ? "HU" : "";
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* TODO: This is identical to EllipticalROI's same fn
|
|
487
|
+
* TODO: We may want to make this a utility for ROIs with these values?
|
|
488
|
+
*
|
|
489
|
+
* @param {*} context
|
|
490
|
+
* @param {*} isColorImage
|
|
491
|
+
* @param {*} { area, mean, stdDev, min, max, meanStdDevSUV }
|
|
492
|
+
* @param {*} modality
|
|
493
|
+
* @param {*} hasPixelSpacing
|
|
494
|
+
* @param {*} [options={}]
|
|
495
|
+
* @returns {string[]}
|
|
496
|
+
*/
|
|
497
|
+
function _createTextBoxContent(
|
|
498
|
+
context,
|
|
499
|
+
isColorImage,
|
|
500
|
+
{ area = 0, mean = 0, stdDev = 0, min = 0, max = 0, meanStdDevSUV = 0 } = {},
|
|
501
|
+
modality,
|
|
502
|
+
hasPixelSpacing,
|
|
503
|
+
options = {}
|
|
504
|
+
) {
|
|
505
|
+
const showMinMax = options.showMinMax || false;
|
|
506
|
+
const textLines = [];
|
|
507
|
+
|
|
508
|
+
const otherLines = [];
|
|
509
|
+
|
|
510
|
+
if (!isColorImage) {
|
|
511
|
+
const hasStandardUptakeValues = meanStdDevSUV && meanStdDevSUV.mean !== 0;
|
|
512
|
+
const unit = _getUnit(modality, options.showHounsfieldUnits);
|
|
513
|
+
|
|
514
|
+
let meanString = `Mean: ${numbersWithCommas(mean.toFixed(2))} ${unit}`;
|
|
515
|
+
const stdDevString = `Std Dev: ${numbersWithCommas(
|
|
516
|
+
stdDev.toFixed(2)
|
|
517
|
+
)} ${unit}`;
|
|
518
|
+
|
|
519
|
+
// If this image has SUV values to display, concatenate them to the text line
|
|
520
|
+
if (hasStandardUptakeValues) {
|
|
521
|
+
const SUVtext = " SUV: ";
|
|
522
|
+
|
|
523
|
+
const meanSuvString = `${SUVtext}${numbersWithCommas(
|
|
524
|
+
meanStdDevSUV.mean.toFixed(2)
|
|
525
|
+
)}`;
|
|
526
|
+
const stdDevSuvString = `${SUVtext}${numbersWithCommas(
|
|
527
|
+
meanStdDevSUV.stdDev.toFixed(2)
|
|
528
|
+
)}`;
|
|
529
|
+
|
|
530
|
+
const targetStringLength = Math.floor(
|
|
531
|
+
context.measureText(`${stdDevString} `).width
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
while (context.measureText(meanString).width < targetStringLength) {
|
|
535
|
+
meanString += " ";
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
otherLines.push(`${meanString}${meanSuvString}`);
|
|
539
|
+
otherLines.push(`${stdDevString} ${stdDevSuvString}`);
|
|
540
|
+
} else {
|
|
541
|
+
otherLines.push(`${meanString}`);
|
|
542
|
+
otherLines.push(`${stdDevString}`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (showMinMax) {
|
|
546
|
+
let minString = `Min: ${min} ${unit}`;
|
|
547
|
+
const maxString = `Max: ${max} ${unit}`;
|
|
548
|
+
const targetStringLength = hasStandardUptakeValues
|
|
549
|
+
? Math.floor(context.measureText(`${stdDevString} `).width)
|
|
550
|
+
: Math.floor(context.measureText(`${meanString} `).width);
|
|
551
|
+
|
|
552
|
+
while (context.measureText(minString).width < targetStringLength) {
|
|
553
|
+
minString += " ";
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
otherLines.push(`${minString}${maxString}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
textLines.push(_formatArea(area, hasPixelSpacing));
|
|
561
|
+
otherLines.forEach(x => textLines.push(x));
|
|
562
|
+
|
|
563
|
+
return textLines;
|
|
564
|
+
}
|
|
@@ -4,21 +4,15 @@
|
|
|
4
4
|
* @ronzim
|
|
5
5
|
*/
|
|
6
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
7
|
const ARRAY_TYPES = {
|
|
16
8
|
UINT_16_ARRAY: 0,
|
|
17
9
|
FLOAT_32_ARRAY: 1
|
|
18
10
|
};
|
|
19
11
|
const { UINT_16_ARRAY, FLOAT_32_ARRAY } = ARRAY_TYPES;
|
|
20
12
|
|
|
21
|
-
import cornerstoneTools from "cornerstone-tools
|
|
13
|
+
import cornerstoneTools from "cornerstone-tools";
|
|
14
|
+
import type { TypedArray } from "../../types";
|
|
15
|
+
import { EnabledElement } from "cornerstone-core";
|
|
22
16
|
const { triggerLabelmapModifiedEvent } = cornerstoneTools.importInternal(
|
|
23
17
|
"util/segmentationUtils"
|
|
24
18
|
);
|
|
@@ -33,12 +27,12 @@ const state = getModule("segmentation").state;
|
|
|
33
27
|
*/
|
|
34
28
|
|
|
35
29
|
// from getSegmentsOnPixelData.js
|
|
36
|
-
function getSegmentsOnPixelData(pixelData) {
|
|
30
|
+
function getSegmentsOnPixelData(pixelData: TypedArray) {
|
|
37
31
|
return [...new Set(pixelData)];
|
|
38
32
|
}
|
|
39
33
|
|
|
40
34
|
// from getElement.js
|
|
41
|
-
function getElement(elementOrEnabledElementUID) {
|
|
35
|
+
function getElement(elementOrEnabledElementUID: EnabledElement | string) {
|
|
42
36
|
if (elementOrEnabledElementUID instanceof HTMLElement) {
|
|
43
37
|
return elementOrEnabledElementUID;
|
|
44
38
|
}
|
|
@@ -61,11 +55,11 @@ function getElement(elementOrEnabledElementUID) {
|
|
|
61
55
|
* @returns {null}
|
|
62
56
|
*/
|
|
63
57
|
async function setLabelmap3DForElement(
|
|
64
|
-
elementOrEnabledElementUID,
|
|
65
|
-
buffer,
|
|
66
|
-
labelmapIndex,
|
|
67
|
-
metadata = [],
|
|
68
|
-
segmentsOnLabelmapArray,
|
|
58
|
+
elementOrEnabledElementUID: EnabledElement | string,
|
|
59
|
+
buffer: ArrayBuffer,
|
|
60
|
+
labelmapIndex: number,
|
|
61
|
+
metadata: Object[] = [],
|
|
62
|
+
segmentsOnLabelmapArray: number[][],
|
|
69
63
|
colorLUTIndex = 0
|
|
70
64
|
) {
|
|
71
65
|
const element = getElement(elementOrEnabledElementUID);
|
|
@@ -110,13 +104,13 @@ async function setLabelmap3DForElement(
|
|
|
110
104
|
* @returns {null}
|
|
111
105
|
*/
|
|
112
106
|
function setLabelmap3DByFirstImageId(
|
|
113
|
-
firstImageId,
|
|
114
|
-
buffer,
|
|
115
|
-
labelmapIndex,
|
|
116
|
-
metadata = [],
|
|
117
|
-
numberOfFrames,
|
|
118
|
-
segmentsOnLabelmapArray,
|
|
119
|
-
colorLUTIndex = 0
|
|
107
|
+
firstImageId: string,
|
|
108
|
+
buffer: ArrayBuffer,
|
|
109
|
+
labelmapIndex: number,
|
|
110
|
+
metadata: Object[] = [],
|
|
111
|
+
numberOfFrames: number,
|
|
112
|
+
segmentsOnLabelmapArray: number[][],
|
|
113
|
+
colorLUTIndex: number = 0
|
|
120
114
|
) {
|
|
121
115
|
const { configuration } = getModule("segmentation");
|
|
122
116
|
|
|
@@ -148,8 +142,8 @@ function setLabelmap3DByFirstImageId(
|
|
|
148
142
|
/* non-blocking implementation by @ronzim */
|
|
149
143
|
|
|
150
144
|
return new Promise(resolve => {
|
|
151
|
-
function setSingleSlice(i, numberOfFrames) {
|
|
152
|
-
var pixelData = void 0;
|
|
145
|
+
function setSingleSlice(i: number, numberOfFrames: number) {
|
|
146
|
+
var pixelData: TypedArray | undefined = void 0;
|
|
153
147
|
|
|
154
148
|
switch (configuration.arrayType) {
|
|
155
149
|
case UINT_16_ARRAY:
|