copper3d 3.0.0 → 3.0.2

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 (98) hide show
  1. package/README.md +195 -28
  2. package/dist/Utils/segmentation/CommToolsData.d.ts +32 -18
  3. package/dist/Utils/segmentation/CommToolsData.js +69 -152
  4. package/dist/Utils/segmentation/CommToolsData.js.map +1 -1
  5. package/dist/Utils/segmentation/DragOperator.d.ts +10 -2
  6. package/dist/Utils/segmentation/DragOperator.js +18 -8
  7. package/dist/Utils/segmentation/DragOperator.js.map +1 -1
  8. package/dist/Utils/segmentation/DrawToolCore.d.ts +24 -18
  9. package/dist/Utils/segmentation/DrawToolCore.js +255 -436
  10. package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
  11. package/dist/Utils/segmentation/NrrdTools.d.ts +152 -10
  12. package/dist/Utils/segmentation/NrrdTools.js +604 -203
  13. package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
  14. package/dist/Utils/segmentation/core/MaskVolume.d.ts +1 -34
  15. package/dist/Utils/segmentation/core/MaskVolume.js +24 -124
  16. package/dist/Utils/segmentation/core/MaskVolume.js.map +1 -1
  17. package/dist/Utils/segmentation/core/UndoManager.d.ts +1 -1
  18. package/dist/Utils/segmentation/core/UndoManager.js +1 -1
  19. package/dist/Utils/segmentation/core/index.d.ts +0 -2
  20. package/dist/Utils/segmentation/core/index.js +0 -1
  21. package/dist/Utils/segmentation/core/index.js.map +1 -1
  22. package/dist/Utils/segmentation/core/types.js +24 -24
  23. package/dist/Utils/segmentation/core/types.js.map +1 -1
  24. package/dist/Utils/segmentation/coreTools/GuiState.d.ts +22 -0
  25. package/dist/Utils/segmentation/coreTools/GuiState.js +48 -0
  26. package/dist/Utils/segmentation/coreTools/GuiState.js.map +1 -0
  27. package/dist/Utils/segmentation/coreTools/NrrdState.d.ts +25 -0
  28. package/dist/Utils/segmentation/coreTools/NrrdState.js +85 -0
  29. package/dist/Utils/segmentation/coreTools/NrrdState.js.map +1 -0
  30. package/dist/Utils/segmentation/coreTools/coreType.d.ts +88 -65
  31. package/dist/Utils/segmentation/coreTools/coreType.js.map +1 -1
  32. package/dist/Utils/segmentation/coreTools/divControlTools.js +1 -1
  33. package/dist/Utils/segmentation/coreTools/divControlTools.js.map +1 -1
  34. package/dist/Utils/segmentation/coreTools/gui.d.ts +12 -9
  35. package/dist/Utils/segmentation/coreTools/gui.js +81 -107
  36. package/dist/Utils/segmentation/coreTools/gui.js.map +1 -1
  37. package/dist/Utils/segmentation/eventRouter/EventRouter.d.ts +2 -0
  38. package/dist/Utils/segmentation/eventRouter/EventRouter.js +19 -8
  39. package/dist/Utils/segmentation/eventRouter/EventRouter.js.map +1 -1
  40. package/dist/Utils/segmentation/eventRouter/types.d.ts +3 -3
  41. package/dist/Utils/segmentation/tools/BaseTool.d.ts +11 -5
  42. package/dist/Utils/segmentation/tools/BaseTool.js +1 -2
  43. package/dist/Utils/segmentation/tools/BaseTool.js.map +1 -1
  44. package/dist/Utils/segmentation/tools/CrosshairTool.js +25 -25
  45. package/dist/Utils/segmentation/tools/CrosshairTool.js.map +1 -1
  46. package/dist/Utils/segmentation/tools/DragSliceTool.d.ts +2 -0
  47. package/dist/Utils/segmentation/tools/DragSliceTool.js +63 -53
  48. package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -1
  49. package/dist/Utils/segmentation/tools/DrawingTool.d.ts +93 -0
  50. package/dist/Utils/segmentation/tools/DrawingTool.js +222 -0
  51. package/dist/Utils/segmentation/tools/DrawingTool.js.map +1 -0
  52. package/dist/Utils/segmentation/tools/EraserTool.js +8 -8
  53. package/dist/Utils/segmentation/tools/EraserTool.js.map +1 -1
  54. package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +12 -32
  55. package/dist/Utils/segmentation/tools/ImageStoreHelper.js +45 -102
  56. package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -1
  57. package/dist/Utils/segmentation/tools/PanTool.d.ts +53 -0
  58. package/dist/Utils/segmentation/tools/PanTool.js +90 -0
  59. package/dist/Utils/segmentation/tools/PanTool.js.map +1 -0
  60. package/dist/Utils/segmentation/tools/SphereTool.d.ts +208 -3
  61. package/dist/Utils/segmentation/tools/SphereTool.js +456 -45
  62. package/dist/Utils/segmentation/tools/SphereTool.js.map +1 -1
  63. package/dist/Utils/segmentation/tools/ZoomTool.js +5 -4
  64. package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -1
  65. package/dist/Utils/segmentation/tools/index.d.ts +6 -2
  66. package/dist/Utils/segmentation/tools/index.js +4 -1
  67. package/dist/Utils/segmentation/tools/index.js.map +1 -1
  68. package/dist/bundle.esm.js +2451 -1622
  69. package/dist/bundle.umd.js +2451 -1622
  70. package/dist/index.d.ts +6 -3
  71. package/dist/index.js +1 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/types/Utils/segmentation/CommToolsData.d.ts +32 -18
  74. package/dist/types/Utils/segmentation/DragOperator.d.ts +10 -2
  75. package/dist/types/Utils/segmentation/DrawToolCore.d.ts +24 -18
  76. package/dist/types/Utils/segmentation/NrrdTools.d.ts +152 -10
  77. package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +1 -34
  78. package/dist/types/Utils/segmentation/core/UndoManager.d.ts +1 -1
  79. package/dist/types/Utils/segmentation/core/index.d.ts +0 -2
  80. package/dist/types/Utils/segmentation/coreTools/GuiState.d.ts +22 -0
  81. package/dist/types/Utils/segmentation/coreTools/NrrdState.d.ts +25 -0
  82. package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +88 -65
  83. package/dist/types/Utils/segmentation/coreTools/gui.d.ts +12 -9
  84. package/dist/types/Utils/segmentation/eventRouter/EventRouter.d.ts +2 -0
  85. package/dist/types/Utils/segmentation/eventRouter/types.d.ts +3 -3
  86. package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +11 -5
  87. package/dist/types/Utils/segmentation/tools/DragSliceTool.d.ts +2 -0
  88. package/dist/types/Utils/segmentation/tools/DrawingTool.d.ts +93 -0
  89. package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +12 -32
  90. package/dist/types/Utils/segmentation/tools/PanTool.d.ts +53 -0
  91. package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +208 -3
  92. package/dist/types/Utils/segmentation/tools/index.d.ts +6 -2
  93. package/dist/types/index.d.ts +6 -3
  94. package/package.json +1 -1
  95. package/dist/Utils/segmentation/core/MigrationUtils.d.ts +0 -97
  96. package/dist/Utils/segmentation/core/MigrationUtils.js +0 -163
  97. package/dist/Utils/segmentation/core/MigrationUtils.js.map +0 -1
  98. package/dist/types/Utils/segmentation/core/MigrationUtils.d.ts +0 -97
@@ -1,11 +1,13 @@
1
1
  import { CommToolsData } from "./CommToolsData";
2
- import { switchEraserSize, switchPencilIcon } from "../utils";
2
+ import { switchPencilIcon } from "../utils";
3
3
  import { EventRouter } from "./eventRouter";
4
4
  import { SphereTool } from "./tools/SphereTool";
5
5
  import { CrosshairTool } from "./tools/CrosshairTool";
6
6
  import { ContrastTool } from "./tools/ContrastTool";
7
7
  import { ZoomTool } from "./tools/ZoomTool";
8
8
  import { EraserTool } from "./tools/EraserTool";
9
+ import { PanTool } from "./tools/PanTool";
10
+ import { DrawingTool } from "./tools/DrawingTool";
9
11
  import { ImageStoreHelper } from "./tools/ImageStoreHelper";
10
12
  import { UndoManager } from "./core";
11
13
  export class DrawToolCore extends CommToolsData {
@@ -37,12 +39,10 @@ export class DrawToolCore extends CommToolsData {
37
39
  this.eraserUrls = [];
38
40
  this.pencilUrls = [];
39
41
  this.undoManager = new UndoManager();
40
- /** Snapshot of the active layer's slice captured on mouse-down (before drawing). */
41
- this.preDrawSlice = null;
42
- this.preDrawAxis = "z";
43
- this.preDrawSliceIndex = 0;
44
42
  // Centralized event router
45
43
  this.eventRouter = null;
44
+ /** Slice index recorded when paintOnCanvas() starts, guards stale-click */
45
+ this.paintSliceIndex = 0;
46
46
  // need to return to parent
47
47
  this.start = () => { };
48
48
  /*************************************May consider to move outside *******************************************/
@@ -50,7 +50,7 @@ export class DrawToolCore extends CommToolsData {
50
50
  this.protectedData.ctxes.drawingCtx.beginPath();
51
51
  this.protectedData.ctxes.drawingCtx.moveTo(x1, y1);
52
52
  this.protectedData.ctxes.drawingCtx.lineTo(x2, y2);
53
- this.protectedData.ctxes.drawingCtx.strokeStyle = this.gui_states.color;
53
+ this.protectedData.ctxes.drawingCtx.strokeStyle = this.gui_states.drawing.color;
54
54
  this.protectedData.ctxes.drawingCtx.stroke();
55
55
  };
56
56
  this.configContrastDragMode = () => {
@@ -76,6 +76,7 @@ export class DrawToolCore extends CommToolsData {
76
76
  gui_states: this.gui_states,
77
77
  protectedData: this.protectedData,
78
78
  cursorPage: this.cursorPage,
79
+ callbacks: this.annotationCallbacks,
79
80
  };
80
81
  this.imageStoreHelper = new ImageStoreHelper(toolCtx, {
81
82
  setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
@@ -84,8 +85,6 @@ export class DrawToolCore extends CommToolsData {
84
85
  this.sphereTool = new SphereTool(toolCtx, {
85
86
  setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
86
87
  drawImageOnEmptyImage: (canvas) => this.drawImageOnEmptyImage(canvas),
87
- storeImageToAxis: (index, paintedImages, imageData, axis) => this.imageStoreHelper.storeImageToAxis(index, paintedImages, imageData, axis),
88
- createEmptyPaintImage: (dimensions, paintImages) => this.createEmptyPaintImage(dimensions, paintImages),
89
88
  });
90
89
  this.crosshairTool = new CrosshairTool(toolCtx);
91
90
  this.contrastTool = new ContrastTool(toolCtx, this.container, this.contrastEventPrameters, {
@@ -98,6 +97,18 @@ export class DrawToolCore extends CommToolsData {
98
97
  setIsDrawFalse: (target) => this.setIsDrawFalse(target),
99
98
  });
100
99
  this.eraserTool = new EraserTool(toolCtx);
100
+ this.panTool = new PanTool(toolCtx, {
101
+ zoomActionAfterDrawSphere: () => this.zoomActionAfterDrawSphere(),
102
+ });
103
+ this.drawingTool = new DrawingTool(toolCtx, {
104
+ setCurrentLayer: () => this.setCurrentLayer(),
105
+ compositeAllLayers: () => this.compositeAllLayers(),
106
+ syncLayerSliceData: (index, layer) => this.syncLayerSliceData(index, layer),
107
+ filterDrawedImage: (axis, index) => this.filterDrawedImage(axis, index),
108
+ getVolumeForLayer: (layer) => this.getVolumeForLayer(layer),
109
+ pushUndoDelta: (delta) => this.undoManager.push(delta),
110
+ getEraserUrls: () => this.eraserUrls,
111
+ });
101
112
  }
102
113
  initDrawToolCore() {
103
114
  // Initialize EventRouter for centralized event handling
@@ -108,40 +119,28 @@ export class DrawToolCore extends CommToolsData {
108
119
  // Use string comparison to avoid TypeScript narrowing issues
109
120
  const prev = prevMode;
110
121
  const next = newMode;
111
- // Sync EventRouter mode changes with existing state flags
112
- if (next === 'draw') {
113
- this.protectedData.Is_Shift_Pressed = true;
114
- this.nrrd_states.enableCursorChoose = false;
115
- }
116
- else if (prev === 'draw') {
117
- this.protectedData.Is_Shift_Pressed = false;
118
- }
119
122
  if (next === 'contrast') {
120
- this.protectedData.Is_Ctrl_Pressed = true;
121
- this.protectedData.Is_Shift_Pressed = false;
122
123
  this.configContrastDragMode();
123
124
  }
124
125
  else if (prev === 'contrast') {
125
- this.protectedData.Is_Ctrl_Pressed = false;
126
126
  this.removeContrastDragMode();
127
- this.gui_states.readyToUpdate = true;
127
+ this.gui_states.viewConfig.readyToUpdate = true;
128
128
  }
129
129
  if (next === 'crosshair') {
130
- this.nrrd_states.enableCursorChoose = true;
131
- this.protectedData.Is_Draw = false;
132
- }
133
- else if (prev === 'crosshair') {
134
- this.nrrd_states.enableCursorChoose = false;
130
+ this.protectedData.isDrawing = false;
135
131
  }
136
132
  }
137
133
  });
134
+ // Inject eventRouter into ToolContext so tools can query mode/state
135
+ const toolCtx = this.sphereTool['ctx'];
136
+ toolCtx.eventRouter = this.eventRouter;
138
137
  // Configure keyboard settings from class field
139
138
  this.eventRouter.setKeyboardSettings(this._keyboardSettings);
140
139
  // Track undo flag for Ctrl+Z handling
141
140
  let undoFlag = false;
142
141
  // Register keyboard handlers with EventRouter
143
142
  this.eventRouter.setKeydownHandler((ev) => {
144
- var _a;
143
+ var _a, _b, _c, _d;
145
144
  if (this._configKeyBoard)
146
145
  return;
147
146
  // Handle undo (Ctrl+Z)
@@ -155,40 +154,58 @@ export class DrawToolCore extends CommToolsData {
155
154
  if ((ev.ctrlKey || ev.metaKey) && (ev.key === redoKey || (ev.shiftKey && ev.key === undoKeyUpper))) {
156
155
  this.redoLastPainting();
157
156
  }
158
- // Handle crosshair toggle
157
+ // Handle crosshair toggle (allowed in drawing tools AND sphere mode)
159
158
  if (ev.key === this._keyboardSettings.crosshair) {
160
- if (!this.gui_states.sphere && !this.gui_states.calculator) {
161
- (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.toggleCrosshair();
162
- }
159
+ (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.toggleCrosshair();
163
160
  }
164
161
  // Handle draw mode (Shift key) - EventRouter already tracks this
165
- if (ev.key === this._keyboardSettings.draw && !this.gui_states.sphere && !this.gui_states.calculator) {
166
- if (this.protectedData.Is_Ctrl_Pressed) {
162
+ // EventRouter's handleKeyDown will enforce mutual exclusion
163
+ if (ev.key === this._keyboardSettings.draw && !this.gui_states.mode.sphere) {
164
+ if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCtrlHeld()) {
167
165
  return; // Ctrl takes priority
168
166
  }
169
167
  // EventRouter will set mode to 'draw' via internal handler
170
168
  }
169
+ // Handle sphere mode toggle
170
+ if (ev.key === this._keyboardSettings.sphere) {
171
+ // Block during draw mode or contrast mode
172
+ if (((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isShiftHeld()) || ((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.isCtrlHeld())) {
173
+ return;
174
+ }
175
+ this.gui_states.mode.sphere = !this.gui_states.mode.sphere;
176
+ if (this.gui_states.mode.sphere) {
177
+ this.enterSphereMode();
178
+ }
179
+ else {
180
+ this.exitSphereMode();
181
+ }
182
+ }
171
183
  });
172
184
  this.eventRouter.setKeyupHandler((ev) => {
173
- var _a, _b, _c, _d;
185
+ var _a, _b, _c, _d, _e, _f;
174
186
  if (this._configKeyBoard)
175
187
  return;
176
188
  // Handle Ctrl key release (contrast mode toggle)
177
189
  if (this._keyboardSettings.contrast.includes(ev.key)) {
178
190
  if (undoFlag) {
179
- this.gui_states.readyToUpdate = true;
191
+ this.gui_states.viewConfig.readyToUpdate = true;
180
192
  undoFlag = false;
181
193
  return;
182
194
  }
183
195
  // Skip mode toggle when contrast shortcut is disabled
184
196
  if (!((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()))
185
197
  return;
198
+ // Block contrast toggle during crosshair, draw, or sphere (mutual exclusion)
199
+ if (((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled()) || ((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.getMode()) === 'draw')
200
+ return;
201
+ if (this.gui_states.mode.sphere)
202
+ return;
186
203
  // Toggle contrast mode manually since it's on keyup
187
- if (((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.getMode()) !== 'contrast') {
188
- (_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.setMode('contrast');
204
+ if (((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.getMode()) !== 'contrast') {
205
+ (_e = this.eventRouter) === null || _e === void 0 ? void 0 : _e.setMode('contrast');
189
206
  }
190
207
  else {
191
- (_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.setMode('idle');
208
+ (_f = this.eventRouter) === null || _f === void 0 ? void 0 : _f.setMode('idle');
192
209
  }
193
210
  }
194
211
  });
@@ -200,78 +217,38 @@ export class DrawToolCore extends CommToolsData {
200
217
  }
201
218
  setPencilIconUrls(urls) {
202
219
  this.pencilUrls = urls;
203
- this.gui_states.defaultPaintCursor = switchPencilIcon("dot", this.pencilUrls);
220
+ this.gui_states.viewConfig.defaultPaintCursor = switchPencilIcon("dot", this.pencilUrls);
204
221
  this.protectedData.canvases.drawingCanvas.style.cursor =
205
- this.gui_states.defaultPaintCursor;
222
+ this.gui_states.viewConfig.defaultPaintCursor;
206
223
  }
207
224
  setCurrentLayer() {
208
- const layer = this.gui_states.layer;
225
+ const layer = this.gui_states.layerChannel.layer;
209
226
  let target = this.protectedData.layerTargets.get(layer);
210
227
  if (!target) {
211
228
  // Fallback to first layer
212
- const firstId = this.nrrd_states.layers[0];
229
+ const firstId = this.nrrd_states.image.layers[0];
213
230
  target = this.protectedData.layerTargets.get(firstId);
214
231
  }
215
232
  return { ctx: target.ctx, canvas: target.canvas };
216
233
  }
217
234
  draw(opts) {
218
235
  if (!!opts) {
219
- this.nrrd_states.getMask = opts === null || opts === void 0 ? void 0 : opts.getMaskData;
220
- if (opts === null || opts === void 0 ? void 0 : opts.onClearLayerVolume) {
221
- this.nrrd_states.onClearLayerVolume = opts.onClearLayerVolume;
236
+ // Phase 2: Store callbacks in annotationCallbacks instead of nrrd_states
237
+ if (opts.getMaskData) {
238
+ this.annotationCallbacks.onMaskChanged = opts.getMaskData;
239
+ }
240
+ if (opts.onClearLayerVolume) {
241
+ this.annotationCallbacks.onLayerVolumeCleared = opts.onClearLayerVolume;
242
+ }
243
+ if (opts.getSphereData) {
244
+ this.annotationCallbacks.onSphereChanged = opts.getSphereData;
245
+ }
246
+ if (opts.getCalculateSpherePositionsData) {
247
+ this.annotationCallbacks.onCalculatorPositionsChanged = opts.getCalculateSpherePositionsData;
222
248
  }
223
- this.nrrd_states.getSphere = opts === null || opts === void 0 ? void 0 : opts.getSphereData;
224
- this.nrrd_states.getCalculateSpherePositions = opts === null || opts === void 0 ? void 0 : opts.getCalculateSpherePositionsData;
225
249
  }
226
250
  this.paintOnCanvas();
227
251
  }
228
- drawCalSphereDown(x, y, sliceIndex, cal_position) {
229
- this.nrrd_states.sphereRadius = 5;
230
- this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
231
- let mouseX = x / this.nrrd_states.sizeFoctor;
232
- let mouseY = y / this.nrrd_states.sizeFoctor;
233
- // record mouseX,Y, and enable crosshair function
234
- this.nrrd_states.sphereOrigin[this.protectedData.axis] = [
235
- mouseX,
236
- mouseY,
237
- sliceIndex,
238
- ];
239
- this.setUpSphereOrigins(mouseX, mouseY, sliceIndex);
240
- // Note the sphere origin here is x, y, z
241
- // x: pixel x, y: pixel y, z: slice index (mm)
242
- switch (cal_position) {
243
- case "tumour":
244
- this.nrrd_states.tumourSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphereOrigin));
245
- break;
246
- case "skin":
247
- this.nrrd_states.skinSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphereOrigin));
248
- break;
249
- case "nipple":
250
- this.nrrd_states.nippleSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphereOrigin));
251
- break;
252
- case "ribcage":
253
- this.nrrd_states.ribSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphereOrigin));
254
- break;
255
- }
256
- this.nrrd_states.cursorPageX = mouseX;
257
- this.nrrd_states.cursorPageY = mouseY;
258
- this.enableCrosshair();
259
- // draw circle setup width/height for sphere canvas
260
- this.drawCalculatorSphere(this.nrrd_states.sphereRadius);
261
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
262
- }
263
- drawCalSphereUp() {
264
- // TODO send data to outside
265
- // this.clearStoreImages();
266
- this.clearSpherePrintStoreImages();
267
- this.drawCalculatorSphereOnEachViews("x");
268
- this.drawCalculatorSphereOnEachViews("y");
269
- this.drawCalculatorSphereOnEachViews("z");
270
- !!this.nrrd_states.getCalculateSpherePositions &&
271
- this.nrrd_states.getCalculateSpherePositions(this.nrrd_states.tumourSphereOrigin, this.nrrd_states.skinSphereOrigin, this.nrrd_states.ribSphereOrigin, this.nrrd_states.nippleSphereOrigin, this.protectedData.axis);
272
- this.zoomActionAfterDrawSphere();
273
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
274
- }
275
252
  zoomActionAfterDrawSphere() {
276
253
  this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
277
254
  }
@@ -280,28 +257,16 @@ export class DrawToolCore extends CommToolsData {
280
257
  }
281
258
  paintOnCanvas() {
282
259
  var _a, _b, _c;
283
- /**
284
- * drag paint panel
285
- */
286
- let leftclicked = false;
287
- let rightclicked = false;
288
- let panelMoveInnerX = 0;
289
- let panelMoveInnerY = 0;
290
- // todo
291
- // let currentSliceIndex = this.protectedData.mainPreSlices.index;
292
- let currentSliceIndex = this.protectedData.mainPreSlices.index;
293
- // draw lines starts position
294
- let Is_Painting = false;
295
- let lines = [];
296
- const clearArc = this.useEraser();
260
+ // Initialize tools for this paint cycle
261
+ this.drawingTool.reset(this.useEraser());
262
+ this.panTool.reset();
263
+ this.paintSliceIndex = this.protectedData.mainPreSlices.index;
297
264
  this.updateOriginAndChangedWH();
298
265
  this.initAllCanvas();
299
266
  (_a = this.protectedData.ctxes.displayCtx) === null || _a === void 0 ? void 0 : _a.save();
300
267
  this.flipDisplayImageByAxis();
301
- (_b = this.protectedData.ctxes.displayCtx) === null || _b === void 0 ? void 0 : _b.drawImage(this.protectedData.canvases.originCanvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
268
+ (_b = this.protectedData.ctxes.displayCtx) === null || _b === void 0 ? void 0 : _b.drawImage(this.protectedData.canvases.originCanvas, 0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
302
269
  (_c = this.protectedData.ctxes.displayCtx) === null || _c === void 0 ? void 0 : _c.restore();
303
- this.protectedData.previousDrawingImage =
304
- this.protectedData.ctxes.drawingCtx.getImageData(0, 0, this.protectedData.canvases.drawingCanvas.width, this.protectedData.canvases.drawingCanvas.height);
305
270
  // let a global variable to store the wheel move event
306
271
  // Remove existing listener before creating a new one to prevent leaks
307
272
  this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
@@ -318,124 +283,62 @@ export class DrawToolCore extends CommToolsData {
318
283
  });
319
284
  // sphere Wheel
320
285
  this.drawingPrameters.handleSphereWheel = this.configMouseSphereWheel();
321
- // pan move
322
- this.drawingPrameters.handleOnPanMouseMove = (e) => {
323
- this.protectedData.canvases.drawingCanvas.style.cursor = "grabbing";
324
- this.nrrd_states.previousPanelL = e.clientX - panelMoveInnerX;
325
- this.nrrd_states.previousPanelT = e.clientY - panelMoveInnerY;
326
- this.protectedData.canvases.displayCanvas.style.left =
327
- this.protectedData.canvases.drawingCanvas.style.left =
328
- this.nrrd_states.previousPanelL + "px";
329
- this.protectedData.canvases.displayCanvas.style.top =
330
- this.protectedData.canvases.drawingCanvas.style.top =
331
- this.nrrd_states.previousPanelT + "px";
332
- };
286
+ // pan move — now handled by PanTool (listeners managed internally)
333
287
  // brush circle move
334
288
  this.drawingPrameters.handleOnDrawingBrushCricleMove = (e) => {
335
289
  e.preventDefault();
336
- this.nrrd_states.Mouse_Over_x = e.offsetX;
337
- this.nrrd_states.Mouse_Over_y = e.offsetY;
338
- if (this.nrrd_states.Mouse_Over_x === undefined) {
339
- this.nrrd_states.Mouse_Over_x = e.clientX;
340
- this.nrrd_states.Mouse_Over_y = e.clientY;
290
+ this.nrrd_states.interaction.mouseOverX = e.offsetX;
291
+ this.nrrd_states.interaction.mouseOverY = e.offsetY;
292
+ if (this.nrrd_states.interaction.mouseOverX === undefined) {
293
+ this.nrrd_states.interaction.mouseOverX = e.clientX;
294
+ this.nrrd_states.interaction.mouseOverY = e.clientY;
341
295
  }
342
296
  if (e.type === "mouseout") {
343
- this.nrrd_states.Mouse_Over = false;
297
+ this.nrrd_states.interaction.mouseOver = false;
344
298
  this.protectedData.canvases.drawingCanvas.removeEventListener("mousemove", this.drawingPrameters.handleOnDrawingBrushCricleMove);
345
299
  }
346
300
  else if (e.type === "mouseover") {
347
- this.nrrd_states.Mouse_Over = true;
301
+ this.nrrd_states.interaction.mouseOver = true;
348
302
  this.protectedData.canvases.drawingCanvas.addEventListener("mousemove", this.drawingPrameters.handleOnDrawingBrushCricleMove);
349
303
  }
350
304
  };
351
- // drawing move
305
+ // drawing move — delegated to DrawingTool
352
306
  this.drawingPrameters.handleOnDrawingMouseMove = (e) => {
353
- this.protectedData.Is_Draw = true;
354
- if (Is_Painting) {
355
- if (this.gui_states.Eraser) {
356
- this.nrrd_states.stepClear = 1;
357
- // drawingCtx.clearRect(e.offsetX - 5, e.offsetY - 5, 25, 25);
358
- clearArc(e.offsetX, e.offsetY, this.gui_states.brushAndEraserSize);
359
- }
360
- else {
361
- lines.push({ x: e.offsetX, y: e.offsetY });
362
- this.paintOnCanvasLayer(e.offsetX, e.offsetY);
363
- }
364
- }
307
+ this.drawingTool.onPointerMove(e);
365
308
  };
366
309
  this.drawingPrameters.handleOnDrawingMouseDown = (e) => {
367
- if (leftclicked || rightclicked) {
310
+ var _a, _b, _c;
311
+ if (this.drawingTool.isActive || this.panTool.isActive) {
368
312
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
369
313
  this.protectedData.ctxes.drawingLayerMasterCtx.closePath();
370
314
  return;
371
315
  }
372
- // when switch slice, clear previousDrawingImage
373
- // todo
374
- if (currentSliceIndex !== this.protectedData.mainPreSlices.index) {
375
- this.protectedData.previousDrawingImage =
376
- this.protectedData.ctxes.emptyCtx.createImageData(1, 1);
377
- currentSliceIndex = this.protectedData.mainPreSlices.index;
316
+ if (this.paintSliceIndex !== this.protectedData.mainPreSlices.index) {
317
+ this.paintSliceIndex = this.protectedData.mainPreSlices.index;
378
318
  }
379
319
  // remove it when mouse click down
380
320
  this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
381
321
  if (e.button === 0) {
382
- if (this.protectedData.Is_Shift_Pressed) {
383
- leftclicked = true;
384
- lines = [];
385
- Is_Painting = true;
386
- this.protectedData.Is_Draw = true;
387
- if (this.gui_states.Eraser) {
388
- // this.protectedData.canvases.drawingCanvas.style.cursor =
389
- // "url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_48.png) 48 48, crosshair";
390
- this.eraserUrls.length > 0
391
- ? (this.protectedData.canvases.drawingCanvas.style.cursor =
392
- switchEraserSize(this.gui_states.brushAndEraserSize, this.eraserUrls))
393
- : (this.protectedData.canvases.drawingCanvas.style.cursor =
394
- switchEraserSize(this.gui_states.brushAndEraserSize));
395
- }
396
- else {
397
- this.protectedData.canvases.drawingCanvas.style.cursor =
398
- this.gui_states.defaultPaintCursor;
399
- }
400
- this.nrrd_states.drawStartPos.x = e.offsetX;
401
- this.nrrd_states.drawStartPos.y = e.offsetY;
402
- // Capture pre-draw slice snapshot for undo
403
- try {
404
- this.preDrawAxis = this.protectedData.axis;
405
- this.preDrawSliceIndex = this.nrrd_states.currentIndex;
406
- const vol = this.getVolumeForLayer(this.gui_states.layer);
407
- this.preDrawSlice = vol.getSliceUint8(this.preDrawSliceIndex, this.preDrawAxis).data.slice();
408
- }
409
- catch (_a) {
410
- this.preDrawSlice = null;
411
- }
322
+ if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw') {
323
+ this.drawingTool.onPointerDown(e);
412
324
  this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
413
325
  this.protectedData.canvases.drawingCanvas.addEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
414
326
  }
415
- else if (this.nrrd_states.enableCursorChoose) {
416
- this.nrrd_states.cursorPageX =
417
- e.offsetX / this.nrrd_states.sizeFoctor;
418
- this.nrrd_states.cursorPageY =
419
- e.offsetY / this.nrrd_states.sizeFoctor;
327
+ else if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled()) {
328
+ this.nrrd_states.interaction.cursorPageX =
329
+ e.offsetX / this.nrrd_states.view.sizeFactor;
330
+ this.nrrd_states.interaction.cursorPageY =
331
+ e.offsetY / this.nrrd_states.view.sizeFactor;
420
332
  this.enableCrosshair();
421
333
  this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
422
334
  }
423
- else if (this.gui_states.sphere && !this.nrrd_states.enableCursorChoose) {
424
- sphere(e);
425
- }
426
- else if (this.gui_states.calculator && !this.nrrd_states.enableCursorChoose) {
427
- this.drawCalSphereDown(e.offsetX, e.offsetY, this.nrrd_states.currentIndex, this.gui_states.cal_distance);
335
+ else if (this.gui_states.mode.sphere && !((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
336
+ this.handleSphereClick(e);
428
337
  }
429
338
  }
430
339
  else if (e.button === 2) {
431
- rightclicked = true;
432
- let offsetX = this.protectedData.canvases.drawingCanvas.offsetLeft;
433
- let offsetY = this.protectedData.canvases.drawingCanvas.offsetTop;
434
- panelMoveInnerX = e.clientX - offsetX;
435
- panelMoveInnerY = e.clientY - offsetY;
436
- this.protectedData.canvases.drawingCanvas.style.cursor = "grab";
340
+ this.panTool.onPointerDown(e);
437
341
  this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
438
- this.protectedData.canvases.drawingCanvas.addEventListener("pointermove", this.drawingPrameters.handleOnPanMouseMove);
439
342
  }
440
343
  else {
441
344
  return;
@@ -452,174 +355,65 @@ export class DrawToolCore extends CommToolsData {
452
355
  // Fallback for legacy mode without EventRouter
453
356
  this.protectedData.canvases.drawingCanvas.addEventListener("pointerdown", this.drawingPrameters.handleOnDrawingMouseDown, true);
454
357
  }
455
- const sphere = (e) => {
456
- // set sphere size
457
- this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
458
- let mouseX = e.offsetX / this.nrrd_states.sizeFoctor;
459
- let mouseY = e.offsetY / this.nrrd_states.sizeFoctor;
460
- // record mouseX,Y, and enable crosshair function
461
- this.nrrd_states.sphereOrigin[this.protectedData.axis] = [
462
- mouseX,
463
- mouseY,
464
- this.nrrd_states.currentIndex,
465
- ];
466
- this.setUpSphereOrigins(mouseX, mouseY, this.nrrd_states.currentIndex);
467
- this.nrrd_states.cursorPageX = mouseX;
468
- this.nrrd_states.cursorPageY = mouseY;
469
- this.enableCrosshair();
470
- // draw circle setup width/height for sphere canvas
471
- this.drawSphere(mouseX, mouseY, this.nrrd_states.sphereRadius);
472
- this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
473
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
474
- };
475
- const redrawPreviousImageToLayerCtx = (ctx) => {
476
- var _a;
477
- const tempPreImg = (_a = this.filterDrawedImage(this.protectedData.axis, this.nrrd_states.currentIndex)) === null || _a === void 0 ? void 0 : _a.image;
478
- this.protectedData.canvases.emptyCanvas.width =
479
- this.protectedData.canvases.emptyCanvas.width;
480
- if (tempPreImg) {
481
- this.protectedData.previousDrawingImage = tempPreImg;
482
- }
483
- this.protectedData.ctxes.emptyCtx.putImageData(tempPreImg, 0, 0);
484
- // No flip needed: MaskVolume stores in source coordinates (matching the
485
- // Three.js / layer canvas convention). The layer canvas is in screen
486
- // coordinates, which already match the source coordinate system.
487
- ctx.imageSmoothingEnabled = false;
488
- ctx.drawImage(this.protectedData.canvases.emptyCanvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
489
- };
490
358
  this.drawingPrameters.handleOnDrawingMouseUp = (e) => {
359
+ var _a, _b, _c;
491
360
  if (e.button === 0) {
492
- if (this.protectedData.Is_Shift_Pressed || Is_Painting) {
493
- leftclicked = false;
494
- let { ctx, canvas } = this.setCurrentLayer();
495
- ctx.closePath();
361
+ if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw' || this.drawingTool.painting) {
362
+ this.drawingTool.onPointerUp(e);
496
363
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
497
- if (!this.gui_states.Eraser) {
498
- if (this.gui_states.pencil) {
499
- // Clear only the current layer canvas (NOT master)
500
- canvas.width = canvas.width;
501
- // Redraw previous layer data from volume
502
- redrawPreviousImageToLayerCtx(ctx);
503
- // Draw new pencil strokes on current layer canvas
504
- ctx.beginPath();
505
- ctx.moveTo(lines[0].x, lines[0].y);
506
- for (let i = 1; i < lines.length; i++) {
507
- ctx.lineTo(lines[i].x, lines[i].y);
508
- }
509
- ctx.closePath();
510
- ctx.lineWidth = 1;
511
- ctx.fillStyle = this.gui_states.fillColor;
512
- ctx.fill();
513
- // Composite ALL layers to master (not just current layer)
514
- this.compositeAllLayers();
515
- }
516
- }
517
- this.protectedData.previousDrawingImage =
518
- this.protectedData.ctxes.drawingLayerMasterCtx.getImageData(0, 0, this.protectedData.canvases.drawingCanvasLayerMaster.width, this.protectedData.canvases.drawingCanvasLayerMaster.height);
519
- this.storeAllImages(this.nrrd_states.currentIndex, this.gui_states.layer);
520
- if (this.gui_states.Eraser) {
521
- const restLayers = this.getRestLayer();
522
- this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[0]);
523
- this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[1]);
524
- }
525
- Is_Painting = false;
526
- // Push delta to UndoManager (new Delta-based undo system)
527
- if (this.preDrawSlice) {
528
- try {
529
- const vol = this.getVolumeForLayer(this.gui_states.layer);
530
- const { data: newSlice } = vol.getSliceUint8(this.preDrawSliceIndex, this.preDrawAxis);
531
- const delta = {
532
- layerId: this.gui_states.layer,
533
- axis: this.preDrawAxis,
534
- sliceIndex: this.preDrawSliceIndex,
535
- oldSlice: this.preDrawSlice,
536
- newSlice: newSlice.slice(),
537
- };
538
- this.undoManager.push(delta);
539
- }
540
- catch (_a) {
541
- // Volume not ready — skip
542
- }
543
- this.preDrawSlice = null;
544
- }
545
364
  // add wheel after pointer up
546
365
  this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel, {
547
366
  passive: false,
548
367
  });
549
368
  }
550
- else if (this.gui_states.sphere &&
551
- !this.nrrd_states.enableCursorChoose) {
552
- // plan B
553
- // findout all index in the sphere radius range in Axial view
554
- if (this.nrrd_states.spherePlanB) {
555
- // clear stroe images
556
- // this.clearStoreImages();
557
- this.clearSpherePrintStoreImages();
558
- for (let i = 0; i < this.nrrd_states.sphereRadius; i++) {
559
- this.drawSphereOnEachViews(i, "x");
560
- this.drawSphereOnEachViews(i, "y");
561
- this.drawSphereOnEachViews(i, "z");
562
- }
563
- }
564
- !!this.nrrd_states.getSphere &&
565
- this.nrrd_states.getSphere(this.nrrd_states.sphereOrigin.z, this.nrrd_states.sphereRadius / this.nrrd_states.sizeFoctor);
369
+ else if (this.gui_states.mode.sphere &&
370
+ !((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
371
+ // Write all placed calculator spheres to volume
372
+ this.sphereTool.writeAllCalculatorSpheresToVolume();
373
+ // Render current slice from volume to sphere canvas
374
+ this.sphereTool.refreshSphereCanvas();
375
+ this.annotationCallbacks.onSphereChanged(this.nrrd_states.sphere.sphereOrigin.z, this.nrrd_states.sphere.sphereRadius / this.nrrd_states.view.sizeFactor);
376
+ this.annotationCallbacks.onCalculatorPositionsChanged(this.nrrd_states.sphere.tumourSphereOrigin, this.nrrd_states.sphere.skinSphereOrigin, this.nrrd_states.sphere.ribSphereOrigin, this.nrrd_states.sphere.nippleSphereOrigin, this.protectedData.axis);
566
377
  this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
567
378
  this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
568
379
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
380
+ this.zoomActionAfterDrawSphere();
569
381
  }
570
- else if ((this.gui_states.sphere || this.gui_states.calculator) &&
571
- this.nrrd_states.enableCursorChoose) {
382
+ else if (this.gui_states.mode.sphere &&
383
+ ((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
572
384
  this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
573
385
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
574
386
  }
575
- else if (this.gui_states.calculator &&
576
- !this.nrrd_states.enableCursorChoose) {
577
- // When mouse up
578
- this.drawCalSphereUp();
579
- }
580
387
  }
581
388
  else if (e.button === 2) {
582
- rightclicked = false;
583
- this.protectedData.canvases.drawingCanvas.style.cursor = "grab";
584
- setTimeout(() => {
585
- this.protectedData.canvases.drawingCanvas.style.cursor = this.gui_states.defaultPaintCursor;
586
- }, 2000);
587
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnPanMouseMove);
588
- if (this.gui_states.sphere || this.gui_states.calculator) {
589
- this.zoomActionAfterDrawSphere();
590
- }
389
+ this.panTool.onPointerUp(e, this.gui_states.viewConfig.defaultPaintCursor);
591
390
  }
592
391
  else {
593
392
  return;
594
393
  }
595
- if (!this.gui_states.pencil) {
394
+ if (!this.gui_states.mode.pencil) {
596
395
  this.setIsDrawFalse(100);
597
396
  }
598
397
  };
599
- this.protectedData.canvases.drawingCanvas.addEventListener("pointerleave", (e) => {
600
- Is_Painting = false;
601
- if (leftclicked) {
602
- leftclicked = false;
603
- this.protectedData.ctxes.drawingLayerMasterCtx.closePath();
398
+ this.protectedData.canvases.drawingCanvas.addEventListener("pointerleave", () => {
399
+ const wasDrawing = this.drawingTool.onPointerLeave();
400
+ if (wasDrawing) {
604
401
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
605
402
  this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
606
403
  }
607
- if (rightclicked) {
608
- rightclicked = false;
609
- this.protectedData.canvases.drawingCanvas.style.cursor = "grab";
610
- this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnPanMouseMove);
611
- }
404
+ this.panTool.onPointerLeave();
612
405
  this.setIsDrawFalse(100);
613
- if (this.gui_states.pencil) {
406
+ if (this.gui_states.mode.pencil) {
614
407
  this.setIsDrawFalse(1000);
615
408
  }
616
409
  });
617
410
  this.start = () => {
618
- if (this.gui_states.readyToUpdate) {
619
- this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
411
+ var _a, _b;
412
+ if (this.gui_states.viewConfig.readyToUpdate) {
413
+ this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
620
414
  this.protectedData.ctxes.drawingCtx.globalAlpha =
621
- this.gui_states.globalAlpha;
622
- if (this.protectedData.Is_Draw) {
415
+ this.gui_states.drawing.globalAlpha;
416
+ if (this.protectedData.isDrawing) {
623
417
  this.protectedData.ctxes.drawingLayerMasterCtx.lineCap = "round";
624
418
  this.protectedData.ctxes.drawingLayerMasterCtx.globalAlpha = 1;
625
419
  for (const [, target] of this.protectedData.layerTargets) {
@@ -628,63 +422,82 @@ export class DrawToolCore extends CommToolsData {
628
422
  }
629
423
  }
630
424
  else {
631
- if (this.protectedData.Is_Shift_Pressed) {
632
- if (!this.gui_states.pencil &&
633
- !this.gui_states.Eraser &&
634
- this.nrrd_states.Mouse_Over) {
635
- this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
425
+ // Use EventRouter mode for mutually exclusive crosshair vs draw rendering
426
+ const currentMode = (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode();
427
+ if (currentMode === 'draw') {
428
+ // Draw mode: show brush circle preview
429
+ if (!this.gui_states.mode.pencil &&
430
+ !this.gui_states.mode.eraser &&
431
+ this.nrrd_states.interaction.mouseOver) {
432
+ this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
636
433
  this.protectedData.ctxes.drawingCtx.fillStyle =
637
- this.gui_states.brushColor;
434
+ this.gui_states.drawing.brushColor;
638
435
  this.protectedData.ctxes.drawingCtx.beginPath();
639
- this.protectedData.ctxes.drawingCtx.arc(this.nrrd_states.Mouse_Over_x, this.nrrd_states.Mouse_Over_y, this.gui_states.brushAndEraserSize / 2 + 1, 0, Math.PI * 2);
436
+ this.protectedData.ctxes.drawingCtx.arc(this.nrrd_states.interaction.mouseOverX, this.nrrd_states.interaction.mouseOverY, this.gui_states.drawing.brushAndEraserSize / 2 + 1, 0, Math.PI * 2);
640
437
  this.protectedData.ctxes.drawingCtx.strokeStyle =
641
- this.gui_states.brushColor;
438
+ this.gui_states.drawing.brushColor;
642
439
  this.protectedData.ctxes.drawingCtx.stroke();
643
440
  }
644
441
  }
645
- if (this.nrrd_states.enableCursorChoose) {
646
- this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
647
- const ex = this.nrrd_states.cursorPageX * this.nrrd_states.sizeFoctor;
648
- const ey = this.nrrd_states.cursorPageY * this.nrrd_states.sizeFoctor;
649
- this.drawLine(ex, 0, ex, this.nrrd_states.changedWidth);
650
- this.drawLine(0, ey, this.nrrd_states.changedHeight, ey);
442
+ else if (currentMode === 'crosshair' || ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
443
+ // Crosshair mode: show red cross lines (mutually exclusive with draw)
444
+ this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
445
+ const ex = this.nrrd_states.interaction.cursorPageX * this.nrrd_states.view.sizeFactor;
446
+ const ey = this.nrrd_states.interaction.cursorPageY * this.nrrd_states.view.sizeFactor;
447
+ this.drawLine(ex, 0, ex, this.nrrd_states.view.changedWidth);
448
+ this.drawLine(0, ey, this.nrrd_states.view.changedHeight, ey);
651
449
  }
652
450
  }
653
- // globalAlpha was set to gui_states.globalAlpha at the top of start().
451
+ // globalAlpha was set to gui_states.drawing.globalAlpha at the top of start().
654
452
  // Master stores full-alpha pixels; transparency applied here only.
655
453
  this.protectedData.ctxes.drawingCtx.drawImage(this.protectedData.canvases.drawingCanvasLayerMaster, 0, 0);
454
+ // Draw sphere overlay from cached sphere canvas (separate from master).
455
+ // During preview: drawSphere/drawCalculatorSphere update the sphere canvas.
456
+ // After placement: refreshSphereCanvas renders from sphereMaskVolume.
457
+ if (this.gui_states.mode.sphere) {
458
+ this.protectedData.ctxes.drawingCtx.drawImage(this.protectedData.canvases.drawingSphereCanvas, 0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
459
+ }
656
460
  }
657
461
  else {
658
462
  this.redrawDisplayCanvas();
659
463
  }
660
464
  };
661
465
  }
662
- drawLinesOnLayer(ctx, x, y) {
663
- ctx.beginPath();
664
- ctx.moveTo(this.nrrd_states.drawStartPos.x, this.nrrd_states.drawStartPos.y);
665
- if (this.gui_states.pencil) {
666
- ctx.strokeStyle = this.gui_states.color;
667
- ctx.lineWidth = this.gui_states.lineWidth;
668
- }
669
- else {
670
- ctx.strokeStyle = this.gui_states.brushColor;
671
- ctx.lineWidth = this.gui_states.brushAndEraserSize;
466
+ /** Extracted from paintOnCanvas() — handles sphere placement on left-click */
467
+ handleSphereClick(e) {
468
+ this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
469
+ let mouseX = e.offsetX / this.nrrd_states.view.sizeFactor;
470
+ let mouseY = e.offsetY / this.nrrd_states.view.sizeFactor;
471
+ // record mouseX,Y, and enable crosshair function
472
+ this.nrrd_states.sphere.sphereOrigin[this.protectedData.axis] = [
473
+ mouseX,
474
+ mouseY,
475
+ this.nrrd_states.view.currentSliceIndex,
476
+ ];
477
+ this.setUpSphereOrigins(mouseX, mouseY, this.nrrd_states.view.currentSliceIndex);
478
+ // Store origin for the active sphere type
479
+ const calPos = this.gui_states.mode.activeSphereType;
480
+ switch (calPos) {
481
+ case "tumour":
482
+ this.nrrd_states.sphere.tumourSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
483
+ break;
484
+ case "skin":
485
+ this.nrrd_states.sphere.skinSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
486
+ break;
487
+ case "nipple":
488
+ this.nrrd_states.sphere.nippleSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
489
+ break;
490
+ case "ribcage":
491
+ this.nrrd_states.sphere.ribSphereOrigin = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
492
+ break;
672
493
  }
673
- ctx.lineTo(x, y);
674
- ctx.stroke();
675
- ctx.closePath();
676
- }
677
- paintOnCanvasLayer(x, y) {
678
- let { ctx, canvas } = this.setCurrentLayer();
679
- // Draw only on the current layer canvas (not master directly)
680
- this.drawLinesOnLayer(ctx, x, y);
681
- // Composite all layers to master to preserve other layers' data
682
- this.compositeAllLayers();
683
- // reset drawing start position to current position.
684
- this.nrrd_states.drawStartPos.x = x;
685
- this.nrrd_states.drawStartPos.y = y;
686
- // need to flag the map as needing updating.
687
- this.protectedData.mainPreSlices.mesh.material.map.needsUpdate = true;
494
+ this.nrrd_states.interaction.cursorPageX = mouseX;
495
+ this.nrrd_states.interaction.cursorPageY = mouseY;
496
+ this.enableCrosshair();
497
+ // draw circle setup width/height for sphere canvas
498
+ this.drawCalculatorSphere(this.nrrd_states.sphere.sphereRadius);
499
+ this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
500
+ this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
688
501
  }
689
502
  initAllCanvas() {
690
503
  /**
@@ -693,42 +506,42 @@ export class DrawToolCore extends CommToolsData {
693
506
  this.protectedData.canvases.displayCanvas.style.position = "absolute";
694
507
  this.protectedData.canvases.displayCanvas.style.zIndex = "9";
695
508
  this.protectedData.canvases.displayCanvas.width =
696
- this.nrrd_states.changedWidth;
509
+ this.nrrd_states.view.changedWidth;
697
510
  this.protectedData.canvases.displayCanvas.height =
698
- this.nrrd_states.changedHeight;
511
+ this.nrrd_states.view.changedHeight;
699
512
  /**
700
513
  * drawing canvas
701
514
  */
702
515
  this.protectedData.canvases.drawingCanvas.style.zIndex = "10";
703
516
  this.protectedData.canvases.drawingCanvas.style.position = "absolute";
704
517
  this.protectedData.canvases.drawingCanvas.width =
705
- this.nrrd_states.changedWidth;
518
+ this.nrrd_states.view.changedWidth;
706
519
  this.protectedData.canvases.drawingCanvas.height =
707
- this.nrrd_states.changedHeight;
520
+ this.nrrd_states.view.changedHeight;
708
521
  this.protectedData.canvases.drawingCanvas.style.cursor =
709
- this.gui_states.defaultPaintCursor;
522
+ this.gui_states.viewConfig.defaultPaintCursor;
710
523
  this.protectedData.canvases.drawingCanvas.oncontextmenu = () => false;
711
524
  /**
712
525
  * layer1
713
526
  * it should be hide, so we don't need to add it to mainAreaContainer
714
527
  */
715
528
  this.protectedData.canvases.drawingCanvasLayerMaster.width =
716
- this.nrrd_states.changedWidth;
529
+ this.nrrd_states.view.changedWidth;
717
530
  this.protectedData.canvases.drawingCanvasLayerMaster.height =
718
- this.nrrd_states.changedHeight;
531
+ this.nrrd_states.view.changedHeight;
719
532
  for (const [, target] of this.protectedData.layerTargets) {
720
- target.canvas.width = this.nrrd_states.changedWidth;
721
- target.canvas.height = this.nrrd_states.changedHeight;
533
+ target.canvas.width = this.nrrd_states.view.changedWidth;
534
+ target.canvas.height = this.nrrd_states.view.changedHeight;
722
535
  }
723
536
  /**
724
537
  * display and drawing canvas container
725
538
  */
726
- // this.mainAreaContainer.style.width = this.nrrd_states.changedWidth + "px";
727
- // this.mainAreaContainer.style.height = this.nrrd_states.changedHeight + "px";
539
+ // this.mainAreaContainer.style.width = this.nrrd_states.view.changedWidth + "px";
540
+ // this.mainAreaContainer.style.height = this.nrrd_states.view.changedHeight + "px";
728
541
  this.mainAreaContainer.style.width =
729
- this.nrrd_states.originWidth * 8 + "px";
542
+ this.nrrd_states.image.originWidth * 8 + "px";
730
543
  this.mainAreaContainer.style.height =
731
- this.nrrd_states.originHeight * 8 + "px";
544
+ this.nrrd_states.image.originHeight * 8 + "px";
732
545
  this.mainAreaContainer.appendChild(this.protectedData.canvases.displayCanvas);
733
546
  this.mainAreaContainer.appendChild(this.protectedData.canvases.drawingCanvas);
734
547
  }
@@ -776,6 +589,15 @@ export class DrawToolCore extends CommToolsData {
776
589
  drawSphere(mouseX, mouseY, radius) {
777
590
  this.sphereTool.drawSphere(mouseX, mouseY, radius);
778
591
  }
592
+ /**
593
+ * Refresh sphere canvas from sphereMaskVolume for the current slice/axis.
594
+ * Called after view changes (slice switch, zoom, contrast, axis switch).
595
+ */
596
+ refreshSphereOverlay() {
597
+ if (this.gui_states.mode.sphere) {
598
+ this.sphereTool.refreshSphereCanvas();
599
+ }
600
+ }
779
601
  /**
780
602
  * Convert cursor point between axis views.
781
603
  * Delegated to CrosshairTool.
@@ -788,44 +610,42 @@ export class DrawToolCore extends CommToolsData {
788
610
  }
789
611
  /****************************layer div controls****************************************************/
790
612
  getRestLayer() {
791
- const layers = this.nrrd_states.layers;
613
+ const layers = this.nrrd_states.image.layers;
792
614
  const restLayer = layers.filter((item) => {
793
- return item !== this.gui_states.layer;
615
+ return item !== this.gui_states.layerChannel.layer;
794
616
  });
795
617
  return restLayer;
796
618
  }
797
619
  /**************************** Undo/Redo functions (Phase 6 — Delta-based) ****************************/
798
620
  /**
799
- * Clear mask on current slice canvas.
621
+ * Clear the mask on the current slice canvas for the active layer ONLY.
622
+ *
623
+ * This method only clears the active layer's MaskVolume slice data for the
624
+ * currently viewed slice index. Other slices in the same layer remain intact.
625
+ * Other background layers are also left untouched.
626
+ * After clearing, all layer canvases are re-rendered from the `MaskVolume`
627
+ * to keep the visuals in sync.
800
628
  *
801
- * Phase 2: Clears the MaskVolume slice for all three layers,
802
- * re-stores, and notifies external via getMask callback with clearFlag=true.
803
- * Phase 6: Also records a MaskDelta for undo support.
629
+ * The operation is recorded to the UndoManager to allow for user rollback.
804
630
  */
805
- clearPaint() {
806
- this.protectedData.Is_Draw = true;
807
- this.resetLayerCanvas();
631
+ clearActiveSlice() {
632
+ this.protectedData.isDrawing = true;
808
633
  this.protectedData.canvases.originCanvas.width =
809
634
  this.protectedData.canvases.originCanvas.width;
810
635
  this.protectedData.mainPreSlices.repaint.call(this.protectedData.mainPreSlices);
811
- this.protectedData.previousDrawingImage =
812
- this.protectedData.ctxes.emptyCtx.createImageData(1, 1);
813
- // Phase 2 + 6: Clear volume slices and record undo delta
636
+ // Clear only the active layer's MaskVolume slice and record undo delta
814
637
  try {
815
638
  const axis = this.protectedData.axis;
816
- const idx = this.nrrd_states.currentIndex;
817
- const activeLayer = this.gui_states.layer;
639
+ const idx = this.nrrd_states.view.currentSliceIndex;
640
+ const activeLayer = this.gui_states.layerChannel.layer;
818
641
  const vol = this.getVolumeForLayer(activeLayer);
819
642
  // Capture old slice for undo before clearing
820
643
  const oldSlice = vol.getSliceUint8(idx, axis).data.slice();
821
- // Clear only the active layer (clear also clears all for canvas consistency)
822
- const { layer1, layer2, layer3 } = this.protectedData.maskData.volumes;
823
- layer1.clearSlice(idx, axis);
824
- layer2.clearSlice(idx, axis);
825
- layer3.clearSlice(idx, axis);
644
+ // Clear only the active layer's MaskVolume slice
645
+ vol.clearSlice(idx, axis);
826
646
  // New (all-zero) slice for undo newSlice
827
647
  const { data: newSlice, width, height } = vol.getSliceUint8(idx, axis);
828
- // Push clearPaint delta to UndoManager (supports undo)
648
+ // Push clearActiveSlice delta to UndoManager (supports undo)
829
649
  this.undoManager.push({
830
650
  layerId: activeLayer,
831
651
  axis,
@@ -834,19 +654,30 @@ export class DrawToolCore extends CommToolsData {
834
654
  newSlice: newSlice.slice(),
835
655
  });
836
656
  // Notify external that slice was cleared
837
- if (!this.nrrd_states.loadMaskJson && !this.gui_states.sphere && !this.gui_states.calculator) {
838
- const activeChannel = this.gui_states.activeChannel || 1;
839
- this.nrrd_states.getMask(newSlice, activeLayer, activeChannel, idx, axis, width, height, true // clearFlag = true
657
+ if (!this.nrrd_states.flags.loadingMaskData && !this.gui_states.mode.sphere) {
658
+ const activeChannel = this.gui_states.layerChannel.activeChannel || 1;
659
+ this.annotationCallbacks.onMaskChanged(newSlice, activeLayer, activeChannel, idx, axis, width, height, true // clearFlag = true
840
660
  );
841
661
  }
842
662
  }
843
663
  catch (_a) {
844
- // Volume not ready (1×1×1 placeholder) — continue with legacy path
664
+ // Volume not ready (1×1×1 placeholder)
665
+ }
666
+ // Re-render ALL layers from MaskVolume to canvas (rebuilds visuals from source of truth)
667
+ this.resetLayerCanvas();
668
+ const buffer = this.getOrCreateSliceBuffer(this.protectedData.axis);
669
+ if (buffer) {
670
+ const w = this.nrrd_states.view.changedWidth;
671
+ const h = this.nrrd_states.view.changedHeight;
672
+ for (const layerId of this.nrrd_states.image.layers) {
673
+ const target = this.protectedData.layerTargets.get(layerId);
674
+ if (!target)
675
+ continue;
676
+ target.ctx.clearRect(0, 0, w, h);
677
+ this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.view.currentSliceIndex, buffer, target.ctx, w, h);
678
+ }
845
679
  }
846
- this.storeAllImages(this.nrrd_states.currentIndex, this.gui_states.layer);
847
- const restLayers = this.getRestLayer();
848
- this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[0]);
849
- this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[1]);
680
+ this.compositeAllLayers();
850
681
  this.setIsDrawFalse(1000);
851
682
  }
852
683
  /**
@@ -865,14 +696,14 @@ export class DrawToolCore extends CommToolsData {
865
696
  catch (_a) {
866
697
  return; // Volume not ready
867
698
  }
868
- this.protectedData.Is_Draw = true;
869
- if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.currentIndex) {
699
+ this.protectedData.isDrawing = true;
700
+ if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.view.currentSliceIndex) {
870
701
  this.applyUndoRedoToCanvas(delta.layerId);
871
702
  }
872
- if (!this.nrrd_states.loadMaskJson) {
703
+ if (!this.nrrd_states.flags.loadingMaskData) {
873
704
  const { data: sliceData, width, height } = this.getVolumeForLayer(delta.layerId)
874
705
  .getSliceUint8(delta.sliceIndex, delta.axis);
875
- this.nrrd_states.getMask(sliceData, delta.layerId, this.gui_states.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
706
+ this.annotationCallbacks.onMaskChanged(sliceData, delta.layerId, this.gui_states.layerChannel.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
876
707
  }
877
708
  this.setIsDrawFalse(1000);
878
709
  }
@@ -892,14 +723,14 @@ export class DrawToolCore extends CommToolsData {
892
723
  catch (_a) {
893
724
  return; // Volume not ready
894
725
  }
895
- this.protectedData.Is_Draw = true;
896
- if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.currentIndex) {
726
+ this.protectedData.isDrawing = true;
727
+ if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.view.currentSliceIndex) {
897
728
  this.applyUndoRedoToCanvas(delta.layerId);
898
729
  }
899
- if (!this.nrrd_states.loadMaskJson) {
730
+ if (!this.nrrd_states.flags.loadingMaskData) {
900
731
  const { data: sliceData, width, height } = this.getVolumeForLayer(delta.layerId)
901
732
  .getSliceUint8(delta.sliceIndex, delta.axis);
902
- this.nrrd_states.getMask(sliceData, delta.layerId, this.gui_states.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
733
+ this.annotationCallbacks.onMaskChanged(sliceData, delta.layerId, this.gui_states.layerChannel.activeChannel || 1, delta.sliceIndex, delta.axis, width, height, false);
903
734
  }
904
735
  this.setIsDrawFalse(1000);
905
736
  }
@@ -910,7 +741,7 @@ export class DrawToolCore extends CommToolsData {
910
741
  applyUndoRedoToCanvas(layerId) {
911
742
  let target = this.protectedData.layerTargets.get(layerId);
912
743
  if (!target) {
913
- const firstId = this.nrrd_states.layers[0];
744
+ const firstId = this.nrrd_states.image.layers[0];
914
745
  target = this.protectedData.layerTargets.get(firstId);
915
746
  }
916
747
  const { ctx, canvas } = target;
@@ -918,26 +749,14 @@ export class DrawToolCore extends CommToolsData {
918
749
  canvas.width = canvas.width;
919
750
  const buffer = this.getOrCreateSliceBuffer(this.protectedData.axis);
920
751
  if (buffer) {
921
- this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.currentIndex, buffer, ctx, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
752
+ this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.view.currentSliceIndex, buffer, ctx, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
922
753
  }
923
754
  // Re-composite all layers to master
924
755
  this.compositeAllLayers();
925
- // Update previousDrawingImage from master
926
- this.protectedData.previousDrawingImage =
927
- this.protectedData.ctxes.drawingLayerMasterCtx.getImageData(0, 0, this.protectedData.canvases.drawingCanvasLayerMaster.width, this.protectedData.canvases.drawingCanvasLayerMaster.height);
928
756
  }
929
757
  /****************************Store images (delegated to ImageStoreHelper)****************************************************/
930
- storeImageToAxis(index, paintedImages, imageData, axis) {
931
- this.imageStoreHelper.storeImageToAxis(index, paintedImages, imageData, axis);
932
- }
933
- storeAllImages(index, layer) {
934
- this.imageStoreHelper.storeAllImages(index, layer);
935
- }
936
- storeImageToLayer(index, canvas, paintedImages) {
937
- return this.imageStoreHelper.storeImageToLayer(index, canvas, paintedImages);
938
- }
939
- storeEachLayerImage(index, layer) {
940
- this.imageStoreHelper.storeEachLayerImage(index, layer);
758
+ syncLayerSliceData(index, layer) {
759
+ this.imageStoreHelper.syncLayerSliceData(index, layer);
941
760
  }
942
761
  /******************************** Utils gui related functions (delegated to ContrastTool) ***************************************/
943
762
  setupConrastEvents(callback) {