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