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