copper3d 3.0.2 → 3.1.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.
Files changed (106) hide show
  1. package/README.md +183 -1028
  2. package/dist/Utils/segmentation/CanvasState.d.ts +31 -0
  3. package/dist/Utils/segmentation/CanvasState.js +168 -0
  4. package/dist/Utils/segmentation/CanvasState.js.map +1 -0
  5. package/dist/Utils/segmentation/DragOperator.d.ts +1 -1
  6. package/dist/Utils/segmentation/DragOperator.js +2 -4
  7. package/dist/Utils/segmentation/DragOperator.js.map +1 -1
  8. package/dist/Utils/segmentation/DrawToolCore.d.ts +49 -27
  9. package/dist/Utils/segmentation/DrawToolCore.js +275 -344
  10. package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
  11. package/dist/Utils/segmentation/NrrdTools.d.ts +92 -467
  12. package/dist/Utils/segmentation/NrrdTools.js +499 -1341
  13. package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
  14. package/dist/Utils/segmentation/RenderingUtils.d.ts +73 -0
  15. package/dist/Utils/segmentation/RenderingUtils.js +186 -0
  16. package/dist/Utils/segmentation/RenderingUtils.js.map +1 -0
  17. package/dist/Utils/segmentation/core/MaskVolume.d.ts +1 -1
  18. package/dist/Utils/segmentation/core/MaskVolume.js +1 -1
  19. package/dist/Utils/segmentation/core/index.d.ts +1 -1
  20. package/dist/Utils/segmentation/core/index.js.map +1 -1
  21. package/dist/Utils/segmentation/core/types.d.ts +386 -0
  22. package/dist/Utils/segmentation/core/types.js +1 -0
  23. package/dist/Utils/segmentation/core/types.js.map +1 -1
  24. package/dist/Utils/segmentation/coreTools/GuiState.d.ts +1 -1
  25. package/dist/Utils/segmentation/coreTools/GuiState.js +1 -0
  26. package/dist/Utils/segmentation/coreTools/GuiState.js.map +1 -1
  27. package/dist/Utils/segmentation/coreTools/NrrdState.d.ts +1 -1
  28. package/dist/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
  29. package/dist/Utils/segmentation/coreTools/gui.d.ts +1 -1
  30. package/dist/Utils/segmentation/tools/BaseTool.d.ts +1 -1
  31. package/dist/Utils/segmentation/tools/ContrastTool.d.ts +3 -6
  32. package/dist/Utils/segmentation/tools/ContrastTool.js.map +1 -1
  33. package/dist/Utils/segmentation/tools/CrosshairTool.d.ts +11 -1
  34. package/dist/Utils/segmentation/tools/CrosshairTool.js +25 -0
  35. package/dist/Utils/segmentation/tools/CrosshairTool.js.map +1 -1
  36. package/dist/Utils/segmentation/tools/DataLoader.d.ts +33 -0
  37. package/dist/Utils/segmentation/tools/DataLoader.js +159 -0
  38. package/dist/Utils/segmentation/tools/DataLoader.js.map +1 -0
  39. package/dist/Utils/segmentation/tools/DragSliceTool.d.ts +3 -12
  40. package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -1
  41. package/dist/Utils/segmentation/tools/DrawingTool.d.ts +18 -26
  42. package/dist/Utils/segmentation/tools/DrawingTool.js +48 -0
  43. package/dist/Utils/segmentation/tools/DrawingTool.js.map +1 -1
  44. package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +2 -5
  45. package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -1
  46. package/dist/Utils/segmentation/tools/LayerChannelManager.d.ts +56 -0
  47. package/dist/Utils/segmentation/tools/LayerChannelManager.js +188 -0
  48. package/dist/Utils/segmentation/tools/LayerChannelManager.js.map +1 -0
  49. package/dist/Utils/segmentation/tools/PanTool.d.ts +2 -8
  50. package/dist/Utils/segmentation/tools/PanTool.js.map +1 -1
  51. package/dist/Utils/segmentation/tools/SliceRenderPipeline.d.ts +80 -0
  52. package/dist/Utils/segmentation/tools/SliceRenderPipeline.js +355 -0
  53. package/dist/Utils/segmentation/tools/SliceRenderPipeline.js.map +1 -0
  54. package/dist/Utils/segmentation/tools/SphereTool.d.ts +25 -19
  55. package/dist/Utils/segmentation/tools/SphereTool.js +61 -7
  56. package/dist/Utils/segmentation/tools/SphereTool.js.map +1 -1
  57. package/dist/Utils/segmentation/tools/ToolHost.d.ts +74 -0
  58. package/dist/Utils/segmentation/tools/ToolHost.js +10 -0
  59. package/dist/Utils/segmentation/tools/ToolHost.js.map +1 -0
  60. package/dist/Utils/segmentation/tools/ZoomTool.d.ts +2 -6
  61. package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -1
  62. package/dist/Utils/segmentation/tools/index.d.ts +5 -7
  63. package/dist/Utils/segmentation/tools/index.js +6 -0
  64. package/dist/Utils/segmentation/tools/index.js.map +1 -1
  65. package/dist/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
  66. package/dist/bundle.esm.js +2146 -2359
  67. package/dist/bundle.umd.js +2146 -2359
  68. package/dist/index.d.ts +3 -3
  69. package/dist/index.js +1 -1
  70. package/dist/types/Utils/segmentation/CanvasState.d.ts +31 -0
  71. package/dist/types/Utils/segmentation/DragOperator.d.ts +1 -1
  72. package/dist/types/Utils/segmentation/DrawToolCore.d.ts +49 -27
  73. package/dist/types/Utils/segmentation/NrrdTools.d.ts +92 -467
  74. package/dist/types/Utils/segmentation/RenderingUtils.d.ts +73 -0
  75. package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +1 -1
  76. package/dist/types/Utils/segmentation/core/index.d.ts +1 -1
  77. package/dist/types/Utils/segmentation/core/types.d.ts +386 -0
  78. package/dist/types/Utils/segmentation/coreTools/GuiState.d.ts +1 -1
  79. package/dist/types/Utils/segmentation/coreTools/NrrdState.d.ts +1 -1
  80. package/dist/types/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
  81. package/dist/types/Utils/segmentation/coreTools/gui.d.ts +1 -1
  82. package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +1 -1
  83. package/dist/types/Utils/segmentation/tools/ContrastTool.d.ts +3 -6
  84. package/dist/types/Utils/segmentation/tools/CrosshairTool.d.ts +11 -1
  85. package/dist/types/Utils/segmentation/tools/DataLoader.d.ts +33 -0
  86. package/dist/types/Utils/segmentation/tools/DragSliceTool.d.ts +3 -12
  87. package/dist/types/Utils/segmentation/tools/DrawingTool.d.ts +18 -26
  88. package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +2 -5
  89. package/dist/types/Utils/segmentation/tools/LayerChannelManager.d.ts +56 -0
  90. package/dist/types/Utils/segmentation/tools/PanTool.d.ts +2 -8
  91. package/dist/types/Utils/segmentation/tools/SliceRenderPipeline.d.ts +80 -0
  92. package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +25 -19
  93. package/dist/types/Utils/segmentation/tools/ToolHost.d.ts +74 -0
  94. package/dist/types/Utils/segmentation/tools/ZoomTool.d.ts +2 -6
  95. package/dist/types/Utils/segmentation/tools/index.d.ts +5 -7
  96. package/dist/types/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
  97. package/dist/types/index.d.ts +3 -3
  98. package/package.json +1 -1
  99. package/dist/Utils/segmentation/CommToolsData.d.ts +0 -192
  100. package/dist/Utils/segmentation/CommToolsData.js +0 -474
  101. package/dist/Utils/segmentation/CommToolsData.js.map +0 -1
  102. package/dist/Utils/segmentation/coreTools/coreType.d.ts +0 -389
  103. package/dist/Utils/segmentation/coreTools/coreType.js +0 -3
  104. package/dist/Utils/segmentation/coreTools/coreType.js.map +0 -1
  105. package/dist/types/Utils/segmentation/CommToolsData.d.ts +0 -192
  106. package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +0 -389
package/README.md CHANGED
@@ -6,9 +6,9 @@
6
6
  [![NRRD_Segmentation_Tool example][nrrd_example]][nrrd_example-url]
7
7
  [![MedTech Heart example][heart_example]][heart_example-url]
8
8
 
9
- A 3d visualisation package base on threejs provides multiple scenes and Nrrd image load funtion.
9
+ A 3D visualisation package based on three.js multiple scenes, NRRD/DICOM image loading, and a full medical image segmentation annotation engine.
10
10
 
11
- ### Documentation:
11
+ ### Documentation
12
12
 
13
13
  https://copper3d-visualisation.readthedocs.io/en/latest/
14
14
 
@@ -17,72 +17,57 @@ https://copper3d-visualisation.readthedocs.io/en/latest/
17
17
  Old: https://www.npmjs.com/package/copper3d_visualisation
18
18
  Very old: https://www.npmjs.com/package/gltfloader-plugin-test
19
19
 
20
- ### Example
20
+ ### Examples
21
21
 
22
22
  [Pick model with Gltfloader](https://linkungao.github.io/loadHumanModel_example/)
23
-
24
23
  [Copper3d_examples](https://linkungao.github.io/copper3d_examples)
25
24
 
26
- ### Useage
25
+ ---
26
+
27
+ ### Basic Usage
27
28
 
28
- - Load demo
29
+ **Load demo**
29
30
 
30
31
  ```ts
31
32
  import * as Copper from "copper3d";
32
33
  import { getCurrentInstance, onMounted } from "vue";
33
- let refs = null;
34
+
34
35
  let appRenderer;
35
36
  onMounted(() => {
36
- let { $refs } = (getCurrentInstance() as any).proxy;
37
- refs = $refs;
38
- const bg: HTMLDivElement = refs.classfy;
37
+ const { $refs } = (getCurrentInstance() as any).proxy;
38
+ const bg: HTMLDivElement = $refs.classfy;
39
39
  appRenderer = new Copper.copperRenderer(bg);
40
- const scene = appRenderer.getCurrentScene();
41
- scene.createDemoMesh();
40
+ appRenderer.getCurrentScene().createDemoMesh();
42
41
  appRenderer.animate();
43
42
  });
44
43
  ```
45
44
 
46
- - Add options (curently only control gui)
45
+ **Options**
47
46
 
48
47
  ```ts
49
48
  appRenderer = new Copper.copperRenderer(bg, { guiOpen: true });
50
49
  ```
51
50
 
52
- - Load multiple scenes with gltf-loader
51
+ **Multiple scenes with glTF**
53
52
 
54
53
  ```ts
55
- import * as Copper from "copper3d";
56
- import { getCurrentInstance, onMounted } from "vue";
57
-
58
- let refs = null;
59
- let appRenderer;
60
- onMounted(() => {
61
- let { $refs } = (getCurrentInstance() as any).proxy;
62
- refs = $refs;
63
- const bg: HTMLDivElement = refs.classfy;
64
- appRenderer = new Copper.copperRenderer(bg);
65
- appRenderer.animate();
66
- loadModel("/Healthy.glb", "health");
67
- });
68
-
69
- function loadModel(url, name) {
70
- let scene1 = appRenderer.getSceneByName(name);
71
- if (scene1 == undefined) {
72
- const scene1 = appRenderer.createScene(name);
73
- appRenderer.setCurrentScene(scene1);
74
- scene1.loadViewUrl("/noInfarct_view.json");
75
- scene1.loadGltf(url);
54
+ function loadModel(url: string, name: string) {
55
+ let scene = appRenderer.getSceneByName(name);
56
+ if (!scene) {
57
+ scene = appRenderer.createScene(name);
58
+ appRenderer.setCurrentScene(scene);
59
+ scene.loadViewUrl("/noInfarct_view.json");
60
+ scene.loadGltf(url);
76
61
  } else {
77
- appRenderer.setCurrentScene(scene1);
62
+ appRenderer.setCurrentScene(scene);
78
63
  }
79
64
  }
80
65
  ```
81
66
 
82
- #### Viewdata Structure
67
+ **View data structure**
83
68
 
84
69
  ```ts
85
- CameraViewPoint {
70
+ CameraViewPoint {
86
71
  nearPlane: number = 0.1;
87
72
  farPlane: number = 2000.0;
88
73
  eyePosition: Array<number> = [0.0, 0.0, 0.0];
@@ -91,30 +76,23 @@ function loadModel(url, name) {
91
76
  }
92
77
  ```
93
78
 
79
+ ---
80
+
94
81
  ## NrrdTools Usage Guide
95
82
 
96
83
  > Copper3D `NrrdTools` — Medical Image Segmentation Annotation Engine
97
84
 
98
- `NrrdTools` is the core annotation engine of Copper3D. It manages multi-layer mask volumes, a layered canvas pipeline, drawing tools, undo/redo history, channel color customization, and keyboard shortcuts — all on top of a Three.js medical image viewer.
99
-
100
- ---
85
+ `NrrdTools` manages multi-layer mask volumes, a layered canvas pipeline, drawing tools, undo/redo history, channel color customization, and keyboard shortcuts on top of a Three.js medical image viewer.
101
86
 
102
- ### Table of Contents
103
-
104
- 1. [Quick Start](#1-quick-start)
105
- 2. [Constructor & Initialization](#2-constructor--initialization)
106
- 3. [Data Loading](#3-data-loading)
107
- 4. [Render Loop Integration](#4-render-loop-integration)
108
- 5. [Drawing Setup](#5-drawing-setup)
109
- 6. [Layer & Channel Management](#6-layer--channel-management)
110
- 7. [Channel Color Customization](#7-channel-color-customization)
111
- 8. [Undo / Redo](#8-undo--redo)
112
- 9. [Keyboard Shortcuts](#9-keyboard-shortcuts)
113
- 10. [Display & Canvas Control](#10-display--canvas-control)
114
- 11. [Reading State & Diagnostics](#11-reading-state--diagnostics)
115
- 12. [Advanced Scenarios](#12-advanced-scenarios)
116
- 13. [Vue 3 Integration Pattern](#13-vue-3-integration-pattern)
117
- 14. [Type Reference](#14-type-reference)
87
+ > **Internal Architecture**: `NrrdTools` is a **Facade** using **composition** (no inheritance). It composes:
88
+ > - **`CanvasState`** — unified state container (nrrd_states, gui_states, protectedData, callbacks, keyboardSettings)
89
+ > - **`DrawToolCore`** — tool orchestration and event routing
90
+ > - **`RenderingUtils`** — slice extraction and canvas compositing helpers
91
+ > - **`LayerChannelManager`** — layer/channel/sphere-type management and color customization
92
+ > - **`SliceRenderPipeline`** — slice setup, canvas rendering, mask reload, canvas flip
93
+ > - **`DataLoader`** — NRRD slice loading, NIfTI voxel loading
94
+ >
95
+ > The old inheritance chain (`NrrdTools → DrawToolCore → CommToolsData`) has been fully replaced. All modules communicate via `ToolContext` (shared state). The public API below is unchanged.
118
96
 
119
97
  ---
120
98
 
@@ -123,27 +101,20 @@ function loadModel(url, name) {
123
101
  ```typescript
124
102
  import * as Copper from 'copper3d';
125
103
 
126
- // 1. Mount the tool on a container div
127
104
  const container = document.getElementById('viewer') as HTMLDivElement;
128
105
  const nrrdTools = new Copper.NrrdTools(container);
129
106
 
130
- // 2. After NRRD images are loaded via Copper scene:
131
107
  nrrdTools.reset();
132
- nrrdTools.setAllSlices(allSlices); // allSlices from Copper scene loader
108
+ nrrdTools.setAllSlices(allSlices); // allSlices from Copper scene loader
133
109
 
134
- // 3. Hook drawing callbacks
135
- nrrdTools.drag({ getSliceNum: (index, contrastIndex) => {
136
- console.log('Slice changed to:', index);
137
- }});
110
+ nrrdTools.drag({ getSliceNum: (index) => console.log('Slice:', index) });
138
111
 
139
112
  nrrdTools.draw({
140
113
  getMaskData: (sliceData, layerId, channelId, sliceIndex, axis, width, height, clearFlag) => {
141
114
  // Called after every stroke, undo, redo — sync to backend here
142
- console.log(`Layer ${layerId}, channel ${channelId}, slice ${sliceIndex} on ${axis}-axis`);
143
115
  }
144
116
  });
145
117
 
146
- // 4. Register with the Copper render loop
147
118
  scene.addPreRenderCallbackFunction(nrrdTools.start);
148
119
  ```
149
120
 
@@ -151,580 +122,206 @@ scene.addPreRenderCallbackFunction(nrrdTools.start);
151
122
 
152
123
  ### 2. Constructor & Initialization
153
124
 
154
- #### Signature
155
-
156
125
  ```typescript
157
126
  new Copper.NrrdTools(container: HTMLDivElement, options?: { layers?: string[] })
158
127
  ```
159
128
 
160
129
  | Parameter | Type | Default | Description |
161
130
  |-----------|------|---------|-------------|
162
- | `container` | `HTMLDivElement` | required | The DOM element that will host all annotation canvases |
163
- | `options.layers` | `string[]` | `["layer1","layer2","layer3"]` | Named layers to create. Each layer gets its own `MaskVolume` and canvas |
164
-
165
- #### Example: Single-layer (minimal)
131
+ | `container` | `HTMLDivElement` | required | Host DOM element for all annotation canvases |
132
+ | `options.layers` | `string[]` | `["layer1","layer2","layer3"]` | Named layers to create |
166
133
 
167
134
  ```typescript
168
- const nrrdTools = new Copper.NrrdTools(document.getElementById('viewer') as HTMLDivElement);
169
- ```
170
-
171
- #### Example: Custom layer set
172
-
173
- ```typescript
174
- // For a 4-layer segmentation workflow:
175
- // layer1 = Tumour, layer2 = Edema, layer3 = Necrosis, layer4 = Enhancement
135
+ // Custom layer set
176
136
  const nrrdTools = new Copper.NrrdTools(container, {
177
137
  layers: ['layer1', 'layer2', 'layer3', 'layer4']
178
138
  });
179
- ```
180
-
181
- > **Important**: The layer list you pass here must match what your backend and UI expect.
182
- > Adding or removing layers later requires re-instantiation.
183
-
184
- #### Optional display panel
185
139
 
186
- Attach a panel element to show current slice index in the viewer:
140
+ // Optional: show current slice index in a panel
141
+ nrrdTools.setDisplaySliceIndexPanel(document.getElementById('slice-panel') as HTMLDivElement);
187
142
 
188
- ```typescript
189
- const slicePanel = document.getElementById('slice-index-panel') as HTMLDivElement;
190
- nrrdTools.setDisplaySliceIndexPanel(slicePanel);
191
- ```
192
-
193
- #### Optional GUI (dat.GUI)
194
-
195
- If your project uses `dat.GUI`, wire up the built-in control panel:
196
-
197
- ```typescript
198
- import GUI from 'lil-gui'; // or 'dat.gui'
199
- const gui = new GUI();
200
- nrrdTools.setupGUI(gui as any);
143
+ // Optional: connect dat.GUI / lil-gui
144
+ import GUI from 'lil-gui';
145
+ nrrdTools.setupGUI(new GUI() as any);
201
146
  ```
202
147
 
203
148
  ---
204
149
 
205
150
  ### 3. Data Loading
206
151
 
207
- #### 3.1 Loading NRRD / NIfTI image slices
208
-
209
- This is the entry point that initializes all `MaskVolume` instances to the correct voxel dimensions.
210
- Must be called **after** Copper has loaded and decoded the NRRD files.
211
-
212
152
  ```typescript
213
- // Reset state from previous case
153
+ // Reset state then load new slices
214
154
  nrrdTools.reset();
215
-
216
- // allSlices is the array of decoded NRRD slice objects returned by Copper's loader
217
155
  nrrdTools.setAllSlices(allSlices);
218
- ```
219
-
220
- > After `setAllSlices()` returns, you may safely call all layer/channel/color APIs.
221
- > Calling color APIs **before** `setAllSlices()` will silently fail (no MaskVolume exists yet).
222
-
223
- #### 3.2 Loading existing mask data (NIfTI)
224
-
225
- If the user has previously saved annotations, reload them into the volumes:
226
156
 
227
- ```typescript
228
- // layerVoxels maps each layer ID to a Uint8Array of raw NIfTI voxels
157
+ // Load existing NIfTI mask data
229
158
  const layerVoxels = new Map<string, Uint8Array>([
230
159
  ['layer1', layer1Uint8Array],
231
160
  ['layer2', layer2Uint8Array],
232
161
  ]);
233
-
234
162
  nrrdTools.setMasksFromNIfTI(layerVoxels);
235
- ```
236
163
 
237
- With a loading progress bar:
238
-
239
- ```typescript
240
- const loadingBar = { value: 0 }; // must be a reactive object with .value
164
+ // With loading progress bar
165
+ const loadingBar = { value: 0 };
241
166
  nrrdTools.setMasksFromNIfTI(layerVoxels, loadingBar);
242
167
  ```
243
168
 
244
- #### Scenario: Loading a saved case
245
-
246
- ```typescript
247
- async function loadCase(caseDetail: ICaseDetail) {
248
- const layerVoxels = new Map<string, Uint8Array>();
249
-
250
- // layer1 — always load from NIfTI if it exists
251
- if (Number(caseDetail.output.mask_layer1_nii_size) > 0) {
252
- const voxels = await fetchNiftiVoxels(caseDetail.output.mask_layer1_nii_path!);
253
- if (voxels) layerVoxels.set('layer1', voxels);
254
- }
255
-
256
- // layer2 — same pattern
257
- if (Number(caseDetail.output.mask_layer2_nii_size) > 0) {
258
- const voxels = await fetchNiftiVoxels(caseDetail.output.mask_layer2_nii_path!);
259
- if (voxels) layerVoxels.set('layer2', voxels);
260
- }
261
-
262
- if (layerVoxels.size > 0) {
263
- nrrdTools.setMasksFromNIfTI(layerVoxels);
264
- }
265
- }
266
- ```
267
-
268
169
  ---
269
170
 
270
171
  ### 4. Render Loop Integration
271
172
 
272
- `NrrdTools.start` is a function property that must be called every frame to refresh the annotation overlay on top of the Three.js rendered CT/MRI slice.
273
-
274
- #### With Copper scene
275
-
276
173
  ```typescript
277
174
  // Register once after initialization
278
175
  const callbackId = scene.addPreRenderCallbackFunction(nrrdTools.start);
279
176
 
280
- // Unregister on teardown (e.g., Vue component unmount)
177
+ // Unregister on teardown
281
178
  scene.removePreRenderCallbackFunction(callbackId);
282
179
  ```
283
180
 
284
- #### Manual render loop
285
-
286
- ```typescript
287
- function animate() {
288
- requestAnimationFrame(animate);
289
- renderer.render(scene, camera);
290
- nrrdTools.start();
291
- }
292
- animate();
293
- ```
294
-
295
181
  ---
296
182
 
297
183
  ### 5. Drawing Setup
298
184
 
299
- #### 5.1 `drag()` — Slice navigation
300
-
301
- Enables dragging to scroll through CT/MRI slices. Call once before adding to the render loop.
185
+ #### `drag()` — Slice navigation
302
186
 
303
187
  ```typescript
304
188
  nrrdTools.drag({
305
- showNumber: true, // optional: show slice number overlay
306
- getSliceNum: (sliceIndex, contrastIndex) => { // optional: callback on slice change
307
- console.log('Now viewing slice:', sliceIndex);
308
- updateUISliceDisplay(sliceIndex);
309
- }
189
+ showNumber: true,
190
+ getSliceNum: (sliceIndex, contrastIndex) => updateUI(sliceIndex),
310
191
  });
311
192
  ```
312
193
 
313
- #### 5.2 `draw()` — Annotation callbacks
314
-
315
- Hooks into the drawing events. The `getMaskData` callback is the primary integration point for syncing annotations to a backend.
194
+ #### `draw()` — Annotation callbacks
316
195
 
317
196
  ```typescript
318
197
  nrrdTools.draw({
319
- // Called after every draw stroke, undo, redo, or clear
320
- getMaskData: (
321
- sliceData: Uint8Array, // Raw voxel data for this slice (label values 0-8)
322
- layerId: string, // Which layer was modified, e.g. "layer1"
323
- channelId: number, // Active channel (1-8)
324
- sliceIndex: number, // Index along the current axis
325
- axis: 'x' | 'y' | 'z', // Current viewing axis
326
- width: number, // Slice width in voxels
327
- height: number, // Slice height in voxels
328
- clearFlag?: boolean // true if the user cleared the slice
329
- ) => {
198
+ getMaskData: (sliceData, layerId, channelId, sliceIndex, axis, width, height, clearFlag?) => {
330
199
  sendSliceToBackend({ sliceData, layerId, channelId, sliceIndex, axis, width, height, clearFlag });
331
200
  },
332
-
333
- // Called when the user clears an entire layer volume
334
- onClearLayerVolume: (layerId: string) => {
335
- console.log(`Layer ${layerId} fully cleared`);
336
- notifyBackendLayerCleared(layerId);
337
- },
338
-
339
- // Called when the 3D sphere annotation tool places a sphere (sphere mode)
340
- //
341
- // sphereOrigin: [mouseX, mouseY, sliceIndex] — center on z-axis view
342
- // sphereRadius: radius in pixels (1-50)
343
- //
344
- // Note: Sphere data does NOT write to layer MaskVolume.
345
- // It is rendered as a visual overlay via the dedicated sphereMaskVolume.
346
- getSphereData: (sphereOrigin: number[], sphereRadius: number) => {
347
- console.log('Sphere placed at', sphereOrigin, 'radius', sphereRadius);
348
- sendSphereToBackend({ sphereOrigin, sphereRadius });
349
- },
350
-
351
- // Called when the calculator sphere positions are updated (calculator mode)
352
- //
353
- // Each sphere type maps to a specific channel on layer1:
354
- // tumour → channel 1
355
- // ribcage → channel 3
356
- // skin → channel 4
357
- // nipple → channel 2
358
- //
359
- // Origins are ICommXYZ: { x: [mx, my, slice], y: [...], z: [...] }
360
- // representing sphere center coordinates on all 3 axis views.
361
- // null if that sphere type has not been placed yet.
362
- getCalculateSpherePositionsData: (
363
- tumourOrigin, skinOrigin, ribOrigin, nippleOrigin, axis
364
- ) => {
365
- runAISegmentation({ tumourOrigin, skinOrigin, ribOrigin, nippleOrigin, axis });
201
+ onClearLayerVolume: (layerId) => notifyBackendLayerCleared(layerId),
202
+ getSphereData: (sphereOrigin, sphereRadius) => sendSphereToBackend({ sphereOrigin, sphereRadius }),
203
+ getCalculateSpherePositionsData: (tumour, skin, rib, nipple, axis) => {
204
+ if (tumour && skin && rib && nipple) aiBackend.runSegmentation({ tumour, skin, rib, nipple, axis });
366
205
  },
367
206
  });
368
207
  ```
369
208
 
370
- #### 5.3 SphereTool 3D Sphere Placement & Distance Calculator
371
-
372
- The SphereTool provides unified sphere placement with 4 sphere types controlled by `gui_states.mode.activeSphereType`. The active type determines which origin and color are used. The old separate "Calculator" mode has been merged into SphereTool.
373
-
374
- ##### Sphere Types & Channel Mapping
375
-
376
- Each sphere type maps to a specific channel on layer1. Switch between types using `gui_states.mode.activeSphereType`:
377
-
378
- | Sphere Type | Channel | Default Color | `activeSphereType` value |
379
- |-------------|---------|---------------|---------------------|
380
- | tumour | 1 | `#10b981` (Emerald) | `"tumour"` (default) |
381
- | nipple | 2 | `#f43f5e` (Rose) | `"nipple"` |
382
- | ribcage | 3 | `#3b82f6` (Blue) | `"ribcage"` |
383
- | skin | 4 | `#fbbf24` (Amber) | `"skin"` |
384
-
385
- These mappings are exported as `SPHERE_CHANNEL_MAP` and `SPHERE_LABELS` from `tools/SphereTool.ts`. (`SPHERE_COLORS` was removed — colors are now derived dynamically from each volume's `colorMap`.)
386
-
387
- ##### Switching Sphere Type
388
-
389
- Use the public API — do NOT mutate `gui_states.mode.activeSphereType` directly, as `setActiveSphereType()` also updates brush/fill color as a side-effect:
209
+ #### `enableContrastDragEvents()`Window/Level
390
210
 
391
211
  ```typescript
392
- // Set active sphere type (also updates fillColor / brushColor)
393
- nrrdTools.setActiveSphereType('nipple'); // channel 2, Rose #f43f5e
394
- nrrdTools.setActiveSphereType('tumour'); // channel 1, Emerald #10b981
395
- nrrdTools.setActiveSphereType('skin'); // channel 4, Amber #fbbf24
396
- nrrdTools.setActiveSphereType('ribcage'); // channel 3, Blue #3b82f6
397
-
398
- // Read current type
399
- const type = nrrdTools.getActiveSphereType();
400
- // → 'tumour' | 'skin' | 'nipple' | 'ribcage'
401
- ```
402
-
403
- ##### Interaction Flow
404
-
405
- ```
406
- Sphere mode activated (gui_states.mode.sphere = true, keyboard shortcut: 'q'):
407
- ├─ Shift key DISABLED (no draw mode)
408
- ├─ Crosshair toggle allowed (S key)
409
-
410
- ├─ Left-click DOWN → record origin for current activeSphereType type,
411
- │ show preview, bind sphere wheel
412
- ├─ Scroll wheel (while holding) → adjust radius [1, 50]
413
- └─ Left-click UP → write all placed spheres to volume,
414
- fire getSphere + getCalculateSpherePositions callbacks,
415
- restore zoom/slice wheel
416
- ```
417
-
418
- ##### SphereMaskVolume
419
-
420
- Sphere 3D data is stored in a dedicated `MaskVolume` (`nrrd_states.sphereMaskVolume`), separate from the layer draw mask volumes. This prevents sphere overlay data from polluting layer1's annotations.
421
-
422
- - **Created** in `setAllSlices()` (same dimensions as CT volume)
423
- - **Cleared** in `reset()` (when switching cases)
424
-
425
- > **Note**: Currently sphere data does NOT write to layer1's MaskVolume. The channel mapping and `sphereMaskVolume` are reserved for future integration.
426
-
427
- ##### Scenario: Sphere mode with AI backend (distance calculation)
428
-
429
- ```typescript
430
- nrrdTools.draw({
431
- // Called on every sphere placement — receives all 4 sphere origins
432
- getCalculateSpherePositionsData: (tumour, skin, rib, nipple, axis) => {
433
- // Each origin is { x: [mx, my, slice], y: [...], z: [...] } or null
434
- if (tumour && skin && rib && nipple) {
435
- aiBackend.runSegmentation({ tumour, skin, rib, nipple, axis });
436
- }
437
- },
438
- // Also called — receives the current sphere's origin and radius
439
- getSphereData: (origin, radius) => {
440
- console.log('Sphere placed at', origin, 'radius', radius);
441
- },
212
+ nrrdTools.enableContrastDragEvents((step, towards) => {
213
+ console.log(`Contrast: ${towards} ${step}`);
442
214
  });
443
215
  ```
444
216
 
445
- ##### Programmatic Sphere Placement (Backend Frontend)
217
+ #### SphereTool Sphere Types & Channel Mapping
446
218
 
447
- When the backend returns sphere coordinates (e.g., from AI detection), use `setCalculateDistanceSphere()` to place them without user interaction. This method replicates the full mouse-down → mouse-up flow internally:
219
+ | Sphere Type | Channel | Default Color | `activeSphereType` value |
220
+ |-------------|---------|---------------|--------------------------|
221
+ | tumour | 1 | `#10b981` | `"tumour"` (default) |
222
+ | nipple | 2 | `#f43f5e` | `"nipple"` |
223
+ | ribcage | 3 | `#3b82f6` | `"ribcage"` |
224
+ | skin | 4 | `#fbbf24` | `"skin"` |
448
225
 
449
226
  ```typescript
450
- // Backend returns sphere data for a case
451
- interface BackendSphereData {
452
- type: 'tumour' | 'skin' | 'nipple' | 'ribcage';
453
- x: number; // X in unscaled image space
454
- y: number; // Y in unscaled image space
455
- sliceIndex: number; // Target slice index
456
- }
457
-
458
- // Place each sphere programmatically
459
- function applySphereFromBackend(nrrdTools: Copper.NrrdTools, spheres: BackendSphereData[]) {
460
- for (const s of spheres) {
461
- nrrdTools.setCalculateDistanceSphere(s.x, s.y, s.sliceIndex, s.type);
462
- }
463
- }
464
-
465
- // Example: place a tumour sphere at (120, 95) on slice 42
466
- nrrdTools.setCalculateDistanceSphere(120, 95, 42, 'tumour');
227
+ nrrdTools.setActiveSphereType('nipple'); // also updates brush/fill color
228
+ const type = nrrdTools.getActiveSphereType(); // → 'tumour' | 'skin' | 'nipple' | 'ribcage'
467
229
  ```
468
230
 
469
- Internally, `setCalculateDistanceSphere` performs:
470
- 1. Sets `sphereRadius = 5` and navigates to the target slice
471
- 2. Records `sphereOrigin` on all 3 axes (via `crosshairTool.setUpSphereOrigins`)
472
- 3. Deep-copies the origin into the type-specific field (e.g., `tumourSphereOrigin`)
473
- 4. Draws the sphere preview on canvas (`drawCalculatorSphere`)
474
- 5. Writes all placed spheres to `sphereMaskVolume` (`writeAllCalculatorSpheresToVolume`)
475
- 6. Re-renders the sphere overlay (`refreshSphereCanvas`)
476
-
477
- > **Note**: Coordinates (`x`, `y`) are in **unscaled** image space. The method automatically applies `sizeFactor` scaling internally.
478
-
479
- #### 5.4 `enableContrastDragEvents()` — Windowing
480
-
481
- Enable Ctrl+drag to adjust window/level (brightness/contrast):
231
+ **Programmatic sphere placement (backend → frontend):**
482
232
 
483
233
  ```typescript
484
- nrrdTools.enableContrastDragEvents((step: number, towards: 'horizental' | 'vertical') => {
485
- // step: magnitude of drag movement
486
- // towards: direction of drag
487
- console.log(`Contrast adjusted: ${towards} ${step}`);
488
- });
234
+ // Replicates full click flow internally no user interaction needed
235
+ nrrdTools.setCalculateDistanceSphere(120, 95, 42, 'tumour');
236
+ // Coordinates are in unscaled image space; sizeFactor is applied internally
489
237
  ```
490
238
 
491
239
  ---
492
240
 
493
241
  ### 6. Layer & Channel Management
494
242
 
495
- NrrdTools supports **multi-layer** annotation (e.g., tumour, edema, necrosis) and **multi-channel** within each layer (e.g., different anatomical structures painted in different colors).
496
-
497
- #### 6.1 Active Layer & Channel
498
-
499
243
  ```typescript
500
- // Switch the active layer (where new strokes go)
244
+ // Active layer / channel
501
245
  nrrdTools.setActiveLayer('layer2');
246
+ nrrdTools.setActiveChannel(3);
247
+ const layer = nrrdTools.getActiveLayer();
248
+ const channel = nrrdTools.getActiveChannel();
502
249
 
503
- // Switch the active channel within the current layer
504
- nrrdTools.setActiveChannel(3); // channel 3 = blue by default
505
-
506
- // Query current state
507
- const currentLayer = nrrdTools.getActiveLayer(); // → 'layer2'
508
- const currentChannel = nrrdTools.getActiveChannel(); // → 3
509
- ```
510
-
511
- #### 6.2 Layer Visibility
512
-
513
- Toggle layers on/off in the composite canvas:
514
-
515
- ```typescript
516
- // Hide layer2 (e.g., edema) while keeping layer1 visible
250
+ // Layer visibility
517
251
  nrrdTools.setLayerVisible('layer2', false);
518
- nrrdTools.setLayerVisible('layer1', true);
519
-
520
- // Query visibility
521
- const layer2Visible = nrrdTools.isLayerVisible('layer2'); // → false
522
-
523
- // Get all at once
524
- const visibilityMap = nrrdTools.getLayerVisibility();
525
- // → { layer1: true, layer2: false, layer3: true, layer4: true }
526
- ```
527
-
528
- #### Scenario: Eye-button toggle in UI
529
-
530
- ```typescript
531
- function onToggleLayerEye(layerId: string) {
532
- const current = nrrdTools.isLayerVisible(layerId);
533
- nrrdTools.setLayerVisible(layerId, !current);
534
- }
535
- ```
536
-
537
- #### 6.3 Channel Visibility
538
-
539
- Each layer can independently show/hide its channels:
252
+ const visible = nrrdTools.isLayerVisible('layer2');
253
+ const visMap = nrrdTools.getLayerVisibility(); // { layer1: true, layer2: false, ... }
540
254
 
541
- ```typescript
542
- // Hide channel 2 in layer1 (e.g., secondary annotation color)
255
+ // Channel visibility (per layer)
543
256
  nrrdTools.setChannelVisible('layer1', 2, false);
544
-
545
- // Show it again
546
- nrrdTools.setChannelVisible('layer1', 2, true);
547
-
548
- // Query
549
- const ch2Visible = nrrdTools.isChannelVisible('layer1', 2); // → true
550
-
551
- // Get all channel visibility for all layers
552
257
  const allChannelVis = nrrdTools.getChannelVisibility();
553
- // → { layer1: { 1: true, 2: false, 3: true, ... }, layer2: { 1: true, ... }, ... }
554
- ```
555
-
556
- #### Scenario: Showing only the selected channel
557
258
 
558
- ```typescript
559
- function isolateChannel(layerId: string, targetChannel: number, totalChannels = 8) {
560
- for (let ch = 1; ch <= totalChannels; ch++) {
561
- nrrdTools.setChannelVisible(layerId, ch, ch === targetChannel);
562
- }
563
- }
564
-
565
- // Show only channel 1 in layer1
566
- isolateChannel('layer1', 1);
567
-
568
- // Restore all
569
- for (let ch = 1; ch <= 8; ch++) {
570
- nrrdTools.setChannelVisible('layer1', ch, true);
571
- }
572
- ```
573
-
574
- #### 6.4 Checking if a layer has annotations
575
-
576
- ```typescript
577
- if (nrrdTools.hasLayerData('layer2')) {
578
- console.log('layer2 has annotated voxels — saving...');
579
- await saveLayer('layer2');
580
- } else {
581
- console.log('layer2 is empty, skipping save');
582
- }
259
+ // Check if a layer has annotations
260
+ if (nrrdTools.hasLayerData('layer1')) await saveLayer('layer1');
583
261
  ```
584
262
 
585
263
  ---
586
264
 
587
265
  ### 7. Channel Color Customization
588
266
 
589
- Each layer has its own independent color map. Changing channel 3's color in `layer1` does **not** affect `layer2`.
590
-
591
- #### Default Channel Colors
267
+ **Default colors:**
592
268
 
593
269
  | Channel | Color | Hex |
594
270
  |---------|-------|-----|
595
- | 1 | Emerald (Tumour) | `#10b981` |
596
- | 2 | Rose (Edema) | `#f43f5e` |
597
- | 3 | Blue (Necrosis) | `#3b82f6` |
598
- | 4 | Amber (Enhancement) | `#fbbf24` |
599
- | 5 | Fuchsia (Vessel) | `#d946ef` |
600
- | 6 | Cyan (Additional) | `#06b6d4` |
601
- | 7 | Orange (Auxiliary) | `#f97316` |
602
- | 8 | Violet (Extended) | `#8b5cf6` |
603
-
604
- #### 7.1 Set a single channel color
271
+ | 1 | Emerald | `#10b981` |
272
+ | 2 | Rose | `#f43f5e` |
273
+ | 3 | Blue | `#3b82f6` |
274
+ | 4 | Amber | `#fbbf24` |
275
+ | 5 | Fuchsia | `#d946ef` |
276
+ | 6 | Cyan | `#06b6d4` |
277
+ | 7 | Orange | `#f97316` |
278
+ | 8 | Violet | `#8b5cf6` |
605
279
 
606
280
  ```typescript
607
- // RGBAColor: { r, g, b, a } — all values 0-255
608
- nrrdTools.setChannelColor('layer1', 3, { r: 255, g: 128, b: 0, a: 255 }); // → orange
609
- ```
610
-
611
- #### 7.2 Batch-set multiple channel colors (recommended)
612
-
613
- One `reloadMasksFromVolume()` call instead of N calls — better performance:
281
+ // Set one channel color (RGBAColor: { r, g, b, a } — 0-255)
282
+ nrrdTools.setChannelColor('layer1', 3, { r: 255, g: 128, b: 0, a: 255 });
614
283
 
615
- ```typescript
284
+ // Batch-set (one reloadMasksFromVolume call — better performance)
616
285
  nrrdTools.setChannelColors('layer1', {
617
- 1: { r: 255, g: 80, b: 80, a: 255 }, // Soft red
618
- 2: { r: 80, g: 180, b: 255, a: 255 }, // Sky blue
619
- 3: { r: 255, g: 230, b: 50, a: 255 }, // Golden yellow
286
+ 1: { r: 255, g: 80, b: 80, a: 255 },
287
+ 2: { r: 80, g: 180, b: 255, a: 255 },
620
288
  });
621
- ```
622
-
623
- #### 7.3 Apply the same color to all layers
624
289
 
625
- ```typescript
626
- // Paint all layers' channel 1 in the same tumour color
290
+ // Apply same channel color across all layers
627
291
  nrrdTools.setAllLayersChannelColor(1, { r: 0, g: 220, b: 100, a: 255 });
628
- ```
629
-
630
- #### 7.4 Read current colors
631
-
632
- ```typescript
633
- const rgba = nrrdTools.getChannelColor('layer2', 3);
634
- // → { r: 255, g: 128, b: 0, a: 255 }
635
-
636
- const hex = nrrdTools.getChannelHexColor('layer2', 3);
637
- // → "#ff8000" — suitable for canvas fillStyle
638
-
639
- const css = nrrdTools.getChannelCssColor('layer2', 3);
640
- // → "rgba(255,128,0,1.00)" — suitable for Vue :style binding
641
- ```
642
-
643
- #### 7.5 Reset colors
644
-
645
- ```typescript
646
- // Reset one channel in one layer
647
- nrrdTools.resetChannelColors('layer1', 3);
648
-
649
- // Reset all channels in one layer
650
- nrrdTools.resetChannelColors('layer1');
651
-
652
- // Reset everything across all layers
653
- nrrdTools.resetChannelColors();
654
- ```
655
-
656
- #### Scenario: Color picker integration
657
-
658
- ```typescript
659
- // User picks a new color in the UI color picker
660
- function onColorPicked(hexColor: string) {
661
- // Parse hex to RGBA (simple implementation)
662
- const r = parseInt(hexColor.slice(1, 3), 16);
663
- const g = parseInt(hexColor.slice(3, 5), 16);
664
- const b = parseInt(hexColor.slice(5, 7), 16);
665
292
 
666
- const activeLayer = nrrdTools.getActiveLayer();
667
- const activeChannel = nrrdTools.getActiveChannel();
293
+ // Read colors
294
+ const rgba = nrrdTools.getChannelColor('layer1', 3);
295
+ const hex = nrrdTools.getChannelHexColor('layer1', 3); // → "#ff8000"
296
+ const css = nrrdTools.getChannelCssColor('layer1', 3); // → "rgba(255,128,0,1.00)"
668
297
 
669
- nrrdTools.setChannelColor(activeLayer, activeChannel, { r, g, b, a: 255 });
670
- }
671
- ```
672
-
673
- #### Scenario: Vue UI reactivity after color change
674
-
675
- After calling `setChannelColor()`, the canvas re-renders automatically.
676
- But Vue computed properties that show the color need a manual nudge:
677
-
678
- ```typescript
679
- // In your composable or component
680
- const colorVersion = ref(0);
681
-
682
- const channelCssColor = computed(() => {
683
- colorVersion.value; // subscribe to version
684
- return nrrdTools.value?.getChannelCssColor(activeLayer.value, activeChannel.value) ?? '#00ff00';
685
- });
686
-
687
- function refreshColors() {
688
- colorVersion.value++; // forces recompute
689
- }
690
-
691
- // After setting color:
692
- nrrdTools.value.setChannelColor('layer1', 2, { r: 255, g: 0, b: 0, a: 255 });
693
- refreshColors();
298
+ // Reset
299
+ nrrdTools.resetChannelColors('layer1', 3); // one channel
300
+ nrrdTools.resetChannelColors('layer1'); // all channels in layer
301
+ nrrdTools.resetChannelColors(); // everything
694
302
  ```
695
303
 
696
304
  ---
697
305
 
698
306
  ### 8. Undo / Redo
699
307
 
700
- NrrdTools maintains a per-layer undo stack (max 50 entries). Every completed brush stroke pushes a delta snapshot.
308
+ Per-layer undo stack (max 50 entries). Every completed stroke pushes a delta snapshot.
701
309
 
702
310
  ```typescript
703
- // Undo the last stroke on the active layer
704
311
  nrrdTools.undo();
705
-
706
- // Redo the last undone stroke
707
312
  nrrdTools.redo();
708
- ```
709
313
 
710
- #### Scenario: Keyboard shortcut binding
711
-
712
- ```typescript
314
+ // Or via keyboard
713
315
  window.addEventListener('keydown', (e) => {
714
316
  if (e.ctrlKey && e.key === 'z') nrrdTools.undo();
715
317
  if (e.ctrlKey && e.key === 'y') nrrdTools.redo();
716
318
  });
717
319
  ```
718
320
 
719
- > The built-in keyboard system also handles undo/redo (default: `z` and `y` without Ctrl).
720
- > See [Keyboard Shortcuts](#9-keyboard-shortcuts) for configuring this.
721
-
722
321
  ---
723
322
 
724
323
  ### 9. Keyboard Shortcuts
725
324
 
726
- #### Default bindings
727
-
728
325
  | Action | Default Key |
729
326
  |--------|-------------|
730
327
  | Draw mode | `Shift` (hold) |
@@ -735,523 +332,43 @@ window.addEventListener('keydown', (e) => {
735
332
  | Sphere mode | `q` |
736
333
  | Mouse wheel | Zoom |
737
334
 
738
- #### Reading current settings
739
-
740
335
  ```typescript
336
+ nrrdTools.setKeyboardSettings({ undo: 'u', mouseWheel: 'Scroll:Slice' });
741
337
  const settings = nrrdTools.getKeyboardSettings();
742
- console.log(settings);
743
- // {
744
- // draw: 'Shift',
745
- // undo: 'z',
746
- // redo: 'y',
747
- // contrast: ['Control', 'Meta'],
748
- // crosshair: 's',
749
- // mouseWheel: 'Scroll:Zoom'
750
- // }
751
- ```
752
338
 
753
- #### Customizing bindings
754
-
755
- ```typescript
756
- nrrdTools.setKeyboardSettings({
757
- undo: 'u', // Remap undo to 'u'
758
- mouseWheel: 'Scroll:Slice', // Mouse wheel scrolls slices instead of zooming
759
- });
760
- ```
761
-
762
- #### Scenario: Form input focus — suppressing shortcuts
763
-
764
- When a user types in an input field, you don't want tool shortcuts firing:
765
-
766
- ```typescript
767
- inputElement.addEventListener('focus', () => {
768
- nrrdTools.enterKeyboardConfig(); // Suppress all tool shortcuts
769
- });
770
-
771
- inputElement.addEventListener('blur', () => {
772
- nrrdTools.exitKeyboardConfig(); // Restore shortcuts
773
- });
774
- ```
775
-
776
- #### Enabling / disabling contrast shortcut independently
777
-
778
- ```typescript
779
- // Disable only the contrast (window/level) keyboard shortcut
780
- nrrdTools.setContrastShortcutEnabled(false);
781
-
782
- // Check status
783
- const isEnabled = nrrdTools.isContrastShortcutEnabled(); // → false
784
-
785
- // Re-enable
786
- nrrdTools.setContrastShortcutEnabled(true);
339
+ // Suppress shortcuts while a form input is focused
340
+ inputEl.addEventListener('focus', () => nrrdTools.enterKeyboardConfig());
341
+ inputEl.addEventListener('blur', () => nrrdTools.exitKeyboardConfig());
787
342
  ```
788
343
 
789
344
  ---
790
345
 
791
- ### 10. Display & Canvas Control
792
-
793
- #### Canvas size scaling
794
-
795
- Set the base display size multiplier (1–8). Larger values use more GPU memory but give sharper annotations:
796
-
797
- ```typescript
798
- nrrdTools.setBaseDrawDisplayCanvasesSize(2); // 2× base resolution
799
- ```
800
-
801
- #### Reading image metadata
802
-
803
- ```typescript
804
- // Voxel dimensions [width, height, depth]
805
- const dims = nrrdTools.getCurrentImageDimension();
806
- // → [512, 512, 256]
807
-
808
- // Physical spacing (mm per voxel, from NRRD header)
809
- const spacing = nrrdTools.getVoxelSpacing();
810
- // → [0.488, 0.488, 1.0]
811
-
812
- // World-space origin
813
- const origin = nrrdTools.getSpaceOrigin();
814
- // → [-125.0, -125.0, -127.5]
815
-
816
- // Max slice count per axis
817
- const maxSlices = nrrdTools.getMaxSliceNum();
818
- // → [512, 512, 256] (one per axis)
819
-
820
- // Current viewing state
821
- const { currentSliceIndex, contrastIndex } = nrrdTools.getCurrentSlicesNumAndContrastNum();
822
- ```
823
-
824
- #### Accessing internal canvases
825
-
826
- ```typescript
827
- // The topmost interactive canvas (mouse/pen events target here)
828
- const drawingCanvas = nrrdTools.getDrawingCanvas();
829
-
830
- // The inner main-area container div (dynamically created by NrrdTools, NOT the original container passed to constructor)
831
- const container = nrrdTools.getContainer();
832
-
833
- // Raw NrrdState (all internal state fields, grouped by semantic sub-objects)
834
- const states = nrrdTools.getNrrdToolsSettings();
835
- console.log(states.image.layers); // ["layer1", "layer2", ...]
836
- console.log(states.image.dimensions); // [512, 512, 256]
837
- ```
838
-
839
- #### Accessing full mask data
840
-
841
- ```typescript
842
- // All MaskVolume data structures
843
- const maskData = nrrdTools.getMaskData();
844
- // maskData.volumes is Record<string, MaskVolume>
845
- ```
846
-
847
- ---
848
-
849
- ### 11. Reading State & Diagnostics
850
-
851
- ```typescript
852
- // Full snapshot of current state
853
- const state = nrrdTools.getNrrdToolsSettings();
854
-
855
- // Active layer / channel
856
- const layer = nrrdTools.getActiveLayer();
857
- const channel = nrrdTools.getActiveChannel();
858
-
859
- // Visibility maps
860
- const layerVis = nrrdTools.getLayerVisibility();
861
- const channelVis = nrrdTools.getChannelVisibility();
862
-
863
- // Check if any real annotation exists in a layer
864
- const hasTumour = nrrdTools.hasLayerData('layer1');
865
-
866
- // Get current channel color as hex
867
- const hex = nrrdTools.getChannelHexColor('layer1', 1); // → "#10b981"
868
-
869
- // Keyboard settings
870
- const keys = nrrdTools.getKeyboardSettings();
871
- ```
872
-
873
- ---
874
-
875
- ### 12. Advanced Scenarios
876
-
877
- #### Scenario A: Complete multi-layer initialization from scratch
878
-
879
- ```typescript
880
- async function initAnnotationTool(container: HTMLDivElement, allSlices: any[]) {
881
- // Create tool with 4 layers
882
- const nrrdTools = new Copper.NrrdTools(container, {
883
- layers: ['layer1', 'layer2', 'layer3', 'layer4']
884
- });
885
-
886
- // Attach GUI
887
- nrrdTools.setupGUI(gui as any);
888
-
889
- // Attach contrast drag
890
- nrrdTools.enableContrastDragEvents((step, towards) => {
891
- console.log('Windowing:', towards, step);
892
- });
893
-
894
- // Load image volume
895
- nrrdTools.reset();
896
- nrrdTools.setAllSlices(allSlices);
897
-
898
- // Register callbacks
899
- nrrdTools.drag({ getSliceNum: (idx) => updateSliceUI(idx) });
900
-
901
- nrrdTools.draw({
902
- getMaskData: (sliceData, layerId, channelId, sliceIndex, axis, w, h, clearFlag) => {
903
- syncSliceToBackend({ sliceData, layerId, channelId, sliceIndex, axis, clearFlag });
904
- },
905
- onClearLayerVolume: (layerId) => {
906
- notifyBackendCleared(layerId);
907
- },
908
- });
909
-
910
- // Connect to render loop
911
- scene.addPreRenderCallbackFunction(nrrdTools.start);
912
-
913
- return nrrdTools;
914
- }
915
- ```
916
-
917
- #### Scenario B: Load existing annotations and apply custom color scheme
918
-
919
- ```typescript
920
- async function loadAndColorCase(nrrdTools: Copper.NrrdTools, caseId: string) {
921
- // 1. Load existing NIfTI masks
922
- const masks = await fetchCaseMasks(caseId);
923
- const layerVoxels = new Map<string, Uint8Array>();
924
- for (const [layerId, data] of Object.entries(masks)) {
925
- layerVoxels.set(layerId, data);
926
- }
927
- nrrdTools.setMasksFromNIfTI(layerVoxels);
928
-
929
- // 2. Apply per-layer color schemes
930
- nrrdTools.setChannelColors('layer1', {
931
- 1: { r: 0, g: 200, b: 80, a: 255 }, // Tumour core — green
932
- 2: { r: 255, g: 200, b: 0, a: 255 }, // Tumour ring — yellow
933
- });
934
-
935
- nrrdTools.setChannelColors('layer2', {
936
- 1: { r: 255, g: 60, b: 60, a: 200 }, // Edema — semi-transparent red
937
- });
938
-
939
- // 3. Set initial active state
940
- nrrdTools.setActiveLayer('layer1');
941
- nrrdTools.setActiveChannel(1);
942
- }
943
- ```
944
-
945
- #### Scenario C: Switching cases — full reset
946
-
947
- ```typescript
948
- async function switchCase(nrrdTools: Copper.NrrdTools, newCaseData: ICaseData) {
949
- // Reset annotation volumes
950
- nrrdTools.reset();
951
-
952
- // Load new image slices (from Copper's scene loader result)
953
- nrrdTools.setAllSlices(newCaseData.slices);
954
-
955
- // Reset colors to defaults
956
- nrrdTools.resetChannelColors();
957
-
958
- // Make all layers and channels visible
959
- for (const layerId of ['layer1', 'layer2', 'layer3', 'layer4']) {
960
- nrrdTools.setLayerVisible(layerId, true);
961
- for (let ch = 1; ch <= 8; ch++) {
962
- nrrdTools.setChannelVisible(layerId, ch, true);
963
- }
964
- }
965
-
966
- // Start fresh on layer1, channel1
967
- nrrdTools.setActiveLayer('layer1');
968
- nrrdTools.setActiveChannel(1);
969
-
970
- // Load existing masks if available
971
- if (newCaseData.hasExistingMasks) {
972
- nrrdTools.setMasksFromNIfTI(newCaseData.layerVoxels);
973
- }
974
- }
975
- ```
976
-
977
- #### Scenario D: Save workflow with dirty-layer detection
346
+ ### 10. Clearing Annotations
978
347
 
979
348
  ```typescript
980
- async function onSave(nrrdTools: Copper.NrrdTools, caseId: string) {
981
- const layers = ['layer1', 'layer2', 'layer3', 'layer4'];
982
-
983
- for (const layerId of layers) {
984
- if (!nrrdTools.hasLayerData(layerId)) {
985
- console.log(`${layerId} is empty — initializing blank NIfTI on backend`);
986
- await initBlankLayerOnBackend(caseId, layerId);
987
- } else {
988
- console.log(`${layerId} has data — saving...`);
989
- await saveLayerToBackend(caseId, layerId);
990
- }
991
- }
992
- }
993
- ```
994
-
995
- #### Scenario E: AI segmentation result — write back to volume
996
-
997
- After receiving a segmentation result from a backend AI model, write it directly into a layer:
998
-
999
- ```typescript
1000
- async function applyAIResult(nrrdTools: Copper.NrrdTools, layerId: string) {
1001
- // Fetch AI-generated NIfTI from backend
1002
- const response = await fetch(`/api/ai-result/${layerId}`);
1003
- const buffer = await response.arrayBuffer();
1004
- const voxels = new Uint8Array(buffer);
1005
-
1006
- // Write directly into the target layer volume
1007
- const layerVoxels = new Map<string, Uint8Array>([[layerId, voxels]]);
1008
- nrrdTools.setMasksFromNIfTI(layerVoxels);
1009
-
1010
- // Optionally switch to that layer so user sees the result
1011
- nrrdTools.setActiveLayer(layerId);
1012
- }
1013
- ```
1014
-
1015
- #### Scenario F: Dynamic keyboard remap from user settings
1016
-
1017
- ```typescript
1018
- interface UserPreferences {
1019
- undoKey: string;
1020
- redoKey: string;
1021
- sliceScrollMode: 'zoom' | 'slice';
1022
- }
1023
-
1024
- function applyUserKeyboardPreferences(nrrdTools: Copper.NrrdTools, prefs: UserPreferences) {
1025
- nrrdTools.setKeyboardSettings({
1026
- undo: prefs.undoKey,
1027
- redo: prefs.redoKey,
1028
- mouseWheel: prefs.sliceScrollMode === 'slice' ? 'Scroll:Slice' : 'Scroll:Zoom',
1029
- });
1030
- }
1031
-
1032
- // Example: Apply on preference change
1033
- applyUserKeyboardPreferences(nrrdTools, {
1034
- undoKey: 'u',
1035
- redoKey: 'r',
1036
- sliceScrollMode: 'slice',
1037
- });
1038
- ```
1039
-
1040
- #### Scenario G: Read layer colors to build a color legend
1041
-
1042
- ```typescript
1043
- function buildColorLegend(nrrdTools: Copper.NrrdTools, layerId: string) {
1044
- const legend = [];
1045
- for (let ch = 1; ch <= 8; ch++) {
1046
- legend.push({
1047
- channel: ch,
1048
- cssColor: nrrdTools.getChannelCssColor(layerId, ch),
1049
- hexColor: nrrdTools.getChannelHexColor(layerId, ch),
1050
- rgba: nrrdTools.getChannelColor(layerId, ch),
1051
- });
1052
- }
1053
- return legend;
1054
- }
1055
-
1056
- // → [
1057
- // { channel: 1, cssColor: 'rgba(16,185,129,1.00)', hexColor: '#10b981', rgba: { r:16, g:185, b:129, a:255 } },
1058
- // { channel: 2, cssColor: 'rgba(244,63,94,1.00)', hexColor: '#f43f5e', rgba: { r:244, g:63, b:94, a:255 } },
1059
- // ...
1060
- // ]
1061
- ```
1062
-
1063
- #### Scenario H: Clearing annotations
1064
-
1065
- There are different levels of clearing data depending on the use case:
1066
-
1067
- ```typescript
1068
- // 1. Clear ALL data across ALL layers comprehensively (typically used when switching cases)
1069
- // This clears all volumes, undo histories, UI canvases, index parameters, and sphere data globally.
1070
- nrrdTools.reset();
1071
-
1072
- // 2. Clear all annotations on the currently active layer across its entire 3D volume
1073
- // This clears the active layer's MaskVolume, clears its undo/redo history, and re-renders the canvas.
1074
- // It also triggers the `onClearLayerVolume` callback so you can notify the backend.
1075
- nrrdTools.clearActiveLayer();
1076
-
1077
- // 3. Clear the mask ONLY on the currently viewed 2D slice for the active layer
1078
- // This removes annotations specifically on the current slice index and orientation.
1079
- // It records an undo operation to allow rollback.
1080
- nrrdTools.clearActiveSlice();
349
+ nrrdTools.reset(); // Reset ALL layers, volumes, undo histories, canvases (use when switching cases)
350
+ nrrdTools.clearActiveLayer(); // Clear active layer's entire 3D volume + undo history, fire onClearLayerVolume
351
+ nrrdTools.clearActiveSlice(); // Clear only the currently viewed 2D slice (undoable)
1081
352
  ```
1082
353
 
1083
354
  ---
1084
355
 
1085
- ### 13. Vue 3 Integration Pattern
1086
-
1087
- The recommended pattern in Vue 3 is to distribute the `NrrdTools` instance via a Vue event emitter after NRRD loading completes, so any descendant component can access it.
1088
-
1089
- #### LeftPanel (creator)
1090
-
1091
- ```vue
1092
- <script setup lang="ts">
1093
- import * as Copper from 'copper3d';
1094
- import emitter from '@/plugins/custom-emitter';
1095
-
1096
- let nrrdTools: Copper.NrrdTools | undefined;
1097
-
1098
- onMounted(() => {
1099
- nrrdTools = new Copper.NrrdTools(canvasContainer.value as HTMLDivElement, {
1100
- layers: ['layer1', 'layer2', 'layer3', 'layer4']
1101
- });
1102
- });
1103
-
1104
- // After all NRRD files are loaded by the Copper scene:
1105
- function onAllImagesLoaded(allSlices: any[]) {
1106
- nrrdTools!.reset();
1107
- nrrdTools!.setAllSlices(allSlices);
1108
-
1109
- nrrdTools!.drag({ getSliceNum: (idx) => emit('sliceChanged', idx) });
1110
-
1111
- nrrdTools!.draw({
1112
- getMaskData: (sliceData, layerId, channelId, sliceIndex, axis, w, h, clearFlag) => {
1113
- emit('maskDataUpdated', { sliceData, layerId, channelId, sliceIndex, axis, w, h, clearFlag });
1114
- },
1115
- onClearLayerVolume: (layerId) => emit('layerCleared', layerId),
1116
- });
1117
-
1118
- scene!.addPreRenderCallbackFunction(nrrdTools!.start);
1119
-
1120
- // Broadcast to all other components
1121
- emitter.emit('Core:NrrdTools', nrrdTools!);
1122
- emitter.emit('Segmentation:FinishLoadAllCaseImages');
1123
- }
1124
- </script>
1125
- ```
1126
-
1127
- #### Annotation Control Panel (consumer)
1128
-
1129
- ```vue
1130
- <script setup lang="ts">
1131
- import * as Copper from 'copper3d';
1132
- import emitter from '@/plugins/custom-emitter';
1133
-
1134
- const nrrdTools = ref<Copper.NrrdTools>();
1135
-
1136
- onMounted(() => {
1137
- emitter.on('Core:NrrdTools', (tools: Copper.NrrdTools) => {
1138
- nrrdTools.value = tools;
1139
- });
1140
-
1141
- emitter.on('Segmentation:FinishLoadAllCaseImages', () => {
1142
- // Safe to call all APIs now — MaskVolume is initialized
1143
- syncStateFromTools();
1144
- });
1145
- });
1146
-
1147
- function syncStateFromTools() {
1148
- if (!nrrdTools.value) return;
1149
- activeLayer.value = nrrdTools.value.getActiveLayer();
1150
- activeChannel.value = nrrdTools.value.getActiveChannel();
1151
- layerVisibility.value = nrrdTools.value.getLayerVisibility();
1152
- channelVisibility.value = nrrdTools.value.getChannelVisibility();
1153
- }
1154
-
1155
- function onChannelColorPicked(hex: string) {
1156
- if (!nrrdTools.value) return;
1157
- const r = parseInt(hex.slice(1,3), 16);
1158
- const g = parseInt(hex.slice(3,5), 16);
1159
- const b = parseInt(hex.slice(5,7), 16);
1160
- nrrdTools.value.setChannelColor(activeLayer.value, activeChannel.value, { r, g, b, a: 255 });
1161
- refreshColors(); // bump colorVersion to re-trigger computed
1162
- }
1163
- </script>
1164
- ```
1165
-
1166
- ---
1167
-
1168
- ### 14. Type Reference
1169
-
1170
- ```typescript
1171
- // Color type used by all channel color APIs
1172
- interface RGBAColor {
1173
- r: number; // 0-255
1174
- g: number; // 0-255
1175
- b: number; // 0-255
1176
- a: number; // 0-255 (255 = fully opaque)
1177
- }
1178
-
1179
- // Batch color map for setChannelColors()
1180
- type ChannelColorMap = Record<number, RGBAColor>; // key = channel number 1-8
1181
-
1182
- // draw() options
1183
- interface IDrawOpts {
1184
- getMaskData?: (
1185
- sliceData: Uint8Array,
1186
- layerId: string,
1187
- channelId: number,
1188
- sliceIndex: number,
1189
- axis: 'x' | 'y' | 'z',
1190
- width: number,
1191
- height: number,
1192
- clearFlag?: boolean
1193
- ) => void;
1194
- onClearLayerVolume?: (layerId: string) => void;
1195
- getSphereData?: (sphereOrigin: number[], sphereRadius: number) => void;
1196
- getCalculateSpherePositionsData?: (
1197
- tumourOrigin: ICommXYZ | null,
1198
- skinOrigin: ICommXYZ | null,
1199
- ribOrigin: ICommXYZ | null,
1200
- nippleOrigin: ICommXYZ | null,
1201
- axis: 'x' | 'y' | 'z'
1202
- ) => void;
1203
- }
1204
-
1205
- // drag() options
1206
- interface IDragOpts {
1207
- showNumber?: boolean;
1208
- getSliceNum?: (sliceIndex: number, contrastIndex: number) => void;
1209
- }
1210
-
1211
- // Keyboard settings
1212
- interface IKeyBoardSettings {
1213
- draw: string;
1214
- undo: string;
1215
- redo: string;
1216
- contrast: string[]; // always an array, e.g. ["Control", "Meta"]
1217
- crosshair: string;
1218
- sphere: string;
1219
- mouseWheel: 'Scroll:Zoom' | 'Scroll:Slice';
1220
- }
1221
-
1222
- // 3D coordinate (used in sphere position callbacks)
1223
- interface ICommXYZ {
1224
- x: number;
1225
- y: number;
1226
- z: number;
1227
- }
1228
- ```
1229
-
1230
- #### Common type aliases
1231
-
1232
- ```typescript
1233
- type LayerId = 'layer1' | 'layer2' | 'layer3' | 'layer4'; // or any string
1234
- type ChannelValue = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
1235
- ```
1236
-
1237
- ---
1238
-
1239
- ### API Summary
356
+ ### 11. API Summary
1240
357
 
1241
358
  | Category | Method | Description |
1242
359
  |----------|--------|-------------|
1243
360
  | **Constructor** | `new NrrdTools(container, { layers })` | Create instance with optional layer config |
1244
361
  | **Setup** | `drag(opts?)` | Enable slice-drag navigation |
1245
362
  | | `draw(opts?)` | Bind annotation callbacks |
1246
- | | `setupGUI(gui)` | Connect dat.GUI panel |
363
+ | | `setupGUI(gui)` | Connect dat.GUI / lil-gui panel |
1247
364
  | | `enableContrastDragEvents(cb)` | Enable Ctrl+drag windowing |
1248
365
  | | `setDisplaySliceIndexPanel(el)` | Show slice index in a panel |
1249
- | | `setBaseDrawDisplayCanvasesSize(n)` | Set canvas resolution multiplier (1-8) |
1250
- | **Data** | `reset()` | Reset all volumes, undo histories, canvases, and sphere data |
1251
- | | `clearActiveLayer()` | Clear annotations and undo history for the *currently active layer* |
1252
- | | `clearActiveSlice()` | Clear annotations exclusively on the *currently viewed slice* of the active layer |
366
+ | | `setBaseDrawDisplayCanvasesSize(n)` | Set canvas resolution multiplier (18) |
367
+ | **Data** | `reset()` | Reset all volumes, undo histories, canvases, sphere data |
368
+ | | `clearActiveLayer()` | Clear active layer volume + undo history |
369
+ | | `clearActiveSlice()` | Clear current slice (undoable) |
1253
370
  | | `setAllSlices(slices)` | Load NRRD slices, init MaskVolumes |
1254
- | | `setMasksFromNIfTI(map)` | Load saved NIfTI voxel data |
371
+ | | `setMasksFromNIfTI(map, bar?)` | Load saved NIfTI voxel data |
1255
372
  | **Render** | `start` | Frame callback — pass to render loop |
1256
373
  | **Layer** | `setActiveLayer(id)` | Switch drawing target layer |
1257
374
  | | `getActiveLayer()` | Read current layer |
@@ -1259,8 +376,9 @@ type ChannelValue = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
1259
376
  | | `isLayerVisible(id)` | Query layer visibility |
1260
377
  | | `getLayerVisibility()` | All layer visibility map |
1261
378
  | | `hasLayerData(id)` | Check if layer has non-zero voxels |
1262
- | **Sphere** | `setActiveSphereType(type)` | Set active sphere type (`'tumour'`/`'skin'`/`'nipple'`/`'ribcage'`), updates brush color |
379
+ | **Sphere** | `setActiveSphereType(type)` | Set active sphere type, updates brush color |
1263
380
  | | `getActiveSphereType()` | Read current sphere type |
381
+ | | `setCalculateDistanceSphere(x, y, slice, type)` | Programmatically place a calculator sphere |
1264
382
  | **Channel** | `setActiveChannel(ch)` | Switch drawing target channel |
1265
383
  | | `getActiveChannel()` | Read current channel |
1266
384
  | | `setChannelVisible(id, ch, bool)` | Toggle channel visibility in a layer |
@@ -1273,45 +391,82 @@ type ChannelValue = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
1273
391
  | | `getChannelHexColor(id, ch)` | Read Hex string |
1274
392
  | | `getChannelCssColor(id, ch)` | Read CSS rgba() string |
1275
393
  | | `resetChannelColors(id?, ch?)` | Reset to defaults |
1276
- | **Tool Mode** | `setMode(mode)` | Switch tool mode: `"pencil"` / `"brush"` / `"eraser"` / `"sphere"` / `"calculator"` |
394
+ | **Tool Mode** | `setMode(mode)` | Switch tool: `"pencil"` / `"brush"` / `"eraser"` / `"sphere"` / `"calculator"` |
1277
395
  | | `getMode()` | Read current tool mode |
1278
- | | `isCalculatorActive()` | Check if calculator (distance) mode is active |
1279
396
  | **Drawing** | `setOpacity(value)` | Set mask overlay opacity [0.1, 1] |
1280
397
  | | `getOpacity()` | Read current opacity |
1281
- | | `setBrushSize(size)` | Set brush/eraser size [5, 50], updates cursor |
398
+ | | `setBrushSize(size)` | Set brush/eraser size [5, 50] |
1282
399
  | | `getBrushSize()` | Read current brush size |
1283
- | | `setPencilColor(hex)` | Set pencil stroke color (hex string) |
1284
- | | `getPencilColor()` | Read current pencil color |
1285
- | **Contrast** | `setWindowHigh(value)` | Set window high (image contrast), call `finishWindowAdjustment()` after drag |
1286
- | | `setWindowLow(value)` | Set window low (image center) |
400
+ | **Contrast** | `setWindowHigh(value)` | Set window high |
401
+ | | `setWindowLow(value)` | Set window low |
1287
402
  | | `finishWindowAdjustment()` | Repaint all contrast slices after drag ends |
1288
- | | `getSliderMeta(key)` | Get slider min/max/step/value for UI config (`"globalAlpha"`, `"brushAndEraserSize"`, etc.) |
1289
- | **Actions** | `executeAction(action)` | Run named action: `"undo"`, `"redo"`, `"clearActiveSliceMask"`, `"clearActiveLayerMask"`, `"resetZoom"`, `"downloadCurrentMask"` |
403
+ | **Actions** | `executeAction(action)` | Run: `"undo"` / `"redo"` / `"clearActiveSliceMask"` / `"clearActiveLayerMask"` / `"resetZoom"` / `"downloadCurrentMask"` |
1290
404
  | **Navigation** | `setSliceOrientation(axis)` | Switch viewing axis `"x"` / `"y"` / `"z"` |
1291
- | | `setCalculateDistanceSphere(x, y, slice, type)` | Programmatically place a calculator sphere (simulates full click flow: record origin → draw → write to volume) |
1292
- | **History** | `undo()` | Undo last stroke |
1293
- | | `redo()` | Redo last undone stroke |
405
+ | **History** | `undo()` / `redo()` | Undo / redo last stroke |
1294
406
  | **Keyboard** | `setKeyboardSettings(partial)` | Remap shortcuts |
1295
407
  | | `getKeyboardSettings()` | Read current bindings |
1296
- | | `enterKeyboardConfig()` | Suppress all shortcuts |
1297
- | | `exitKeyboardConfig()` | Restore shortcuts |
408
+ | | `enterKeyboardConfig()` / `exitKeyboardConfig()` | Suppress / restore shortcuts |
1298
409
  | | `setContrastShortcutEnabled(bool)` | Enable/disable contrast key |
1299
- | | `isContrastShortcutEnabled()` | Query contrast key state |
1300
410
  | **Inspect** | `getCurrentImageDimension()` | `[w, h, d]` voxel dims |
1301
411
  | | `getVoxelSpacing()` | Physical mm spacing |
1302
412
  | | `getSpaceOrigin()` | World-space origin |
1303
413
  | | `getMaxSliceNum()` | Max slice index per axis |
1304
414
  | | `getCurrentSlicesNumAndContrastNum()` | Current slice & contrast index |
1305
415
  | | `getMaskData()` | Raw `IMaskData` object |
1306
- | | `getNrrdToolsSettings()` | Full `NrrdState` snapshot (grouped sub-objects: image, view, interaction, sphere, flags) |
416
+ | | `getNrrdToolsSettings()` | Full `NrrdState` snapshot |
1307
417
  | | `getContainer()` | Host `HTMLElement` |
1308
418
  | | `getDrawingCanvas()` | Top-layer `HTMLCanvasElement` |
1309
419
 
420
+ ---
1310
421
 
1311
- ## Acknowledgements
422
+ ### 12. Type Reference
423
+
424
+ ```typescript
425
+ interface RGBAColor { r: number; g: number; b: number; a: number; } // 0-255
426
+
427
+ type ChannelColorMap = Record<number, RGBAColor>; // key = channel 1-8
428
+
429
+ interface IDrawOpts {
430
+ getMaskData?: (
431
+ sliceData: Uint8Array, layerId: string, channelId: number,
432
+ sliceIndex: number, axis: 'x' | 'y' | 'z',
433
+ width: number, height: number, clearFlag?: boolean
434
+ ) => void;
435
+ onClearLayerVolume?: (layerId: string) => void;
436
+ getSphereData?: (sphereOrigin: number[], sphereRadius: number) => void;
437
+ getCalculateSpherePositionsData?: (
438
+ tumourOrigin: ICommXYZ | null, skinOrigin: ICommXYZ | null,
439
+ ribOrigin: ICommXYZ | null, nippleOrigin: ICommXYZ | null,
440
+ axis: 'x' | 'y' | 'z'
441
+ ) => void;
442
+ }
1312
443
 
1313
- Special thanks to [Duke University dataset](https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId=70226903) provides these awsome MRI data!!!
444
+ interface IDragOpts {
445
+ showNumber?: boolean;
446
+ getSliceNum?: (sliceIndex: number, contrastIndex: number) => void;
447
+ }
448
+
449
+ interface IKeyBoardSettings {
450
+ draw: string;
451
+ undo: string;
452
+ redo: string;
453
+ contrast: string[];
454
+ crosshair: string;
455
+ sphere: string;
456
+ mouseWheel: 'Scroll:Zoom' | 'Scroll:Slice';
457
+ }
458
+
459
+ interface ICommXYZ { x: number; y: number; z: number; }
460
+
461
+ type LayerId = 'layer1' | 'layer2' | 'layer3' | 'layer4'; // or any string
462
+ type ChannelValue = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Acknowledgements
1314
468
 
469
+ Special thanks to [Duke University dataset](https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId=70226903) for providing the MRI data.
1315
470
 
1316
471
  ---
1317
472
 
@@ -1324,4 +479,4 @@ Special thanks to [Duke University dataset](https://wiki.cancerimagingarchive.ne
1324
479
  [nrrd_example]: https://img.shields.io/badge/Nrrd__Segmentation__tool-example-orange
1325
480
  [nrrd_example-url]: https://abi-web-apps.github.io/NRRD_Segmentation_Tool/
1326
481
  [heart_example-url]: https://uoa-heart-mechanics-research.github.io/medtech-heart/model-heart
1327
- [heart_example]: https://img.shields.io/badge/Medtech%20Heart-example-brightgreen
482
+ [heart_example]: https://img.shields.io/badge/Medtech%20Heart-example-brightgreen