@cornerstonejs/tools 1.71.7 → 1.72.1
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/index.d.ts +2 -2
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tools/WindowLevelRegionTool.d.ts +26 -0
- package/dist/cjs/tools/WindowLevelRegionTool.js +228 -0
- package/dist/cjs/tools/WindowLevelRegionTool.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/utilities/voi/index.d.ts +2 -1
- package/dist/cjs/utilities/voi/index.js +3 -1
- package/dist/cjs/utilities/voi/index.js.map +1 -1
- package/dist/cjs/utilities/voi/windowlevel/calculateMinMaxMean.d.ts +6 -0
- package/dist/cjs/utilities/voi/windowlevel/calculateMinMaxMean.js +29 -0
- package/dist/cjs/utilities/voi/windowlevel/calculateMinMaxMean.js.map +1 -0
- package/dist/cjs/utilities/voi/windowlevel/extractWindowLevelRegionToolData.d.ts +11 -0
- package/dist/cjs/utilities/voi/windowlevel/extractWindowLevelRegionToolData.js +52 -0
- package/dist/cjs/utilities/voi/windowlevel/extractWindowLevelRegionToolData.js.map +1 -0
- package/dist/cjs/utilities/voi/windowlevel/getLuminanceFromRegion.d.ts +2 -0
- package/dist/cjs/utilities/voi/windowlevel/getLuminanceFromRegion.js +31 -0
- package/dist/cjs/utilities/voi/windowlevel/getLuminanceFromRegion.js.map +1 -0
- package/dist/cjs/utilities/voi/windowlevel/index.d.ts +4 -0
- package/dist/cjs/utilities/voi/windowlevel/index.js +10 -0
- package/dist/cjs/utilities/voi/windowlevel/index.js.map +1 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tools/WindowLevelRegionTool.js +223 -0
- package/dist/esm/tools/WindowLevelRegionTool.js.map +1 -0
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/index.js.map +1 -1
- package/dist/esm/utilities/voi/index.js +2 -1
- package/dist/esm/utilities/voi/index.js.map +1 -1
- package/dist/esm/utilities/voi/windowlevel/calculateMinMaxMean.js +26 -0
- package/dist/esm/utilities/voi/windowlevel/calculateMinMaxMean.js.map +1 -0
- package/dist/esm/utilities/voi/windowlevel/extractWindowLevelRegionToolData.js +49 -0
- package/dist/esm/utilities/voi/windowlevel/extractWindowLevelRegionToolData.js.map +1 -0
- package/dist/esm/utilities/voi/windowlevel/getLuminanceFromRegion.js +28 -0
- package/dist/esm/utilities/voi/windowlevel/getLuminanceFromRegion.js.map +1 -0
- package/dist/esm/utilities/voi/windowlevel/index.js +5 -0
- package/dist/esm/utilities/voi/windowlevel/index.js.map +1 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/tools/WindowLevelRegionTool.d.ts +27 -0
- package/dist/types/tools/WindowLevelRegionTool.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/utilities/voi/index.d.ts +2 -1
- package/dist/types/utilities/voi/index.d.ts.map +1 -1
- package/dist/types/utilities/voi/windowlevel/calculateMinMaxMean.d.ts +7 -0
- package/dist/types/utilities/voi/windowlevel/calculateMinMaxMean.d.ts.map +1 -0
- package/dist/types/utilities/voi/windowlevel/extractWindowLevelRegionToolData.d.ts +12 -0
- package/dist/types/utilities/voi/windowlevel/extractWindowLevelRegionToolData.d.ts.map +1 -0
- package/dist/types/utilities/voi/windowlevel/getLuminanceFromRegion.d.ts +3 -0
- package/dist/types/utilities/voi/windowlevel/getLuminanceFromRegion.d.ts.map +1 -0
- package/dist/types/utilities/voi/windowlevel/index.d.ts +5 -0
- package/dist/types/utilities/voi/windowlevel/index.d.ts.map +1 -0
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/tools/WindowLevelRegionTool.ts +399 -0
- package/src/tools/index.ts +2 -0
- package/src/utilities/voi/index.ts +2 -1
- package/src/utilities/voi/windowlevel/calculateMinMaxMean.ts +30 -0
- package/src/utilities/voi/windowlevel/extractWindowLevelRegionToolData.ts +63 -0
- package/src/utilities/voi/windowlevel/getLuminanceFromRegion.ts +43 -0
- package/src/utilities/voi/windowlevel/index.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.72.1",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@cornerstonejs/core": "^1.
|
|
32
|
+
"@cornerstonejs/core": "^1.72.1",
|
|
33
33
|
"@icr/polyseg-wasm": "0.4.0",
|
|
34
34
|
"@types/offscreencanvas": "2019.7.3",
|
|
35
35
|
"comlink": "^4.4.1",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"type": "individual",
|
|
60
60
|
"url": "https://ohif.org/donate"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "8ae23a54d44a169da137795244394e79389437f6"
|
|
63
63
|
}
|
package/src/index.ts
CHANGED
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
EraserTool,
|
|
74
74
|
SculptorTool,
|
|
75
75
|
SegmentSelectTool,
|
|
76
|
+
WindowLevelRegionTool,
|
|
76
77
|
} from './tools';
|
|
77
78
|
|
|
78
79
|
import VideoRedactionTool from './tools/annotation/VideoRedactionTool';
|
|
@@ -96,6 +97,7 @@ export {
|
|
|
96
97
|
TrackballRotateTool,
|
|
97
98
|
DragProbeTool,
|
|
98
99
|
WindowLevelTool,
|
|
100
|
+
WindowLevelRegionTool,
|
|
99
101
|
ZoomTool,
|
|
100
102
|
StackScrollTool,
|
|
101
103
|
PlanarRotateTool,
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { AnnotationTool } from './base';
|
|
2
|
+
|
|
3
|
+
import { getEnabledElement, utilities } from '@cornerstonejs/core';
|
|
4
|
+
import type { Types } from '@cornerstonejs/core';
|
|
5
|
+
import {
|
|
6
|
+
addAnnotation,
|
|
7
|
+
getAnnotations,
|
|
8
|
+
removeAnnotation,
|
|
9
|
+
} from '../stateManagement';
|
|
10
|
+
import { triggerAnnotationCompleted } from '../stateManagement/annotation/helpers/state';
|
|
11
|
+
import { drawRect as drawRectSvg } from '../drawingSvg';
|
|
12
|
+
import { state } from '../store';
|
|
13
|
+
import { Events } from '../enums';
|
|
14
|
+
import { getViewportIdsWithToolToRender } from '../utilities/viewportFilters';
|
|
15
|
+
import {
|
|
16
|
+
resetElementCursor,
|
|
17
|
+
hideElementCursor,
|
|
18
|
+
} from '../cursors/elementCursor';
|
|
19
|
+
import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
EventTypes,
|
|
23
|
+
ToolProps,
|
|
24
|
+
PublicToolProps,
|
|
25
|
+
SVGDrawingHelper,
|
|
26
|
+
} from '../types';
|
|
27
|
+
import { RectangleROIAnnotation } from '../types/ToolSpecificAnnotationTypes';
|
|
28
|
+
import { StyleSpecifier } from '../types/AnnotationStyle';
|
|
29
|
+
|
|
30
|
+
import { windowLevel } from '../utilities/voi';
|
|
31
|
+
|
|
32
|
+
import { clip } from '../utilities';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* WindowLevelRegion tool manipulates the windowLevel applied to a viewport. It
|
|
36
|
+
* provides a way to set the windowCenter and windowWidth of a viewport
|
|
37
|
+
* by dragging mouse over the image to draw a rectangle region which is used to calculate
|
|
38
|
+
* the windowCenter and windowWidth based on the ROI
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
class WindowLevelRegionTool extends AnnotationTool {
|
|
43
|
+
static toolName;
|
|
44
|
+
|
|
45
|
+
editData: {
|
|
46
|
+
annotation: any;
|
|
47
|
+
viewportIdsToRender: string[];
|
|
48
|
+
} | null;
|
|
49
|
+
isDrawing: boolean;
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
toolProps: PublicToolProps = {},
|
|
53
|
+
defaultToolProps: ToolProps = {
|
|
54
|
+
supportedInteractionTypes: ['Mouse', 'Touch'],
|
|
55
|
+
configuration: {
|
|
56
|
+
// The minimum window width to be applied to the viewport regardless of the calculated value
|
|
57
|
+
minWindowWidth: 10,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
) {
|
|
61
|
+
super(toolProps, defaultToolProps);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Based on the current position of the mouse and the current imageId to create
|
|
66
|
+
* a RectangleROI Annotation and stores it in the annotationManager
|
|
67
|
+
*
|
|
68
|
+
* @param evt - EventTypes.NormalizedMouseEventType
|
|
69
|
+
* @returns The annotation object.
|
|
70
|
+
*
|
|
71
|
+
*/
|
|
72
|
+
addNewAnnotation = (evt: EventTypes.InteractionEventType): any => {
|
|
73
|
+
const eventDetail = evt.detail;
|
|
74
|
+
const { currentPoints, element } = eventDetail;
|
|
75
|
+
const worldPos = currentPoints.world;
|
|
76
|
+
|
|
77
|
+
const enabledElement = getEnabledElement(element);
|
|
78
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
79
|
+
|
|
80
|
+
this.isDrawing = true;
|
|
81
|
+
|
|
82
|
+
const camera = viewport.getCamera();
|
|
83
|
+
const { viewPlaneNormal, viewUp } = camera;
|
|
84
|
+
|
|
85
|
+
const referencedImageId = this.getReferencedImageId(
|
|
86
|
+
viewport,
|
|
87
|
+
worldPos,
|
|
88
|
+
viewPlaneNormal,
|
|
89
|
+
viewUp
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
|
93
|
+
|
|
94
|
+
const annotation = {
|
|
95
|
+
invalidated: true,
|
|
96
|
+
highlighted: true,
|
|
97
|
+
metadata: {
|
|
98
|
+
toolName: this.getToolName(),
|
|
99
|
+
viewPlaneNormal: <Types.Point3>[...viewPlaneNormal],
|
|
100
|
+
viewUp: <Types.Point3>[...viewUp],
|
|
101
|
+
FrameOfReferenceUID,
|
|
102
|
+
referencedImageId,
|
|
103
|
+
},
|
|
104
|
+
data: {
|
|
105
|
+
handles: {
|
|
106
|
+
points: [
|
|
107
|
+
<Types.Point3>[...worldPos],
|
|
108
|
+
<Types.Point3>[...worldPos],
|
|
109
|
+
<Types.Point3>[...worldPos],
|
|
110
|
+
<Types.Point3>[...worldPos],
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
cachedStats: {},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
addAnnotation(annotation, element);
|
|
118
|
+
|
|
119
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
120
|
+
element,
|
|
121
|
+
this.getToolName()
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
this.editData = {
|
|
125
|
+
annotation,
|
|
126
|
+
viewportIdsToRender,
|
|
127
|
+
};
|
|
128
|
+
this._activateDraw(element);
|
|
129
|
+
|
|
130
|
+
hideElementCursor(element);
|
|
131
|
+
|
|
132
|
+
evt.preventDefault();
|
|
133
|
+
|
|
134
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
135
|
+
|
|
136
|
+
return annotation;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
_endCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
140
|
+
const eventDetail = evt.detail;
|
|
141
|
+
const { element } = eventDetail;
|
|
142
|
+
|
|
143
|
+
const { annotation, viewportIdsToRender } = this.editData;
|
|
144
|
+
|
|
145
|
+
this._deactivateDraw(element);
|
|
146
|
+
|
|
147
|
+
resetElementCursor(element);
|
|
148
|
+
|
|
149
|
+
const { renderingEngine } = getEnabledElement(element);
|
|
150
|
+
|
|
151
|
+
this.editData = null;
|
|
152
|
+
this.isDrawing = false;
|
|
153
|
+
|
|
154
|
+
removeAnnotation(annotation.annotationUID);
|
|
155
|
+
|
|
156
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
157
|
+
|
|
158
|
+
triggerAnnotationCompleted(annotation);
|
|
159
|
+
|
|
160
|
+
this.applyWindowLevelRegion(annotation, element);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
_dragCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
164
|
+
this.isDrawing = true;
|
|
165
|
+
|
|
166
|
+
const eventDetail = evt.detail;
|
|
167
|
+
const { element } = eventDetail;
|
|
168
|
+
|
|
169
|
+
const { annotation, viewportIdsToRender } = this.editData;
|
|
170
|
+
const { data } = annotation;
|
|
171
|
+
const { currentPoints } = eventDetail;
|
|
172
|
+
const enabledElement = getEnabledElement(element);
|
|
173
|
+
const { worldToCanvas, canvasToWorld } = enabledElement.viewport;
|
|
174
|
+
const worldPos = currentPoints.world;
|
|
175
|
+
|
|
176
|
+
const { points } = data.handles;
|
|
177
|
+
const DEFAULT_HANDLE_INDEX = 3;
|
|
178
|
+
points[DEFAULT_HANDLE_INDEX] = [...worldPos];
|
|
179
|
+
|
|
180
|
+
const bottomLeftCanvas = worldToCanvas(points[0]);
|
|
181
|
+
const topRightCanvas = worldToCanvas(points[3]);
|
|
182
|
+
|
|
183
|
+
const bottomRightCanvas = <Types.Point2>[
|
|
184
|
+
topRightCanvas[0],
|
|
185
|
+
bottomLeftCanvas[1],
|
|
186
|
+
];
|
|
187
|
+
const topLeftCanvas = <Types.Point2>[
|
|
188
|
+
bottomLeftCanvas[0],
|
|
189
|
+
topRightCanvas[1],
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const bottomRightWorld = canvasToWorld(bottomRightCanvas);
|
|
193
|
+
const topLeftWorld = canvasToWorld(topLeftCanvas);
|
|
194
|
+
|
|
195
|
+
points[1] = bottomRightWorld;
|
|
196
|
+
points[2] = topLeftWorld;
|
|
197
|
+
|
|
198
|
+
annotation.invalidated = true;
|
|
199
|
+
|
|
200
|
+
const { renderingEngine } = enabledElement;
|
|
201
|
+
|
|
202
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Add event handlers for the modify event loop, and prevent default event prapogation.
|
|
207
|
+
*/
|
|
208
|
+
_activateDraw = (element) => {
|
|
209
|
+
state.isInteractingWithTool = true;
|
|
210
|
+
|
|
211
|
+
element.addEventListener(Events.MOUSE_UP, this._endCallback);
|
|
212
|
+
element.addEventListener(Events.MOUSE_DRAG, this._dragCallback);
|
|
213
|
+
element.addEventListener(Events.MOUSE_MOVE, this._dragCallback);
|
|
214
|
+
element.addEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
215
|
+
|
|
216
|
+
element.addEventListener(Events.TOUCH_END, this._endCallback);
|
|
217
|
+
element.addEventListener(Events.TOUCH_DRAG, this._dragCallback);
|
|
218
|
+
element.addEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Add event handlers for the modify event loop, and prevent default event prapogation.
|
|
223
|
+
*/
|
|
224
|
+
_deactivateDraw = (element) => {
|
|
225
|
+
state.isInteractingWithTool = false;
|
|
226
|
+
|
|
227
|
+
element.removeEventListener(Events.MOUSE_UP, this._endCallback);
|
|
228
|
+
element.removeEventListener(Events.MOUSE_DRAG, this._dragCallback);
|
|
229
|
+
element.removeEventListener(Events.MOUSE_MOVE, this._dragCallback);
|
|
230
|
+
element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
|
|
231
|
+
|
|
232
|
+
element.removeEventListener(Events.TOUCH_END, this._endCallback);
|
|
233
|
+
element.removeEventListener(Events.TOUCH_DRAG, this._dragCallback);
|
|
234
|
+
element.removeEventListener(Events.TOUCH_TAP, this._endCallback);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* it is used to draw the rectangleROI annotation in each
|
|
239
|
+
* request animation frame. It calculates the updated cached statistics if
|
|
240
|
+
* data is invalidated and cache it.
|
|
241
|
+
*
|
|
242
|
+
* @param enabledElement - The Cornerstone's enabledElement.
|
|
243
|
+
* @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
|
|
244
|
+
*/
|
|
245
|
+
renderAnnotation = (
|
|
246
|
+
enabledElement: Types.IEnabledElement,
|
|
247
|
+
svgDrawingHelper: SVGDrawingHelper
|
|
248
|
+
): boolean => {
|
|
249
|
+
let renderStatus = false;
|
|
250
|
+
const { viewport } = enabledElement;
|
|
251
|
+
const { element } = viewport;
|
|
252
|
+
|
|
253
|
+
let annotations = getAnnotations(this.getToolName(), element);
|
|
254
|
+
|
|
255
|
+
if (!annotations?.length) {
|
|
256
|
+
return renderStatus;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
annotations = this.filterInteractableAnnotationsForElement(
|
|
260
|
+
element,
|
|
261
|
+
annotations
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (!annotations?.length) {
|
|
265
|
+
return renderStatus;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const styleSpecifier: StyleSpecifier = {
|
|
269
|
+
toolGroupId: this.toolGroupId,
|
|
270
|
+
toolName: this.getToolName(),
|
|
271
|
+
viewportId: enabledElement.viewport.id,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
for (let i = 0; i < annotations.length; i++) {
|
|
275
|
+
const annotation = annotations[i] as RectangleROIAnnotation;
|
|
276
|
+
const { annotationUID, data } = annotation;
|
|
277
|
+
const { points } = data.handles;
|
|
278
|
+
|
|
279
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
280
|
+
|
|
281
|
+
styleSpecifier.annotationUID = annotationUID;
|
|
282
|
+
|
|
283
|
+
const { color, lineWidth, lineDash } = this.getAnnotationStyle({
|
|
284
|
+
annotation,
|
|
285
|
+
styleSpecifier,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// If rendering engine has been destroyed while rendering
|
|
289
|
+
if (!viewport.getRenderingEngine()) {
|
|
290
|
+
console.warn('Rendering Engine has been destroyed');
|
|
291
|
+
return renderStatus;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const dataId = `${annotationUID}-rect`;
|
|
295
|
+
const rectangleUID = '0';
|
|
296
|
+
drawRectSvg(
|
|
297
|
+
svgDrawingHelper,
|
|
298
|
+
annotationUID,
|
|
299
|
+
rectangleUID,
|
|
300
|
+
canvasCoordinates[0],
|
|
301
|
+
canvasCoordinates[3],
|
|
302
|
+
{
|
|
303
|
+
color,
|
|
304
|
+
lineDash,
|
|
305
|
+
lineWidth,
|
|
306
|
+
},
|
|
307
|
+
dataId
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
renderStatus = true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return renderStatus;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
applyWindowLevelRegion = (annotation, element): void => {
|
|
317
|
+
const enabledElement = getEnabledElement(element);
|
|
318
|
+
const { viewport } = enabledElement;
|
|
319
|
+
const imageData = windowLevel.extractWindowLevelRegionToolData(viewport);
|
|
320
|
+
const { data } = annotation;
|
|
321
|
+
const { points } = data.handles;
|
|
322
|
+
|
|
323
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
324
|
+
const startCanvas = canvasCoordinates[0];
|
|
325
|
+
const endCanvas = canvasCoordinates[3];
|
|
326
|
+
|
|
327
|
+
let left = Math.min(startCanvas[0], endCanvas[0]);
|
|
328
|
+
let top = Math.min(startCanvas[1], endCanvas[1]);
|
|
329
|
+
let width = Math.abs(startCanvas[0] - endCanvas[0]);
|
|
330
|
+
let height = Math.abs(startCanvas[1] - endCanvas[1]);
|
|
331
|
+
|
|
332
|
+
left = clip(left, 0, imageData.width);
|
|
333
|
+
top = clip(top, 0, imageData.height);
|
|
334
|
+
width = Math.floor(Math.min(width, Math.abs(imageData.width - left)));
|
|
335
|
+
height = Math.floor(Math.min(height, Math.abs(imageData.height - top)));
|
|
336
|
+
|
|
337
|
+
// Get the pixel data in the rectangular region
|
|
338
|
+
const pixelLuminanceData = windowLevel.getLuminanceFromRegion(
|
|
339
|
+
imageData,
|
|
340
|
+
Math.round(left),
|
|
341
|
+
Math.round(top),
|
|
342
|
+
width,
|
|
343
|
+
height
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Calculate the minimum and maximum pixel values
|
|
347
|
+
const minMaxMean = windowLevel.calculateMinMaxMean(
|
|
348
|
+
pixelLuminanceData,
|
|
349
|
+
imageData.minPixelValue,
|
|
350
|
+
imageData.maxPixelValue
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Adjust the viewport window width and center based on the calculated values
|
|
354
|
+
if (this.configuration.minWindowWidth === undefined) {
|
|
355
|
+
this.configuration.minWindowWidth = 10;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const windowWidth = Math.max(
|
|
359
|
+
Math.abs(minMaxMean.max - minMaxMean.min),
|
|
360
|
+
this.configuration.minWindowWidth
|
|
361
|
+
);
|
|
362
|
+
const windowCenter = minMaxMean.mean;
|
|
363
|
+
|
|
364
|
+
const voiRange = utilities.windowLevel.toLowHighRange(
|
|
365
|
+
windowWidth,
|
|
366
|
+
windowCenter
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
viewport.setProperties({ voiRange });
|
|
370
|
+
viewport.render();
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
cancel = (): void => {
|
|
374
|
+
return null;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
isPointNearTool = () => {
|
|
378
|
+
return null;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
toolSelectedCallback = (): void => {
|
|
382
|
+
return null;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
handleSelectedCallback = (): void => {
|
|
386
|
+
return null;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
_activateModify = (): void => {
|
|
390
|
+
return null;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
_deactivateModify = (): void => {
|
|
394
|
+
return null;
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
WindowLevelRegionTool.toolName = 'WindowLevelRegion';
|
|
399
|
+
export default WindowLevelRegionTool;
|
package/src/tools/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { BaseTool, AnnotationTool, AnnotationDisplayTool } from './base';
|
|
|
2
2
|
import PanTool from './PanTool';
|
|
3
3
|
import TrackballRotateTool from './TrackballRotateTool';
|
|
4
4
|
import WindowLevelTool from './WindowLevelTool';
|
|
5
|
+
import WindowLevelRegionTool from './WindowLevelRegionTool';
|
|
5
6
|
import StackScrollTool from './StackScrollTool';
|
|
6
7
|
import PlanarRotateTool from './PlanarRotateTool';
|
|
7
8
|
import StackScrollMouseWheelTool from './StackScrollToolMouseWheelTool';
|
|
@@ -65,6 +66,7 @@ export {
|
|
|
65
66
|
TrackballRotateTool,
|
|
66
67
|
DragProbeTool,
|
|
67
68
|
WindowLevelTool,
|
|
69
|
+
WindowLevelRegionTool,
|
|
68
70
|
StackScrollTool,
|
|
69
71
|
PlanarRotateTool,
|
|
70
72
|
StackScrollMouseWheelTool,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function calculateMinMaxMean(pixelLuminance, globalMin, globalMax) {
|
|
2
|
+
const numPixels = pixelLuminance.length;
|
|
3
|
+
let min = globalMax;
|
|
4
|
+
let max = globalMin;
|
|
5
|
+
let sum = 0;
|
|
6
|
+
|
|
7
|
+
if (numPixels < 2) {
|
|
8
|
+
return {
|
|
9
|
+
min,
|
|
10
|
+
max,
|
|
11
|
+
mean: (globalMin + globalMax) / 2,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (let index = 0; index < numPixels; index++) {
|
|
16
|
+
const spv = pixelLuminance[index];
|
|
17
|
+
|
|
18
|
+
min = Math.min(min, spv);
|
|
19
|
+
max = Math.max(max, spv);
|
|
20
|
+
sum += spv;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
min,
|
|
25
|
+
max,
|
|
26
|
+
mean: sum / numPixels,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { calculateMinMaxMean };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VolumeViewport,
|
|
3
|
+
utilities as csUtils,
|
|
4
|
+
cache,
|
|
5
|
+
StackViewport,
|
|
6
|
+
} from '@cornerstonejs/core';
|
|
7
|
+
|
|
8
|
+
function extractWindowLevelRegionToolData(viewport) {
|
|
9
|
+
if (viewport instanceof VolumeViewport) {
|
|
10
|
+
return extractImageDataVolume(viewport);
|
|
11
|
+
}
|
|
12
|
+
if (viewport instanceof StackViewport) {
|
|
13
|
+
return extractImageDataStack(viewport);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
throw new Error('Viewport not supported');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function extractImageDataVolume(viewport) {
|
|
20
|
+
const { scalarData, width, height } =
|
|
21
|
+
csUtils.getCurrentVolumeViewportSlice(viewport);
|
|
22
|
+
const { min: minPixelValue, max: maxPixelValue } =
|
|
23
|
+
csUtils.getMinMax(scalarData);
|
|
24
|
+
const volumeId = viewport.getVolumeId();
|
|
25
|
+
const volume = cache.getVolume(volumeId);
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
const { metadata, cornerstoneImageMetaData } = volume;
|
|
28
|
+
const { Rows: rows, Columns: columns } = metadata;
|
|
29
|
+
const { color } = cornerstoneImageMetaData;
|
|
30
|
+
return {
|
|
31
|
+
scalarData,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
minPixelValue,
|
|
35
|
+
maxPixelValue,
|
|
36
|
+
rows,
|
|
37
|
+
columns,
|
|
38
|
+
color,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extractImageDataStack(viewport) {
|
|
43
|
+
const imageData = viewport.getImageData();
|
|
44
|
+
const { scalarData } = imageData;
|
|
45
|
+
const { min: minPixelValue, max: maxPixelValue } =
|
|
46
|
+
csUtils.getMinMax(scalarData);
|
|
47
|
+
const width = imageData.dimensions[0];
|
|
48
|
+
const height = imageData.dimensions[1];
|
|
49
|
+
const { rows, columns, color } = viewport.getCornerstoneImage();
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
scalarData,
|
|
53
|
+
width,
|
|
54
|
+
height,
|
|
55
|
+
minPixelValue,
|
|
56
|
+
maxPixelValue,
|
|
57
|
+
rows,
|
|
58
|
+
columns,
|
|
59
|
+
color,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { extractWindowLevelRegionToolData };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the luminance values from a specified region of an image.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} imageData - The image data object containing pixel information.
|
|
5
|
+
* @param {Uint8Array} imageData.scalarData - The pixel data array.
|
|
6
|
+
* @param {boolean} imageData.color - A flag indicating if the image is in color (true) or grayscale (false).
|
|
7
|
+
* @param {number} imageData.columns - The number of columns (width) in the image.
|
|
8
|
+
* @param {number} x - The x-coordinate of the top-left corner of the region.
|
|
9
|
+
* @param {number} y - The y-coordinate of the top-left corner of the region.
|
|
10
|
+
* @param {number} width - The width of the region.
|
|
11
|
+
* @param {number} height - The height of the region.
|
|
12
|
+
* @returns {number[]} An array containing the luminance values of the specified region.
|
|
13
|
+
*/
|
|
14
|
+
function getLuminanceFromRegion(imageData, x, y, width, height) {
|
|
15
|
+
const luminance = [];
|
|
16
|
+
let index = 0;
|
|
17
|
+
const pixelData = imageData.scalarData;
|
|
18
|
+
let spIndex, row, column;
|
|
19
|
+
|
|
20
|
+
if (imageData.color) {
|
|
21
|
+
for (row = 0; row < height; row++) {
|
|
22
|
+
for (column = 0; column < width; column++) {
|
|
23
|
+
spIndex = ((row + y) * imageData.columns + (column + x)) * 4;
|
|
24
|
+
const red = pixelData[spIndex];
|
|
25
|
+
const green = pixelData[spIndex + 1];
|
|
26
|
+
const blue = pixelData[spIndex + 2];
|
|
27
|
+
|
|
28
|
+
luminance[index++] = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
for (row = 0; row < height; row++) {
|
|
33
|
+
for (column = 0; column < width; column++) {
|
|
34
|
+
spIndex = (row + y) * imageData.columns + (column + x);
|
|
35
|
+
luminance[index++] = pixelData[spIndex];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return luminance;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { getLuminanceFromRegion };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getLuminanceFromRegion } from './getLuminanceFromRegion';
|
|
2
|
+
import { calculateMinMaxMean } from './calculateMinMaxMean';
|
|
3
|
+
import { extractWindowLevelRegionToolData } from './extractWindowLevelRegionToolData';
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
getLuminanceFromRegion,
|
|
7
|
+
calculateMinMaxMean,
|
|
8
|
+
extractWindowLevelRegionToolData,
|
|
9
|
+
};
|