@cornerstonejs/tools 1.78.3 → 1.80.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/cjs/cursors/SVGCursorDescriptor.js +7 -0
- package/dist/cjs/cursors/SVGCursorDescriptor.js.map +1 -1
- package/dist/cjs/drawingSvg/drawHeight.d.ts +3 -0
- package/dist/cjs/drawingSvg/drawHeight.js +49 -0
- package/dist/cjs/drawingSvg/drawHeight.js.map +1 -0
- package/dist/cjs/drawingSvg/index.d.ts +2 -1
- package/dist/cjs/drawingSvg/index.js +3 -1
- package/dist/cjs/drawingSvg/index.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tools/annotation/HeightTool.d.ts +40 -0
- package/dist/cjs/tools/annotation/HeightTool.js +463 -0
- package/dist/cjs/tools/annotation/HeightTool.js.map +1 -0
- package/dist/cjs/tools/index.d.ts +2 -1
- package/dist/cjs/tools/index.js +4 -2
- package/dist/cjs/tools/index.js.map +1 -1
- package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.d.ts +15 -8
- package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.js +189 -62
- package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.js.map +1 -1
- package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +17 -7
- package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.js +167 -52
- package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.js.map +1 -1
- package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +26 -4
- package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.d.ts +3 -0
- package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.js +31 -0
- package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.js.map +1 -0
- package/dist/cjs/utilities/planar/index.d.ts +3 -1
- package/dist/cjs/utilities/planar/index.js +4 -1
- package/dist/cjs/utilities/planar/index.js.map +1 -1
- package/dist/cjs/utilities/viewport/isViewportPreScaled.js +1 -4
- package/dist/cjs/utilities/viewport/isViewportPreScaled.js.map +1 -1
- package/dist/esm/cursors/SVGCursorDescriptor.js +7 -0
- package/dist/esm/cursors/SVGCursorDescriptor.js.map +1 -1
- package/dist/esm/drawingSvg/drawHeight.js +43 -0
- package/dist/esm/drawingSvg/drawHeight.js.map +1 -0
- package/dist/esm/drawingSvg/index.js +2 -1
- package/dist/esm/drawingSvg/index.js.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tools/annotation/HeightTool.js +439 -0
- package/dist/esm/tools/annotation/HeightTool.js.map +1 -0
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/index.js.map +1 -1
- package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +192 -66
- package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js.map +1 -1
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +168 -54
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js.map +1 -1
- package/dist/esm/utilities/planar/filterAnnotationsWithinPlane.js +27 -0
- package/dist/esm/utilities/planar/filterAnnotationsWithinPlane.js.map +1 -0
- package/dist/esm/utilities/planar/index.js +3 -1
- package/dist/esm/utilities/planar/index.js.map +1 -1
- package/dist/esm/utilities/viewport/isViewportPreScaled.js +2 -5
- package/dist/esm/utilities/viewport/isViewportPreScaled.js.map +1 -1
- package/dist/types/cursors/SVGCursorDescriptor.d.ts.map +1 -1
- package/dist/types/drawingSvg/drawHeight.d.ts +4 -0
- package/dist/types/drawingSvg/drawHeight.d.ts.map +1 -0
- package/dist/types/drawingSvg/index.d.ts +2 -1
- package/dist/types/drawingSvg/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/tools/annotation/HeightTool.d.ts +41 -0
- package/dist/types/tools/annotation/HeightTool.d.ts.map +1 -0
- package/dist/types/tools/index.d.ts +2 -1
- package/dist/types/tools/index.d.ts.map +1 -1
- package/dist/types/tools/segmentation/CircleROIStartEndThresholdTool.d.ts +15 -8
- package/dist/types/tools/segmentation/CircleROIStartEndThresholdTool.d.ts.map +1 -1
- package/dist/types/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +17 -7
- package/dist/types/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts.map +1 -1
- package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +26 -4
- package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
- package/dist/types/utilities/planar/filterAnnotationsWithinPlane.d.ts +4 -0
- package/dist/types/utilities/planar/filterAnnotationsWithinPlane.d.ts.map +1 -0
- package/dist/types/utilities/planar/index.d.ts +3 -1
- package/dist/types/utilities/planar/index.d.ts.map +1 -1
- package/dist/types/utilities/viewport/isViewportPreScaled.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/cursors/SVGCursorDescriptor.ts +7 -0
- package/src/drawingSvg/drawHeight.ts +90 -0
- package/src/drawingSvg/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/tools/annotation/HeightTool.ts +882 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/segmentation/CircleROIStartEndThresholdTool.ts +310 -102
- package/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts +287 -77
- package/src/types/ToolSpecificAnnotationTypes.ts +26 -4
- package/src/utilities/planar/filterAnnotationsWithinPlane.ts +76 -0
- package/src/utilities/planar/index.ts +3 -0
- package/src/utilities/viewport/isViewportPreScaled.ts +2 -5
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
getEnabledElement,
|
|
3
3
|
cache,
|
|
4
4
|
StackViewport,
|
|
5
|
-
metaData,
|
|
6
5
|
utilities as csUtils,
|
|
7
6
|
} from '@cornerstonejs/core';
|
|
8
|
-
import
|
|
7
|
+
import { Types, utilities as coreUtils } from '@cornerstonejs/core';
|
|
9
8
|
|
|
9
|
+
import { getCalibratedLengthUnitsAndScale } from '../../utilities/getCalibratedUnits';
|
|
10
10
|
import { vec3 } from 'gl-matrix';
|
|
11
11
|
import {
|
|
12
12
|
addAnnotation,
|
|
@@ -14,20 +14,26 @@ import {
|
|
|
14
14
|
removeAnnotation,
|
|
15
15
|
} from '../../stateManagement';
|
|
16
16
|
import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
|
|
17
|
-
import { triggerAnnotationModified } from '../../stateManagement/annotation/helpers/state';
|
|
18
17
|
import {
|
|
19
18
|
drawHandles as drawHandlesSvg,
|
|
20
19
|
drawRect as drawRectSvg,
|
|
20
|
+
drawLinkedTextBox as drawLinkedTextBoxSvg,
|
|
21
21
|
} from '../../drawingSvg';
|
|
22
22
|
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
23
23
|
import throttle from '../../utilities/throttle';
|
|
24
|
+
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
25
|
+
import getWorldWidthAndHeightFromCorners from '../../utilities/planar/getWorldWidthAndHeightFromCorners';
|
|
26
|
+
|
|
24
27
|
import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility';
|
|
25
28
|
import {
|
|
26
29
|
hideElementCursor,
|
|
27
30
|
resetElementCursor,
|
|
28
31
|
} from '../../cursors/elementCursor';
|
|
29
32
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
30
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
triggerAnnotationCompleted,
|
|
35
|
+
triggerAnnotationModified,
|
|
36
|
+
} from '../../stateManagement/annotation/helpers/state';
|
|
31
37
|
|
|
32
38
|
import {
|
|
33
39
|
PublicToolProps,
|
|
@@ -38,7 +44,11 @@ import {
|
|
|
38
44
|
import { RectangleROIStartEndThresholdAnnotation } from '../../types/ToolSpecificAnnotationTypes';
|
|
39
45
|
import RectangleROITool from '../annotation/RectangleROITool';
|
|
40
46
|
import { StyleSpecifier } from '../../types/AnnotationStyle';
|
|
41
|
-
import { pointInShapeCallback } from '../../utilities/';
|
|
47
|
+
import { pointInShapeCallback, roundNumber } from '../../utilities/';
|
|
48
|
+
import { getModalityUnit } from '../../utilities/getModalityUnit';
|
|
49
|
+
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
50
|
+
import { BasicStatsCalculator } from '../../utilities/math/basic';
|
|
51
|
+
import { filterAnnotationsWithinSamePlane } from '../../utilities/planar';
|
|
42
52
|
|
|
43
53
|
const { transformWorldToIndex } = csUtils;
|
|
44
54
|
|
|
@@ -73,6 +83,9 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
73
83
|
configuration: {
|
|
74
84
|
numSlicesToPropagate: 10,
|
|
75
85
|
computePointsInsideVolume: false,
|
|
86
|
+
getTextLines: defaultGetTextLines,
|
|
87
|
+
statsCalculator: BasicStatsCalculator,
|
|
88
|
+
showTextBox: false,
|
|
76
89
|
},
|
|
77
90
|
}
|
|
78
91
|
) {
|
|
@@ -120,22 +133,18 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
120
133
|
);
|
|
121
134
|
}
|
|
122
135
|
|
|
123
|
-
if (!referencedImageId) {
|
|
124
|
-
throw new Error('This tool does not work on non-acquisition planes');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const startIndex = viewport.getCurrentImageIdIndex();
|
|
128
136
|
const spacingInNormal = csUtils.getSpacingInNormalDirection(
|
|
129
137
|
imageVolume,
|
|
130
138
|
viewPlaneNormal
|
|
131
139
|
);
|
|
132
140
|
|
|
141
|
+
const startCoord = this._getStartCoordinate(worldPos, viewPlaneNormal);
|
|
142
|
+
|
|
133
143
|
// We cannot simply add numSlicesToPropagate to startIndex because
|
|
134
144
|
// the order of imageIds can be from top to bottom or bottom to top and
|
|
135
145
|
// we want to make sure it is always propagated in the direction of the
|
|
136
146
|
// view and also to make sure we don't go out of bounds.
|
|
137
|
-
const
|
|
138
|
-
imageVolume,
|
|
147
|
+
const endCoord = this._getEndCoordinate(
|
|
139
148
|
worldPos,
|
|
140
149
|
spacingInNormal,
|
|
141
150
|
viewPlaneNormal
|
|
@@ -158,19 +167,24 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
158
167
|
},
|
|
159
168
|
data: {
|
|
160
169
|
label: '',
|
|
161
|
-
|
|
162
|
-
|
|
170
|
+
startCoordinate: startCoord,
|
|
171
|
+
endCoordinate: endCoord,
|
|
163
172
|
cachedStats: {
|
|
164
173
|
pointsInVolume: [],
|
|
165
174
|
projectionPoints: [],
|
|
166
175
|
projectionPointsImageIds: [referencedImageId],
|
|
176
|
+
statistics: [],
|
|
167
177
|
},
|
|
168
178
|
handles: {
|
|
169
|
-
// No need a textBox
|
|
170
179
|
textBox: {
|
|
171
180
|
hasMoved: false,
|
|
172
|
-
worldPosition:
|
|
173
|
-
worldBoundingBox:
|
|
181
|
+
worldPosition: <Types.Point3>[0, 0, 0],
|
|
182
|
+
worldBoundingBox: {
|
|
183
|
+
topLeft: <Types.Point3>[0, 0, 0],
|
|
184
|
+
topRight: <Types.Point3>[0, 0, 0],
|
|
185
|
+
bottomLeft: <Types.Point3>[0, 0, 0],
|
|
186
|
+
bottomRight: <Types.Point3>[0, 0, 0],
|
|
187
|
+
},
|
|
174
188
|
},
|
|
175
189
|
points: [
|
|
176
190
|
<Types.Point3>[...worldPos],
|
|
@@ -249,7 +263,12 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
249
263
|
const imageVolume = cache.getVolume(targetId.split(/volumeId:|\?/)[1]);
|
|
250
264
|
|
|
251
265
|
if (this.configuration.calculatePointsInsideVolume) {
|
|
252
|
-
this._computePointsInsideVolume(
|
|
266
|
+
this._computePointsInsideVolume(
|
|
267
|
+
annotation,
|
|
268
|
+
targetId,
|
|
269
|
+
imageVolume,
|
|
270
|
+
enabledElement
|
|
271
|
+
);
|
|
253
272
|
}
|
|
254
273
|
|
|
255
274
|
triggerAnnotationRenderForViewportIds(
|
|
@@ -262,7 +281,7 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
262
281
|
}
|
|
263
282
|
};
|
|
264
283
|
|
|
265
|
-
//
|
|
284
|
+
//Now works for non-acquisition planes
|
|
266
285
|
_computeProjectionPoints(
|
|
267
286
|
annotation: RectangleROIStartEndThresholdAnnotation,
|
|
268
287
|
imageVolume: Types.IImageVolume
|
|
@@ -270,17 +289,11 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
270
289
|
const { data, metadata } = annotation;
|
|
271
290
|
const { viewPlaneNormal, spacingInNormal } = metadata;
|
|
272
291
|
const { imageData } = imageVolume;
|
|
273
|
-
const {
|
|
292
|
+
const { startCoordinate, endCoordinate } = data;
|
|
274
293
|
const { points } = data.handles;
|
|
275
294
|
|
|
276
295
|
const startIJK = transformWorldToIndex(imageData, points[0]);
|
|
277
|
-
|
|
278
|
-
if (startIJK[2] !== startSlice) {
|
|
279
|
-
throw new Error('Start slice does not match');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// substitute the end slice index 2 with startIJK index 2
|
|
283
|
-
const endIJK = vec3.fromValues(startIJK[0], startIJK[1], endSlice);
|
|
296
|
+
const endIJK = transformWorldToIndex(imageData, points[0]);
|
|
284
297
|
|
|
285
298
|
const startWorld = vec3.create();
|
|
286
299
|
imageData.indexToWorldVec3(startIJK, startWorld);
|
|
@@ -288,6 +301,23 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
288
301
|
const endWorld = vec3.create();
|
|
289
302
|
imageData.indexToWorldVec3(endIJK, endWorld);
|
|
290
303
|
|
|
304
|
+
// substitute the end slice index 2 with startIJK index 2
|
|
305
|
+
|
|
306
|
+
if (this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal) == 2) {
|
|
307
|
+
startWorld[2] = startCoordinate;
|
|
308
|
+
endWorld[2] = endCoordinate;
|
|
309
|
+
} else if (
|
|
310
|
+
this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal) == 0
|
|
311
|
+
) {
|
|
312
|
+
startWorld[0] = startCoordinate;
|
|
313
|
+
endWorld[0] = endCoordinate;
|
|
314
|
+
} else if (
|
|
315
|
+
this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal) == 1
|
|
316
|
+
) {
|
|
317
|
+
startWorld[1] = startCoordinate;
|
|
318
|
+
endWorld[1] = endCoordinate;
|
|
319
|
+
}
|
|
320
|
+
|
|
291
321
|
// distance between start and end slice in the world coordinate
|
|
292
322
|
const distance = vec3.distance(startWorld, endWorld);
|
|
293
323
|
|
|
@@ -298,6 +328,7 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
298
328
|
newProjectionPoints.push(
|
|
299
329
|
points.map((point) => {
|
|
300
330
|
const newPoint = vec3.create();
|
|
331
|
+
//@ts-ignore
|
|
301
332
|
vec3.scaleAndAdd(newPoint, point, viewPlaneNormal, dist);
|
|
302
333
|
return Array.from(newPoint);
|
|
303
334
|
})
|
|
@@ -305,27 +336,54 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
305
336
|
}
|
|
306
337
|
|
|
307
338
|
data.cachedStats.projectionPoints = newProjectionPoints;
|
|
308
|
-
|
|
309
|
-
// Find the imageIds for the projection points
|
|
310
|
-
const projectionPointsImageIds = [];
|
|
311
|
-
for (const RectanglePoints of newProjectionPoints) {
|
|
312
|
-
const imageId = csUtils.getClosestImageId(
|
|
313
|
-
imageVolume,
|
|
314
|
-
RectanglePoints[0],
|
|
315
|
-
viewPlaneNormal
|
|
316
|
-
);
|
|
317
|
-
projectionPointsImageIds.push(imageId);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
data.cachedStats.projectionPointsImageIds = projectionPointsImageIds;
|
|
321
339
|
}
|
|
322
340
|
|
|
323
|
-
//This function return all the points inside the ROI for every slices between
|
|
324
|
-
_computePointsInsideVolume(
|
|
325
|
-
|
|
341
|
+
//This function return all the points inside the ROI and calculate statistics for every slices between startCoordinate and endCoordinate
|
|
342
|
+
_computePointsInsideVolume(
|
|
343
|
+
annotation,
|
|
344
|
+
targetId,
|
|
345
|
+
imageVolume,
|
|
346
|
+
enabledElement
|
|
347
|
+
) {
|
|
348
|
+
const { data, metadata } = annotation;
|
|
349
|
+
const { viewPlaneNormal, viewUp } = metadata;
|
|
350
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
351
|
+
|
|
326
352
|
const projectionPoints = data.cachedStats.projectionPoints;
|
|
327
353
|
|
|
328
354
|
const pointsInsideVolume: Types.Point3[][] = [[]];
|
|
355
|
+
const image = this.getTargetIdImage(targetId, renderingEngine);
|
|
356
|
+
|
|
357
|
+
const worldPos1 = data.handles.points[0];
|
|
358
|
+
const worldPos2 = data.handles.points[3];
|
|
359
|
+
|
|
360
|
+
const { worldWidth, worldHeight } = getWorldWidthAndHeightFromCorners(
|
|
361
|
+
viewPlaneNormal,
|
|
362
|
+
viewUp,
|
|
363
|
+
worldPos1,
|
|
364
|
+
worldPos2
|
|
365
|
+
);
|
|
366
|
+
const measureInfo = getCalibratedLengthUnitsAndScale(image, data.habdles);
|
|
367
|
+
|
|
368
|
+
const area =
|
|
369
|
+
Math.abs(worldWidth * worldHeight) /
|
|
370
|
+
(measureInfo.scale * measureInfo.scale);
|
|
371
|
+
|
|
372
|
+
const modalityUnitOptions = {
|
|
373
|
+
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
374
|
+
|
|
375
|
+
isSuvScaled: this.isSuvScaled(
|
|
376
|
+
viewport,
|
|
377
|
+
targetId,
|
|
378
|
+
annotation.metadata.referencedImageId
|
|
379
|
+
),
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const modalityUnit = getModalityUnit(
|
|
383
|
+
metadata.Modality,
|
|
384
|
+
annotation.metadata.referencedImageId,
|
|
385
|
+
modalityUnitOptions
|
|
386
|
+
);
|
|
329
387
|
|
|
330
388
|
for (let i = 0; i < projectionPoints.length; i++) {
|
|
331
389
|
// If image does not exists for the targetId, skip. This can be due
|
|
@@ -337,9 +395,6 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
337
395
|
|
|
338
396
|
const projectionPoint = projectionPoints[i][0];
|
|
339
397
|
|
|
340
|
-
const worldPos1 = data.handles.points[0];
|
|
341
|
-
const worldPos2 = data.handles.points[3];
|
|
342
|
-
|
|
343
398
|
const { dimensions, imageData } = imageVolume;
|
|
344
399
|
|
|
345
400
|
const worldPos1Index = transformWorldToIndex(imageData, worldPos1);
|
|
@@ -349,15 +404,24 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
349
404
|
projectionPoint
|
|
350
405
|
);
|
|
351
406
|
|
|
407
|
+
const indexOfProjection =
|
|
408
|
+
this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal);
|
|
409
|
+
|
|
352
410
|
worldPos1Index[0] = Math.floor(worldPos1Index[0]);
|
|
353
411
|
worldPos1Index[1] = Math.floor(worldPos1Index[1]);
|
|
354
|
-
worldPos1Index[2] = Math.floor(
|
|
412
|
+
worldPos1Index[2] = Math.floor(worldPos1Index[2]);
|
|
413
|
+
|
|
414
|
+
worldPos1Index[indexOfProjection] =
|
|
415
|
+
worldProjectionPointIndex[indexOfProjection];
|
|
355
416
|
|
|
356
417
|
const worldPos2Index = transformWorldToIndex(imageData, worldPos2);
|
|
357
418
|
|
|
358
419
|
worldPos2Index[0] = Math.floor(worldPos2Index[0]);
|
|
359
420
|
worldPos2Index[1] = Math.floor(worldPos2Index[1]);
|
|
360
|
-
worldPos2Index[2] = Math.floor(
|
|
421
|
+
worldPos2Index[2] = Math.floor(worldPos2Index[2]);
|
|
422
|
+
|
|
423
|
+
worldPos2Index[indexOfProjection] =
|
|
424
|
+
worldProjectionPointIndex[indexOfProjection];
|
|
361
425
|
|
|
362
426
|
// Check if one of the indexes are inside the volume, this then gives us
|
|
363
427
|
// Some area to do stats over.
|
|
@@ -382,7 +446,7 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
382
446
|
const pointsInShape = pointInShapeCallback(
|
|
383
447
|
imageData,
|
|
384
448
|
() => true,
|
|
385
|
-
|
|
449
|
+
this.configuration.statsCalculator.statsCallback,
|
|
386
450
|
boundsIJK
|
|
387
451
|
);
|
|
388
452
|
|
|
@@ -390,7 +454,18 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
390
454
|
pointsInsideVolume.push(pointsInShape);
|
|
391
455
|
}
|
|
392
456
|
}
|
|
457
|
+
const stats = this.configuration.statsCalculator.getStatistics();
|
|
393
458
|
data.cachedStats.pointsInVolume = pointsInsideVolume;
|
|
459
|
+
data.cachedStats.statistics = {
|
|
460
|
+
Modality: metadata.Modality,
|
|
461
|
+
area,
|
|
462
|
+
mean: stats.mean?.value,
|
|
463
|
+
stdDev: stats.stdDev?.value,
|
|
464
|
+
max: stats.max?.value,
|
|
465
|
+
statsArray: stats.array,
|
|
466
|
+
areaUnit: measureInfo.areaUnits,
|
|
467
|
+
modalityUnit,
|
|
468
|
+
};
|
|
394
469
|
}
|
|
395
470
|
|
|
396
471
|
_calculateCachedStatsTool(annotation, enabledElement) {
|
|
@@ -427,14 +502,16 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
427
502
|
): boolean => {
|
|
428
503
|
let renderStatus = false;
|
|
429
504
|
const { viewport } = enabledElement;
|
|
430
|
-
|
|
431
|
-
const annotations = getAnnotations(this.getToolName(), viewport.element);
|
|
505
|
+
let annotations = getAnnotations(this.getToolName(), viewport.element);
|
|
432
506
|
|
|
433
507
|
if (!annotations?.length) {
|
|
434
508
|
return renderStatus;
|
|
435
509
|
}
|
|
436
510
|
|
|
437
|
-
|
|
511
|
+
annotations = filterAnnotationsWithinSamePlane(
|
|
512
|
+
annotations,
|
|
513
|
+
viewport.getCamera()
|
|
514
|
+
);
|
|
438
515
|
|
|
439
516
|
const styleSpecifier: StyleSpecifier = {
|
|
440
517
|
toolGroupId: this.toolGroupId,
|
|
@@ -447,7 +524,7 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
447
524
|
i
|
|
448
525
|
] as RectangleROIStartEndThresholdAnnotation;
|
|
449
526
|
const { annotationUID, data } = annotation;
|
|
450
|
-
const {
|
|
527
|
+
const { startCoordinate, endCoordinate } = data;
|
|
451
528
|
const { points, activeHandleIndex } = data.handles;
|
|
452
529
|
|
|
453
530
|
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
@@ -460,10 +537,37 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
460
537
|
// range of slices to render based on the start and end slice, like
|
|
461
538
|
// np.arange
|
|
462
539
|
|
|
463
|
-
|
|
540
|
+
const focalPoint = viewport.getCamera().focalPoint;
|
|
541
|
+
const viewplaneNormal = viewport.getCamera().viewPlaneNormal;
|
|
542
|
+
|
|
543
|
+
let startCoord: number | vec3 = startCoordinate;
|
|
544
|
+
let endCoord: number | vec3 = endCoordinate;
|
|
545
|
+
if (Array.isArray(startCoordinate)) {
|
|
546
|
+
startCoord = this._getCoordinateForViewplaneNormal(
|
|
547
|
+
startCoord,
|
|
548
|
+
viewplaneNormal
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (Array.isArray(endCoordinate)) {
|
|
553
|
+
endCoord = this._getCoordinateForViewplaneNormal(
|
|
554
|
+
endCoord,
|
|
555
|
+
viewplaneNormal
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const roundedStartCoord = coreUtils.roundToPrecision(startCoord);
|
|
560
|
+
const roundedEndCoord = coreUtils.roundToPrecision(endCoord);
|
|
561
|
+
|
|
562
|
+
const coord = this._getCoordinateForViewplaneNormal(
|
|
563
|
+
focalPoint,
|
|
564
|
+
viewplaneNormal
|
|
565
|
+
);
|
|
566
|
+
const roundedCoord = coreUtils.roundToPrecision(coord);
|
|
567
|
+
// if the focalpoint is outside the start/end coordinates, we don't render
|
|
464
568
|
if (
|
|
465
|
-
|
|
466
|
-
|
|
569
|
+
roundedCoord < Math.min(roundedStartCoord, roundedEndCoord) ||
|
|
570
|
+
roundedCoord > Math.max(roundedStartCoord, roundedEndCoord)
|
|
467
571
|
) {
|
|
468
572
|
continue;
|
|
469
573
|
}
|
|
@@ -477,7 +581,10 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
477
581
|
// if it is inside the start/end slice, but not exactly the first or
|
|
478
582
|
// last slice, we render the line in dash, but not the handles
|
|
479
583
|
let firstOrLastSlice = false;
|
|
480
|
-
if (
|
|
584
|
+
if (
|
|
585
|
+
roundedCoord === roundedStartCoord ||
|
|
586
|
+
roundedCoord === roundedEndCoord
|
|
587
|
+
) {
|
|
481
588
|
firstOrLastSlice = true;
|
|
482
589
|
}
|
|
483
590
|
|
|
@@ -538,13 +645,82 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
538
645
|
);
|
|
539
646
|
|
|
540
647
|
renderStatus = true;
|
|
648
|
+
|
|
649
|
+
if (
|
|
650
|
+
this.configuration.showTextBox &&
|
|
651
|
+
this.configuration.calculatePointsInsideVolume
|
|
652
|
+
) {
|
|
653
|
+
const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
|
654
|
+
if (!options.visibility) {
|
|
655
|
+
data.handles.textBox = {
|
|
656
|
+
hasMoved: false,
|
|
657
|
+
worldPosition: <Types.Point3>[0, 0, 0],
|
|
658
|
+
worldBoundingBox: {
|
|
659
|
+
topLeft: <Types.Point3>[0, 0, 0],
|
|
660
|
+
topRight: <Types.Point3>[0, 0, 0],
|
|
661
|
+
bottomLeft: <Types.Point3>[0, 0, 0],
|
|
662
|
+
bottomRight: <Types.Point3>[0, 0, 0],
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const textLines = this.configuration.getTextLines(data);
|
|
669
|
+
if (!textLines || textLines.length === 0) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (!data.handles.textBox.hasMoved) {
|
|
674
|
+
const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);
|
|
675
|
+
|
|
676
|
+
data.handles.textBox.worldPosition =
|
|
677
|
+
viewport.canvasToWorld(canvasTextBoxCoords);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const textBoxPosition = viewport.worldToCanvas(
|
|
681
|
+
data.handles.textBox.worldPosition
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
const textBoxUID = '1';
|
|
685
|
+
const boundingBox = drawLinkedTextBoxSvg(
|
|
686
|
+
svgDrawingHelper,
|
|
687
|
+
annotationUID,
|
|
688
|
+
textBoxUID,
|
|
689
|
+
textLines,
|
|
690
|
+
textBoxPosition,
|
|
691
|
+
canvasCoordinates,
|
|
692
|
+
{},
|
|
693
|
+
options
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
const { x: left, y: top, width, height } = boundingBox;
|
|
697
|
+
|
|
698
|
+
data.handles.textBox.worldBoundingBox = {
|
|
699
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
700
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
701
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
702
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
541
705
|
}
|
|
542
706
|
|
|
543
707
|
return renderStatus;
|
|
544
708
|
};
|
|
545
709
|
|
|
546
|
-
|
|
547
|
-
|
|
710
|
+
_getStartCoordinate(
|
|
711
|
+
worldPos: Types.Point3,
|
|
712
|
+
viewPlaneNormal: Types.Point3
|
|
713
|
+
): number | undefined {
|
|
714
|
+
const startPos = worldPos;
|
|
715
|
+
const startCoord = this._getCoordinateForViewplaneNormal(
|
|
716
|
+
startPos,
|
|
717
|
+
viewPlaneNormal
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
return startCoord;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
_getEndCoordinate(
|
|
548
724
|
worldPos: Types.Point3,
|
|
549
725
|
spacingInNormal: number,
|
|
550
726
|
viewPlaneNormal: Types.Point3
|
|
@@ -561,31 +737,65 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
|
|
|
561
737
|
numSlicesToPropagate * spacingInNormal
|
|
562
738
|
);
|
|
563
739
|
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
for (let i = 0; i < imageIds.length; i++) {
|
|
569
|
-
const imageId = imageIds[i];
|
|
740
|
+
const endCoord = this._getCoordinateForViewplaneNormal(
|
|
741
|
+
endPos,
|
|
742
|
+
viewPlaneNormal
|
|
743
|
+
);
|
|
570
744
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
imageId
|
|
574
|
-
);
|
|
745
|
+
return endCoord;
|
|
746
|
+
}
|
|
575
747
|
|
|
576
|
-
|
|
577
|
-
|
|
748
|
+
_getIndexOfCoordinatesForViewplaneNormal(
|
|
749
|
+
viewPlaneNormal: Types.Point3
|
|
750
|
+
): number {
|
|
751
|
+
const viewplaneNormalAbs = [
|
|
752
|
+
Math.abs(viewPlaneNormal[0]),
|
|
753
|
+
Math.abs(viewPlaneNormal[1]),
|
|
754
|
+
Math.abs(viewPlaneNormal[2]),
|
|
755
|
+
];
|
|
756
|
+
const indexOfDirection = viewplaneNormalAbs.indexOf(
|
|
757
|
+
Math.max(...viewplaneNormalAbs)
|
|
758
|
+
);
|
|
578
759
|
|
|
579
|
-
|
|
760
|
+
return indexOfDirection;
|
|
761
|
+
}
|
|
580
762
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
763
|
+
_getCoordinateForViewplaneNormal(
|
|
764
|
+
pos: vec3 | number,
|
|
765
|
+
viewPlaneNormal: Types.Point3
|
|
766
|
+
): number | undefined {
|
|
767
|
+
const indexOfDirection =
|
|
768
|
+
this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal);
|
|
585
769
|
|
|
586
|
-
return
|
|
770
|
+
return pos[indexOfDirection];
|
|
587
771
|
}
|
|
588
772
|
}
|
|
589
773
|
|
|
774
|
+
/**
|
|
775
|
+
* _getTextLines - Returns the Area, mean and std deviation of the area of the
|
|
776
|
+
* target volume enclosed by the rectangle.
|
|
777
|
+
*
|
|
778
|
+
* @param data - The annotation tool-specific data.
|
|
779
|
+
* @param targetId - The volumeId of the volume to display the stats for.
|
|
780
|
+
*/
|
|
781
|
+
function defaultGetTextLines(data): string[] {
|
|
782
|
+
const cachedVolumeStats = data.cachedStats.statistics;
|
|
783
|
+
|
|
784
|
+
const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats;
|
|
785
|
+
|
|
786
|
+
if (mean === undefined) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const textLines: string[] = [];
|
|
791
|
+
|
|
792
|
+
textLines.push(`Area: ${roundNumber(area)} ${areaUnit}`);
|
|
793
|
+
textLines.push(`Mean: ${roundNumber(mean)} ${modalityUnit}`);
|
|
794
|
+
textLines.push(`Max: ${roundNumber(max)} ${modalityUnit}`);
|
|
795
|
+
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${modalityUnit}`);
|
|
796
|
+
|
|
797
|
+
return textLines;
|
|
798
|
+
}
|
|
799
|
+
|
|
590
800
|
RectangleROIStartEndThresholdTool.toolName = 'RectangleROIStartEndThreshold';
|
|
591
801
|
export default RectangleROIStartEndThresholdTool;
|
|
@@ -240,16 +240,27 @@ export interface RectangleROIStartEndThresholdAnnotation extends Annotation {
|
|
|
240
240
|
};
|
|
241
241
|
data: {
|
|
242
242
|
label: string;
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
startCoordinate: number;
|
|
244
|
+
endCoordinate: number;
|
|
245
245
|
cachedStats: {
|
|
246
246
|
pointsInVolume: Types.Point3[];
|
|
247
247
|
projectionPoints: Types.Point3[][]; // first slice p1, p2, p3, p4; second slice p1, p2, p3, p4 ...
|
|
248
248
|
projectionPointsImageIds: string[];
|
|
249
|
+
statistics?: ROICachedStats | any[];
|
|
249
250
|
};
|
|
250
251
|
handles: {
|
|
251
252
|
points: Types.Point3[];
|
|
252
253
|
activeHandleIndex: number | null;
|
|
254
|
+
textBox: {
|
|
255
|
+
hasMoved: boolean;
|
|
256
|
+
worldPosition: Types.Point3;
|
|
257
|
+
worldBoundingBox: {
|
|
258
|
+
topLeft: Types.Point3;
|
|
259
|
+
topRight: Types.Point3;
|
|
260
|
+
bottomLeft: Types.Point3;
|
|
261
|
+
bottomRight: Types.Point3;
|
|
262
|
+
};
|
|
263
|
+
};
|
|
253
264
|
};
|
|
254
265
|
};
|
|
255
266
|
}
|
|
@@ -270,15 +281,26 @@ export interface CircleROIStartEndThresholdAnnotation extends Annotation {
|
|
|
270
281
|
};
|
|
271
282
|
data: {
|
|
272
283
|
label: string;
|
|
273
|
-
|
|
274
|
-
|
|
284
|
+
startCoordinate: number;
|
|
285
|
+
endCoordinate: number;
|
|
275
286
|
cachedStats?: {
|
|
276
287
|
pointsInVolume: Types.Point3[];
|
|
277
288
|
projectionPoints: Types.Point3[][];
|
|
289
|
+
statistics?: ROICachedStats | any[];
|
|
278
290
|
};
|
|
279
291
|
handles: {
|
|
280
292
|
points: [Types.Point3, Types.Point3]; // [center, end]
|
|
281
293
|
activeHandleIndex: number | null;
|
|
294
|
+
textBox?: {
|
|
295
|
+
hasMoved: boolean;
|
|
296
|
+
worldPosition: Types.Point3;
|
|
297
|
+
worldBoundingBox: {
|
|
298
|
+
topLeft: Types.Point3;
|
|
299
|
+
topRight: Types.Point3;
|
|
300
|
+
bottomLeft: Types.Point3;
|
|
301
|
+
bottomRight: Types.Point3;
|
|
302
|
+
};
|
|
303
|
+
};
|
|
282
304
|
};
|
|
283
305
|
};
|
|
284
306
|
}
|