@cornerstonejs/tools 4.18.4 → 4.19.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.
@@ -0,0 +1,391 @@
1
+ import macro from '@kitware/vtk.js/macros';
2
+ import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
3
+ import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
4
+ import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
5
+ import vtkTexture from '@kitware/vtk.js/Rendering/Core/Texture';
6
+ import ImageHelper from '@kitware/vtk.js/Common/Core/ImageHelper';
7
+ import vtkRhombicuboctahedronSource from '../RhombicuboctahedronSource';
8
+ function createMainFacesMesh(scale, faceColors, faceTextures) {
9
+ const source = vtkRhombicuboctahedronSource.newInstance({
10
+ generate3DTextureCoordinates: false,
11
+ generateMainFaces: true,
12
+ generateEdgeFaces: false,
13
+ generateCornerFaces: false,
14
+ scale: scale,
15
+ });
16
+ source.update();
17
+ const data = source.getOutputData();
18
+ if (data) {
19
+ const tcoords = [];
20
+ for (let faceIdx = 0; faceIdx < 6; faceIdx++) {
21
+ const col = faceIdx % 3;
22
+ const row = Math.floor(faceIdx / 3);
23
+ const u0 = col / 3.0;
24
+ const u1 = (col + 1) / 3.0;
25
+ const v0 = row / 2.0;
26
+ const v1 = (row + 1) / 2.0;
27
+ tcoords.push(u0, v0);
28
+ tcoords.push(u1, v0);
29
+ tcoords.push(u1, v1);
30
+ tcoords.push(u0, v1);
31
+ }
32
+ const tcoordsArray = vtkDataArray.newInstance({
33
+ name: 'TextureCoordinates',
34
+ values: new Float32Array(tcoords),
35
+ numberOfComponents: 2,
36
+ });
37
+ data.getPointData().setTCoords(tcoordsArray);
38
+ data.modified();
39
+ }
40
+ return data;
41
+ }
42
+ function createEdgeFacesMesh(scale, color) {
43
+ const source = vtkRhombicuboctahedronSource.newInstance({
44
+ generate3DTextureCoordinates: false,
45
+ generateMainFaces: false,
46
+ generateEdgeFaces: true,
47
+ generateCornerFaces: false,
48
+ scale: scale,
49
+ });
50
+ source.update();
51
+ const data = source.getOutputData();
52
+ if (data) {
53
+ const colors = [];
54
+ const numCells = data.getNumberOfCells();
55
+ for (let i = 0; i < numCells; i++) {
56
+ colors.push(color[0], color[1], color[2], 255);
57
+ }
58
+ const colorsArray = vtkDataArray.newInstance({
59
+ name: 'Colors',
60
+ values: new Uint8Array(colors),
61
+ numberOfComponents: 4,
62
+ });
63
+ data.getCellData().setScalars(colorsArray);
64
+ data.modified();
65
+ }
66
+ return data;
67
+ }
68
+ function createCornerFacesMesh(scale, color) {
69
+ const source = vtkRhombicuboctahedronSource.newInstance({
70
+ generate3DTextureCoordinates: false,
71
+ generateMainFaces: false,
72
+ generateEdgeFaces: false,
73
+ generateCornerFaces: true,
74
+ scale: scale,
75
+ });
76
+ source.update();
77
+ const data = source.getOutputData();
78
+ if (data) {
79
+ const colors = [];
80
+ const numCells = data.getNumberOfCells();
81
+ for (let i = 0; i < numCells; i++) {
82
+ colors.push(color[0], color[1], color[2], 255);
83
+ }
84
+ const colorsArray = vtkDataArray.newInstance({
85
+ name: 'Colors',
86
+ values: new Uint8Array(colors),
87
+ numberOfComponents: 4,
88
+ });
89
+ data.getCellData().setScalars(colorsArray);
90
+ data.modified();
91
+ }
92
+ return data;
93
+ }
94
+ function createTextureAtlas(faceTextureData) {
95
+ const faceSize = 256;
96
+ const canvas = document.createElement('canvas');
97
+ canvas.width = faceSize * 3;
98
+ canvas.height = faceSize * 2;
99
+ const ctx = canvas.getContext('2d');
100
+ faceTextureData.forEach((data, index) => {
101
+ const col = index % 3;
102
+ const row = Math.floor(index / 3);
103
+ const x = col * faceSize;
104
+ const y = row * faceSize;
105
+ ctx.fillStyle = `rgb(${data.faceColor[0]}, ${data.faceColor[1]}, ${data.faceColor[2]})`;
106
+ ctx.fillRect(x, y, faceSize, faceSize);
107
+ if (data.text) {
108
+ ctx.save();
109
+ ctx.translate(x + faceSize / 2, y + faceSize / 2);
110
+ if (data.flipVertical) {
111
+ ctx.scale(1, -1);
112
+ }
113
+ if (data.flipHorizontal) {
114
+ ctx.scale(-1, 1);
115
+ }
116
+ ctx.rotate(((data.rotation || 0) * Math.PI) / 180);
117
+ ctx.fillStyle = `rgb(${data.textColor[0]}, ${data.textColor[1]}, ${data.textColor[2]})`;
118
+ ctx.font = 'bold 180px Arial';
119
+ ctx.textAlign = 'center';
120
+ ctx.textBaseline = 'middle';
121
+ ctx.fillText(data.text, 0, 0);
122
+ ctx.restore();
123
+ }
124
+ });
125
+ return ImageHelper.canvasToImageData(canvas);
126
+ }
127
+ function vtkAnnotatedRhombicuboctahedronActor(publicAPI, model) {
128
+ model.classHierarchy.push('vtkAnnotatedRhombicuboctahedronActor');
129
+ function updateAllFaceTextures() { }
130
+ function createActors() {
131
+ let sourceScale = 1.0;
132
+ if (model.scale !== undefined && model.scale !== null) {
133
+ if (Array.isArray(model.scale)) {
134
+ sourceScale = model.scale[0] || 1.0;
135
+ }
136
+ else if (typeof model.scale === 'number') {
137
+ sourceScale = model.scale;
138
+ }
139
+ }
140
+ const actors = [];
141
+ const hexToRgb = (hex) => {
142
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
143
+ return result
144
+ ? result.slice(1, 4).map((n) => parseInt(n, 16))
145
+ : [255, 255, 255];
146
+ };
147
+ const parseFontColor = (color) => {
148
+ if (!color) {
149
+ return [0, 0, 0];
150
+ }
151
+ const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
152
+ if (rgbMatch) {
153
+ return [
154
+ parseInt(rgbMatch[1], 10),
155
+ parseInt(rgbMatch[2], 10),
156
+ parseInt(rgbMatch[3], 10),
157
+ ];
158
+ }
159
+ if (color.startsWith('#')) {
160
+ return hexToRgb(color);
161
+ }
162
+ const namedColors = {
163
+ black: [0, 0, 0],
164
+ white: [255, 255, 255],
165
+ red: [255, 0, 0],
166
+ green: [0, 255, 0],
167
+ blue: [0, 0, 255],
168
+ };
169
+ if (namedColors[color.toLowerCase()]) {
170
+ return namedColors[color.toLowerCase()];
171
+ }
172
+ return [0, 0, 0];
173
+ };
174
+ const faceColors = {
175
+ zMinus: hexToRgb(model.zMinusFaceProperty.faceColor || model.defaultStyle.faceColor),
176
+ zPlus: hexToRgb(model.zPlusFaceProperty.faceColor || model.defaultStyle.faceColor),
177
+ yMinus: hexToRgb(model.yMinusFaceProperty.faceColor || model.defaultStyle.faceColor),
178
+ yPlus: hexToRgb(model.yPlusFaceProperty.faceColor || model.defaultStyle.faceColor),
179
+ xMinus: hexToRgb(model.xMinusFaceProperty.faceColor || model.defaultStyle.faceColor),
180
+ xPlus: hexToRgb(model.xPlusFaceProperty.faceColor || model.defaultStyle.faceColor),
181
+ };
182
+ if (model.showMainFaces !== false) {
183
+ const faceTextureData = [
184
+ {
185
+ faceColor: faceColors.zMinus,
186
+ text: model.zMinusFaceProperty.text || 'I',
187
+ textColor: parseFontColor(model.zMinusFaceProperty.fontColor || model.defaultStyle.fontColor),
188
+ rotation: 0,
189
+ },
190
+ {
191
+ faceColor: faceColors.zPlus,
192
+ text: model.zPlusFaceProperty.text || 'S',
193
+ textColor: parseFontColor(model.zPlusFaceProperty.fontColor || model.defaultStyle.fontColor),
194
+ rotation: 0,
195
+ flipVertical: true,
196
+ },
197
+ {
198
+ faceColor: faceColors.yMinus,
199
+ text: model.yMinusFaceProperty.text || 'A',
200
+ textColor: parseFontColor(model.yMinusFaceProperty.fontColor || model.defaultStyle.fontColor),
201
+ rotation: 180,
202
+ },
203
+ {
204
+ faceColor: faceColors.yPlus,
205
+ text: model.yPlusFaceProperty.text || 'P',
206
+ textColor: parseFontColor(model.yPlusFaceProperty.fontColor || model.defaultStyle.fontColor),
207
+ rotation: 180,
208
+ },
209
+ {
210
+ faceColor: faceColors.xMinus,
211
+ text: model.xMinusFaceProperty.text || 'L',
212
+ textColor: parseFontColor(model.xMinusFaceProperty.fontColor || model.defaultStyle.fontColor),
213
+ rotation: 90,
214
+ flipVertical: true,
215
+ },
216
+ {
217
+ faceColor: faceColors.xPlus,
218
+ text: model.xPlusFaceProperty.text || 'R',
219
+ textColor: parseFontColor(model.xPlusFaceProperty.fontColor || model.defaultStyle.fontColor),
220
+ rotation: 90,
221
+ },
222
+ ];
223
+ const atlasImageData = createTextureAtlas(faceTextureData);
224
+ const mainData = createMainFacesMesh(sourceScale, faceColors, null);
225
+ if (mainData) {
226
+ const mainFacesActor = vtkActor.newInstance();
227
+ const mainMapper = vtkMapper.newInstance();
228
+ mainMapper.setInputData(mainData);
229
+ mainFacesActor.setMapper(mainMapper);
230
+ const texture = vtkTexture.newInstance();
231
+ texture.setInputData(atlasImageData);
232
+ texture.setInterpolate(true);
233
+ mainFacesActor.addTexture(texture);
234
+ const property = mainFacesActor.getProperty();
235
+ property.setBackfaceCulling(false);
236
+ property.setFrontfaceCulling(false);
237
+ property.setLighting(false);
238
+ property.setAmbient(1.0);
239
+ property.setDiffuse(0.0);
240
+ property.setSpecular(0.0);
241
+ actors.push(mainFacesActor);
242
+ }
243
+ }
244
+ if (model.showEdgeFaces !== false) {
245
+ const edgeColor = [200, 200, 200];
246
+ const edgeData = createEdgeFacesMesh(sourceScale, edgeColor);
247
+ if (edgeData) {
248
+ const edgeFacesActor = vtkActor.newInstance();
249
+ const edgeMapper = vtkMapper.newInstance();
250
+ edgeMapper.setInputData(edgeData);
251
+ edgeMapper.setScalarModeToUseCellData();
252
+ edgeMapper.setScalarVisibility(true);
253
+ edgeMapper.setColorModeToDirectScalars();
254
+ edgeFacesActor.setMapper(edgeMapper);
255
+ const edgeProperty = edgeFacesActor.getProperty();
256
+ edgeProperty.setBackfaceCulling(false);
257
+ edgeProperty.setFrontfaceCulling(false);
258
+ edgeProperty.setLighting(false);
259
+ edgeProperty.setAmbient(1.0);
260
+ edgeProperty.setDiffuse(0.0);
261
+ edgeProperty.setSpecular(0.0);
262
+ actors.push(edgeFacesActor);
263
+ }
264
+ }
265
+ if (model.showCornerFaces !== false) {
266
+ const cornerColor = [150, 150, 150];
267
+ const cornerData = createCornerFacesMesh(sourceScale, cornerColor);
268
+ if (cornerData) {
269
+ const cornerFacesActor = vtkActor.newInstance();
270
+ const cornerMapper = vtkMapper.newInstance();
271
+ cornerMapper.setInputData(cornerData);
272
+ cornerMapper.setScalarModeToUseCellData();
273
+ cornerMapper.setScalarVisibility(true);
274
+ cornerMapper.setColorModeToDirectScalars();
275
+ cornerFacesActor.setMapper(cornerMapper);
276
+ const cornerProperty = cornerFacesActor.getProperty();
277
+ cornerProperty.setBackfaceCulling(false);
278
+ cornerProperty.setFrontfaceCulling(false);
279
+ cornerProperty.setLighting(false);
280
+ cornerProperty.setAmbient(1.0);
281
+ cornerProperty.setDiffuse(0.0);
282
+ cornerProperty.setSpecular(0.0);
283
+ actors.push(cornerFacesActor);
284
+ }
285
+ }
286
+ return actors;
287
+ }
288
+ publicAPI.setDefaultStyle = (style) => {
289
+ model.defaultStyle = { ...model.defaultStyle, ...style };
290
+ };
291
+ publicAPI.setXPlusFaceProperty = (prop) => {
292
+ Object.assign(model.xPlusFaceProperty, prop);
293
+ publicAPI.modified();
294
+ };
295
+ publicAPI.setXMinusFaceProperty = (prop) => {
296
+ Object.assign(model.xMinusFaceProperty, prop);
297
+ publicAPI.modified();
298
+ };
299
+ publicAPI.setYPlusFaceProperty = (prop) => {
300
+ Object.assign(model.yPlusFaceProperty, prop);
301
+ publicAPI.modified();
302
+ };
303
+ publicAPI.setYMinusFaceProperty = (prop) => {
304
+ Object.assign(model.yMinusFaceProperty, prop);
305
+ publicAPI.modified();
306
+ };
307
+ publicAPI.setZPlusFaceProperty = (prop) => {
308
+ Object.assign(model.zPlusFaceProperty, prop);
309
+ publicAPI.modified();
310
+ };
311
+ publicAPI.setZMinusFaceProperty = (prop) => {
312
+ Object.assign(model.zMinusFaceProperty, prop);
313
+ publicAPI.modified();
314
+ };
315
+ publicAPI.setShowMainFaces = (show) => {
316
+ if (model.showMainFaces !== show) {
317
+ model.showMainFaces = show;
318
+ updateAllFaceTextures();
319
+ }
320
+ };
321
+ publicAPI.setShowEdgeFaces = (show) => {
322
+ if (model.showEdgeFaces !== show) {
323
+ model.showEdgeFaces = show;
324
+ updateAllFaceTextures();
325
+ }
326
+ };
327
+ publicAPI.setShowCornerFaces = (show) => {
328
+ if (model.showCornerFaces !== show) {
329
+ model.showCornerFaces = show;
330
+ updateAllFaceTextures();
331
+ }
332
+ };
333
+ publicAPI.setRhombScale = (scale) => {
334
+ if (model.scale !== scale) {
335
+ model.scale = scale;
336
+ }
337
+ };
338
+ publicAPI.getActors = () => {
339
+ return createActors();
340
+ };
341
+ }
342
+ export const DEFAULT_VALUES = {
343
+ defaultStyle: {
344
+ text: '',
345
+ faceColor: 'white',
346
+ faceRotation: 0,
347
+ fontFamily: 'Arial',
348
+ fontColor: 'black',
349
+ fontStyle: 'normal',
350
+ fontSizeScale: (resolution) => resolution / 1.8,
351
+ edgeThickness: 0.1,
352
+ edgeColor: 'black',
353
+ resolution: 200,
354
+ },
355
+ xPlusFaceProperty: {},
356
+ xMinusFaceProperty: {},
357
+ yPlusFaceProperty: {},
358
+ yMinusFaceProperty: {},
359
+ zPlusFaceProperty: {},
360
+ zMinusFaceProperty: {},
361
+ showMainFaces: true,
362
+ showEdgeFaces: true,
363
+ showCornerFaces: true,
364
+ scale: 1.0,
365
+ };
366
+ export function extend(publicAPI, model, initialValues = {}) {
367
+ Object.assign(model, DEFAULT_VALUES, initialValues);
368
+ vtkActor.extend(publicAPI, model, initialValues);
369
+ model.xPlusFaceProperty = { ...model.xPlusFaceProperty };
370
+ model.xMinusFaceProperty = { ...model.xMinusFaceProperty };
371
+ model.yPlusFaceProperty = { ...model.yPlusFaceProperty };
372
+ model.yMinusFaceProperty = { ...model.yMinusFaceProperty };
373
+ model.zPlusFaceProperty = { ...model.zPlusFaceProperty };
374
+ model.zMinusFaceProperty = { ...model.zMinusFaceProperty };
375
+ macro.get(publicAPI, model, [
376
+ 'defaultStyle',
377
+ 'xPlusFaceProperty',
378
+ 'xMinusFaceProperty',
379
+ 'yPlusFaceProperty',
380
+ 'yMinusFaceProperty',
381
+ 'zPlusFaceProperty',
382
+ 'zMinusFaceProperty',
383
+ 'showMainFaces',
384
+ 'showEdgeFaces',
385
+ 'showCornerFaces',
386
+ 'scale',
387
+ ]);
388
+ vtkAnnotatedRhombicuboctahedronActor(publicAPI, model);
389
+ }
390
+ export const newInstance = macro.newInstance(extend, 'vtkAnnotatedRhombicuboctahedronActor');
391
+ export default { newInstance, extend };
@@ -0,0 +1,59 @@
1
+ import vtkCellPicker from '@kitware/vtk.js/Rendering/Core/CellPicker';
2
+ import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
3
+ import type { Types } from '@cornerstonejs/core';
4
+ export interface OrientationControllerConfig {
5
+ faceColors: {
6
+ topBottom: number[];
7
+ frontBack: number[];
8
+ leftRight: number[];
9
+ };
10
+ letterColors: {
11
+ zMinus: number[];
12
+ zPlus: number[];
13
+ yMinus: number[];
14
+ yPlus: number[];
15
+ xMinus: number[];
16
+ xPlus: number[];
17
+ };
18
+ opacity: number;
19
+ showEdgeFaces: boolean;
20
+ showCornerFaces: boolean;
21
+ }
22
+ export interface PickResult {
23
+ pickedActor: vtkActor;
24
+ cellId: number;
25
+ actorIndex: number;
26
+ }
27
+ export interface PositionConfig {
28
+ position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
29
+ size: number;
30
+ }
31
+ export interface MouseHandlersCallbacks {
32
+ onFacePicked: (result: PickResult) => void;
33
+ onFaceHover?: (result: PickResult | null) => void;
34
+ }
35
+ export declare class vtkOrientationControllerWidget {
36
+ private actors;
37
+ private pickers;
38
+ private highlightedFace;
39
+ private mouseHandlers;
40
+ createActors(config: OrientationControllerConfig): vtkActor[];
41
+ addActorsToViewport(viewportId: string, viewport: Types.IVolumeViewport, actors: vtkActor[]): void;
42
+ removeActorsFromViewport(viewportId: string, viewport: Types.IVolumeViewport): void;
43
+ setupPicker(viewportId: string, actors: vtkActor[]): vtkCellPicker;
44
+ pickAtPosition(evt: MouseEvent, viewportId: string, viewport: Types.IVolumeViewport, element: HTMLDivElement, actors: vtkActor[]): PickResult | null;
45
+ calculateMarkerPosition(viewport: Types.IVolumeViewport, position: PositionConfig['position']): [number, number, number] | null;
46
+ positionActors(viewport: Types.IVolumeViewport, actors: vtkActor[], config: PositionConfig): boolean;
47
+ highlightFace(actor: vtkActor, cellId: number, viewport: Types.IVolumeViewport, isMainFace?: boolean): void;
48
+ clearHighlight(): void;
49
+ setupMouseHandlers(viewportId: string, element: HTMLDivElement, viewport: Types.IVolumeViewport, actors: vtkActor[], callbacks: MouseHandlersCallbacks): {
50
+ cleanup: () => void;
51
+ };
52
+ getActors(viewportId: string): vtkActor[] | undefined;
53
+ syncOverlayViewport(_viewportId: string, _viewport: Types.IVolumeViewport): void;
54
+ getOrientationForFace(cellId: number): {
55
+ viewPlaneNormal: number[];
56
+ viewUp: number[];
57
+ } | null;
58
+ cleanup(viewportId?: string): void;
59
+ }