copper3d 2.2.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1056 -5
- package/dist/Scene/copperScene.d.ts +3 -0
- package/dist/Scene/copperScene.js +13 -0
- package/dist/Scene/copperScene.js.map +1 -1
- package/dist/Utils/segmentation/CommToolsData.d.ts +113 -11
- package/dist/Utils/segmentation/CommToolsData.js +314 -59
- package/dist/Utils/segmentation/CommToolsData.js.map +1 -1
- package/dist/Utils/segmentation/DragOperator.d.ts +13 -7
- package/dist/Utils/segmentation/DragOperator.js +51 -165
- package/dist/Utils/segmentation/DragOperator.js.map +1 -1
- package/dist/Utils/segmentation/DrawToolCore.d.ts +56 -76
- package/dist/Utils/segmentation/DrawToolCore.js +383 -891
- package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
- package/dist/Utils/segmentation/NrrdTools.d.ts +277 -15
- package/dist/Utils/segmentation/NrrdTools.js +600 -234
- package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
- package/dist/Utils/segmentation/core/MaskVolume.d.ts +444 -0
- package/dist/Utils/segmentation/core/MaskVolume.js +1117 -0
- package/dist/Utils/segmentation/core/MaskVolume.js.map +1 -0
- package/dist/Utils/segmentation/core/MigrationUtils.d.ts +97 -0
- package/dist/Utils/segmentation/core/MigrationUtils.js +163 -0
- package/dist/Utils/segmentation/core/MigrationUtils.js.map +1 -0
- package/dist/Utils/segmentation/core/UndoManager.d.ts +47 -0
- package/dist/Utils/segmentation/core/UndoManager.js +86 -0
- package/dist/Utils/segmentation/core/UndoManager.js.map +1 -0
- package/dist/Utils/segmentation/core/index.d.ts +76 -0
- package/dist/Utils/segmentation/core/index.js +75 -0
- package/dist/Utils/segmentation/core/index.js.map +1 -0
- package/dist/Utils/segmentation/core/types.d.ts +106 -0
- package/dist/Utils/segmentation/core/types.js +98 -0
- package/dist/Utils/segmentation/core/types.js.map +1 -0
- package/dist/Utils/segmentation/coreTools/coreType.d.ts +55 -39
- package/dist/Utils/segmentation/coreTools/coreType.js.map +1 -1
- package/dist/Utils/segmentation/coreTools/gui.d.ts +10 -17
- package/dist/Utils/segmentation/coreTools/gui.js +23 -27
- package/dist/Utils/segmentation/coreTools/gui.js.map +1 -1
- package/dist/Utils/segmentation/eventRouter/EventRouter.d.ts +145 -0
- package/dist/Utils/segmentation/eventRouter/EventRouter.js +375 -0
- package/dist/Utils/segmentation/eventRouter/EventRouter.js.map +1 -0
- package/dist/Utils/segmentation/eventRouter/index.d.ts +7 -0
- package/dist/Utils/segmentation/eventRouter/index.js +7 -0
- package/dist/Utils/segmentation/eventRouter/index.js.map +1 -0
- package/dist/Utils/segmentation/eventRouter/types.d.ts +70 -0
- package/dist/Utils/segmentation/eventRouter/types.js +7 -0
- package/dist/Utils/segmentation/eventRouter/types.js.map +1 -0
- package/dist/Utils/segmentation/tools/BaseTool.d.ts +28 -0
- package/dist/Utils/segmentation/tools/BaseTool.js +18 -0
- package/dist/Utils/segmentation/tools/BaseTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/ContrastTool.d.ts +26 -0
- package/dist/Utils/segmentation/tools/ContrastTool.js +118 -0
- package/dist/Utils/segmentation/tools/ContrastTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/CrosshairTool.d.ts +30 -0
- package/dist/Utils/segmentation/tools/CrosshairTool.js +157 -0
- package/dist/Utils/segmentation/tools/CrosshairTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/DragSliceTool.d.ts +44 -0
- package/dist/Utils/segmentation/tools/DragSliceTool.js +170 -0
- package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/EraserTool.d.ts +16 -0
- package/dist/Utils/segmentation/tools/EraserTool.js +92 -0
- package/dist/Utils/segmentation/tools/EraserTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +62 -0
- package/dist/Utils/segmentation/tools/ImageStoreHelper.js +160 -0
- package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -0
- package/dist/Utils/segmentation/tools/SphereTool.d.ts +36 -0
- package/dist/Utils/segmentation/tools/SphereTool.js +193 -0
- package/dist/Utils/segmentation/tools/SphereTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/ZoomTool.d.ts +20 -0
- package/dist/Utils/segmentation/tools/ZoomTool.js +60 -0
- package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/index.d.ts +22 -0
- package/dist/Utils/segmentation/tools/index.js +23 -0
- package/dist/Utils/segmentation/tools/index.js.map +1 -0
- package/dist/bundle.esm.js +4627 -1997
- package/dist/bundle.umd.js +4630 -1996
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/types/Renderer/copperRenderer.d.ts +1 -1
- package/dist/types/Renderer/copperRendererOnDemond.d.ts +1 -1
- package/dist/types/Scene/copperScene.d.ts +3 -0
- package/dist/types/Utils/segmentation/CommToolsData.d.ts +113 -11
- package/dist/types/Utils/segmentation/DragOperator.d.ts +13 -7
- package/dist/types/Utils/segmentation/DrawToolCore.d.ts +56 -76
- package/dist/types/Utils/segmentation/NrrdTools.d.ts +277 -15
- package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +444 -0
- package/dist/types/Utils/segmentation/core/MigrationUtils.d.ts +97 -0
- package/dist/types/Utils/segmentation/core/UndoManager.d.ts +47 -0
- package/dist/types/Utils/segmentation/core/index.d.ts +76 -0
- package/dist/types/Utils/segmentation/core/types.d.ts +106 -0
- package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +55 -39
- package/dist/types/Utils/segmentation/coreTools/gui.d.ts +10 -17
- package/dist/types/Utils/segmentation/eventRouter/EventRouter.d.ts +145 -0
- package/dist/types/Utils/segmentation/eventRouter/index.d.ts +7 -0
- package/dist/types/Utils/segmentation/eventRouter/types.d.ts +70 -0
- package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +28 -0
- package/dist/types/Utils/segmentation/tools/ContrastTool.d.ts +26 -0
- package/dist/types/Utils/segmentation/tools/CrosshairTool.d.ts +30 -0
- package/dist/types/Utils/segmentation/tools/DragSliceTool.d.ts +44 -0
- package/dist/types/Utils/segmentation/tools/EraserTool.d.ts +16 -0
- package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +62 -0
- package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +36 -0
- package/dist/types/Utils/segmentation/tools/ZoomTool.d.ts +20 -0
- package/dist/types/Utils/segmentation/tools/index.d.ts +22 -0
- package/dist/types/index.d.ts +5 -3
- package/dist/types/types/types.d.ts +3 -3
- package/dist/types/types.d.ts +3 -3
- package/package.json +1 -1
- package/dist/Utils/segmentation/coreTools/archive.d.ts +0 -1
- package/dist/Utils/segmentation/coreTools/archive.js +0 -23
- package/dist/Utils/segmentation/coreTools/archive.js.map +0 -1
- package/dist/types/Utils/segmentation/coreTools/archive.d.ts +0 -1
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { CommToolsData } from "./CommToolsData";
|
|
2
|
-
import { switchEraserSize, switchPencilIcon
|
|
2
|
+
import { switchEraserSize, switchPencilIcon } from "../utils";
|
|
3
|
+
import { EventRouter } from "./eventRouter";
|
|
4
|
+
import { SphereTool } from "./tools/SphereTool";
|
|
5
|
+
import { CrosshairTool } from "./tools/CrosshairTool";
|
|
6
|
+
import { ContrastTool } from "./tools/ContrastTool";
|
|
7
|
+
import { ZoomTool } from "./tools/ZoomTool";
|
|
8
|
+
import { EraserTool } from "./tools/EraserTool";
|
|
9
|
+
import { ImageStoreHelper } from "./tools/ImageStoreHelper";
|
|
10
|
+
import { UndoManager } from "./core";
|
|
3
11
|
export class DrawToolCore extends CommToolsData {
|
|
4
|
-
constructor(container) {
|
|
12
|
+
constructor(container, options) {
|
|
5
13
|
const mainAreaContainer = document.createElement("div");
|
|
6
|
-
super(container, mainAreaContainer);
|
|
14
|
+
super(container, mainAreaContainer, options);
|
|
7
15
|
this.drawingPrameters = {
|
|
8
16
|
handleOnDrawingMouseDown: (ev) => { },
|
|
9
17
|
handleOnDrawingMouseMove: (ev) => { },
|
|
@@ -28,7 +36,13 @@ export class DrawToolCore extends CommToolsData {
|
|
|
28
36
|
};
|
|
29
37
|
this.eraserUrls = [];
|
|
30
38
|
this.pencilUrls = [];
|
|
31
|
-
this.
|
|
39
|
+
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
|
+
// Centralized event router
|
|
45
|
+
this.eventRouter = null;
|
|
32
46
|
// need to return to parent
|
|
33
47
|
this.start = () => { };
|
|
34
48
|
/*************************************May consider to move outside *******************************************/
|
|
@@ -40,69 +54,137 @@ export class DrawToolCore extends CommToolsData {
|
|
|
40
54
|
this.protectedData.ctxes.drawingCtx.stroke();
|
|
41
55
|
};
|
|
42
56
|
this.configContrastDragMode = () => {
|
|
43
|
-
this.
|
|
44
|
-
this.container.addEventListener("pointerdown", this.contrastEventPrameters.handleOnContrastMouseDown, true);
|
|
45
|
-
this.container.addEventListener("pointerup", this.contrastEventPrameters.handleOnContrastMouseUp, true);
|
|
57
|
+
this.contrastTool.configContrastDragMode();
|
|
46
58
|
};
|
|
47
59
|
this.removeContrastDragMode = () => {
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
this.contrastTool.removeContrastDragMode();
|
|
61
|
+
};
|
|
62
|
+
this.updateSlicesContrast = (value, flag) => {
|
|
63
|
+
this.contrastTool.updateSlicesContrast(value, flag);
|
|
64
|
+
};
|
|
65
|
+
this.repraintCurrentContrastSlice = () => {
|
|
66
|
+
this.contrastTool.repraintCurrentContrastSlice();
|
|
54
67
|
};
|
|
55
68
|
this.container = container;
|
|
56
69
|
this.mainAreaContainer = mainAreaContainer;
|
|
70
|
+
this.initTools();
|
|
57
71
|
this.initDrawToolCore();
|
|
58
72
|
}
|
|
73
|
+
initTools() {
|
|
74
|
+
const toolCtx = {
|
|
75
|
+
nrrd_states: this.nrrd_states,
|
|
76
|
+
gui_states: this.gui_states,
|
|
77
|
+
protectedData: this.protectedData,
|
|
78
|
+
cursorPage: this.cursorPage,
|
|
79
|
+
};
|
|
80
|
+
this.imageStoreHelper = new ImageStoreHelper(toolCtx, {
|
|
81
|
+
setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
|
|
82
|
+
drawImageOnEmptyImage: (canvas) => this.drawImageOnEmptyImage(canvas),
|
|
83
|
+
});
|
|
84
|
+
this.sphereTool = new SphereTool(toolCtx, {
|
|
85
|
+
setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
|
|
86
|
+
drawImageOnEmptyImage: (canvas) => this.drawImageOnEmptyImage(canvas),
|
|
87
|
+
storeImageToAxis: (index, imageData, axis) => this.imageStoreHelper.storeImageToAxis(index, imageData, axis),
|
|
88
|
+
});
|
|
89
|
+
this.crosshairTool = new CrosshairTool(toolCtx);
|
|
90
|
+
this.contrastTool = new ContrastTool(toolCtx, this.container, this.contrastEventPrameters, {
|
|
91
|
+
setIsDrawFalse: (target) => this.setIsDrawFalse(target),
|
|
92
|
+
setSyncsliceNum: () => this.setSyncsliceNum(),
|
|
93
|
+
});
|
|
94
|
+
this.zoomTool = new ZoomTool(toolCtx, this.container, this.mainAreaContainer, {
|
|
95
|
+
resetPaintAreaUIPosition: (l, t) => this.resetPaintAreaUIPosition(l, t),
|
|
96
|
+
resizePaintArea: (moveDistance) => this.resizePaintArea(moveDistance),
|
|
97
|
+
setIsDrawFalse: (target) => this.setIsDrawFalse(target),
|
|
98
|
+
});
|
|
99
|
+
this.eraserTool = new EraserTool(toolCtx);
|
|
100
|
+
}
|
|
59
101
|
initDrawToolCore() {
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
102
|
+
// Initialize EventRouter for centralized event handling
|
|
103
|
+
this.eventRouter = new EventRouter({
|
|
104
|
+
container: this.container,
|
|
105
|
+
canvas: this.protectedData.canvases.drawingCanvas,
|
|
106
|
+
onModeChange: (prevMode, newMode) => {
|
|
107
|
+
// Use string comparison to avoid TypeScript narrowing issues
|
|
108
|
+
const prev = prevMode;
|
|
109
|
+
const next = newMode;
|
|
110
|
+
if (next === 'contrast') {
|
|
111
|
+
this.configContrastDragMode();
|
|
112
|
+
}
|
|
113
|
+
else if (prev === 'contrast') {
|
|
114
|
+
this.removeContrastDragMode();
|
|
115
|
+
this.gui_states.readyToUpdate = true;
|
|
116
|
+
}
|
|
117
|
+
if (next === 'crosshair') {
|
|
118
|
+
this.protectedData.Is_Draw = false;
|
|
68
119
|
}
|
|
69
|
-
this.protectedData.Is_Shift_Pressed = true;
|
|
70
|
-
this.nrrd_states.enableCursorChoose = false;
|
|
71
|
-
}
|
|
72
|
-
if (ev.key === this.nrrd_states.keyboardSettings.crosshair) {
|
|
73
|
-
this.protectedData.Is_Draw = false;
|
|
74
|
-
this.nrrd_states.enableCursorChoose =
|
|
75
|
-
!this.nrrd_states.enableCursorChoose;
|
|
76
120
|
}
|
|
77
|
-
|
|
121
|
+
});
|
|
122
|
+
// Inject eventRouter into ToolContext so tools can query mode/state
|
|
123
|
+
const toolCtx = this.sphereTool['ctx'];
|
|
124
|
+
toolCtx.eventRouter = this.eventRouter;
|
|
125
|
+
// Configure keyboard settings from class field
|
|
126
|
+
this.eventRouter.setKeyboardSettings(this._keyboardSettings);
|
|
127
|
+
// Track undo flag for Ctrl+Z handling
|
|
128
|
+
let undoFlag = false;
|
|
129
|
+
// Register keyboard handlers with EventRouter
|
|
130
|
+
this.eventRouter.setKeydownHandler((ev) => {
|
|
131
|
+
var _a, _b;
|
|
132
|
+
if (this._configKeyBoard)
|
|
133
|
+
return;
|
|
134
|
+
// Handle undo (Ctrl+Z)
|
|
135
|
+
if ((ev.ctrlKey || ev.metaKey) && ev.key === this._keyboardSettings.undo) {
|
|
78
136
|
undoFlag = true;
|
|
79
137
|
this.undoLastPainting();
|
|
80
138
|
}
|
|
139
|
+
// Handle redo (Ctrl+<redo key> or Ctrl+Shift+<undo key>)
|
|
140
|
+
const redoKey = this._keyboardSettings.redo;
|
|
141
|
+
const undoKeyUpper = this._keyboardSettings.undo.toUpperCase();
|
|
142
|
+
if ((ev.ctrlKey || ev.metaKey) && (ev.key === redoKey || (ev.shiftKey && ev.key === undoKeyUpper))) {
|
|
143
|
+
this.redoLastPainting();
|
|
144
|
+
}
|
|
145
|
+
// Handle crosshair toggle
|
|
146
|
+
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
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Handle draw mode (Shift key) - EventRouter already tracks this
|
|
152
|
+
// EventRouter's handleKeyDown will enforce mutual exclusion
|
|
153
|
+
if (ev.key === this._keyboardSettings.draw && !this.gui_states.sphere && !this.gui_states.calculator) {
|
|
154
|
+
if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCtrlHeld()) {
|
|
155
|
+
return; // Ctrl takes priority
|
|
156
|
+
}
|
|
157
|
+
// EventRouter will set mode to 'draw' via internal handler
|
|
158
|
+
}
|
|
81
159
|
});
|
|
82
|
-
this.
|
|
83
|
-
|
|
160
|
+
this.eventRouter.setKeyupHandler((ev) => {
|
|
161
|
+
var _a, _b, _c, _d, _e, _f;
|
|
162
|
+
if (this._configKeyBoard)
|
|
84
163
|
return;
|
|
85
|
-
|
|
164
|
+
// Handle Ctrl key release (contrast mode toggle)
|
|
165
|
+
if (this._keyboardSettings.contrast.includes(ev.key)) {
|
|
86
166
|
if (undoFlag) {
|
|
87
167
|
this.gui_states.readyToUpdate = true;
|
|
88
168
|
undoFlag = false;
|
|
89
169
|
return;
|
|
90
170
|
}
|
|
91
|
-
//
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
171
|
+
// Skip mode toggle when contrast shortcut is disabled
|
|
172
|
+
if (!((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()))
|
|
173
|
+
return;
|
|
174
|
+
// Block contrast toggle during crosshair or draw (mutual exclusion)
|
|
175
|
+
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
|
+
return;
|
|
177
|
+
// Toggle contrast mode manually since it's on keyup
|
|
178
|
+
if (((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.getMode()) !== 'contrast') {
|
|
179
|
+
(_e = this.eventRouter) === null || _e === void 0 ? void 0 : _e.setMode('contrast');
|
|
96
180
|
}
|
|
97
181
|
else {
|
|
98
|
-
this.
|
|
99
|
-
this.gui_states.readyToUpdate = true;
|
|
182
|
+
(_f = this.eventRouter) === null || _f === void 0 ? void 0 : _f.setMode('idle');
|
|
100
183
|
}
|
|
101
184
|
}
|
|
102
|
-
if (ev.key === this.nrrd_states.keyboardSettings.draw) {
|
|
103
|
-
this.protectedData.Is_Shift_Pressed = false;
|
|
104
|
-
}
|
|
105
185
|
});
|
|
186
|
+
// Bind all event listeners
|
|
187
|
+
this.eventRouter.bindAll();
|
|
106
188
|
}
|
|
107
189
|
setEraserUrls(urls) {
|
|
108
190
|
this.eraserUrls = urls;
|
|
@@ -114,31 +196,21 @@ export class DrawToolCore extends CommToolsData {
|
|
|
114
196
|
this.gui_states.defaultPaintCursor;
|
|
115
197
|
}
|
|
116
198
|
setCurrentLayer() {
|
|
117
|
-
|
|
118
|
-
let
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
break;
|
|
124
|
-
case "label2":
|
|
125
|
-
ctx = this.protectedData.ctxes.drawingLayerTwoCtx;
|
|
126
|
-
canvas = this.protectedData.canvases.drawingCanvasLayerTwo;
|
|
127
|
-
break;
|
|
128
|
-
case "label3":
|
|
129
|
-
ctx = this.protectedData.ctxes.drawingLayerThreeCtx;
|
|
130
|
-
canvas = this.protectedData.canvases.drawingCanvasLayerThree;
|
|
131
|
-
break;
|
|
132
|
-
default:
|
|
133
|
-
ctx = this.protectedData.ctxes.drawingLayerOneCtx;
|
|
134
|
-
canvas = this.protectedData.canvases.drawingCanvasLayerOne;
|
|
135
|
-
break;
|
|
199
|
+
const layer = this.gui_states.layer;
|
|
200
|
+
let target = this.protectedData.layerTargets.get(layer);
|
|
201
|
+
if (!target) {
|
|
202
|
+
// Fallback to first layer
|
|
203
|
+
const firstId = this.nrrd_states.layers[0];
|
|
204
|
+
target = this.protectedData.layerTargets.get(firstId);
|
|
136
205
|
}
|
|
137
|
-
return { ctx, canvas };
|
|
206
|
+
return { ctx: target.ctx, canvas: target.canvas };
|
|
138
207
|
}
|
|
139
208
|
draw(opts) {
|
|
140
209
|
if (!!opts) {
|
|
141
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;
|
|
213
|
+
}
|
|
142
214
|
this.nrrd_states.getSphere = opts === null || opts === void 0 ? void 0 : opts.getSphereData;
|
|
143
215
|
this.nrrd_states.getCalculateSpherePositions = opts === null || opts === void 0 ? void 0 : opts.getCalculateSpherePositionsData;
|
|
144
216
|
}
|
|
@@ -195,10 +267,7 @@ export class DrawToolCore extends CommToolsData {
|
|
|
195
267
|
this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
196
268
|
}
|
|
197
269
|
clearSpherePrintStoreImages() {
|
|
198
|
-
this.
|
|
199
|
-
this.protectedData.maskData.paintImages.y.length = 0;
|
|
200
|
-
this.protectedData.maskData.paintImages.z.length = 0;
|
|
201
|
-
this.createEmptyPaintImage(this.nrrd_states.dimensions, this.protectedData.maskData.paintImages);
|
|
270
|
+
this.sphereTool.clearSpherePrintStoreImages();
|
|
202
271
|
}
|
|
203
272
|
paintOnCanvas() {
|
|
204
273
|
var _a, _b, _c;
|
|
@@ -225,13 +294,16 @@ export class DrawToolCore extends CommToolsData {
|
|
|
225
294
|
this.protectedData.previousDrawingImage =
|
|
226
295
|
this.protectedData.ctxes.drawingCtx.getImageData(0, 0, this.protectedData.canvases.drawingCanvas.width, this.protectedData.canvases.drawingCanvas.height);
|
|
227
296
|
// let a global variable to store the wheel move event
|
|
228
|
-
|
|
297
|
+
// Remove existing listener before creating a new one to prevent leaks
|
|
298
|
+
this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
299
|
+
if (this._keyboardSettings.mouseWheel === "Scroll:Zoom") {
|
|
229
300
|
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseZoomWheel();
|
|
230
301
|
}
|
|
231
302
|
else {
|
|
232
303
|
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseSliceWheel();
|
|
233
304
|
}
|
|
234
|
-
//
|
|
305
|
+
// Keep wheel as direct addEventListener due to dynamic add/remove patterns in handleOnDrawingMouseUp
|
|
306
|
+
// EventRouter routing would conflict with the dynamic wheel switching (zoom vs sphere wheel)
|
|
235
307
|
this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel, {
|
|
236
308
|
passive: false,
|
|
237
309
|
});
|
|
@@ -283,6 +355,7 @@ export class DrawToolCore extends CommToolsData {
|
|
|
283
355
|
}
|
|
284
356
|
};
|
|
285
357
|
this.drawingPrameters.handleOnDrawingMouseDown = (e) => {
|
|
358
|
+
var _a, _b, _c, _d;
|
|
286
359
|
if (leftclicked || rightclicked) {
|
|
287
360
|
this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
|
|
288
361
|
this.protectedData.ctxes.drawingLayerMasterCtx.closePath();
|
|
@@ -298,7 +371,7 @@ export class DrawToolCore extends CommToolsData {
|
|
|
298
371
|
// remove it when mouse click down
|
|
299
372
|
this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
300
373
|
if (e.button === 0) {
|
|
301
|
-
if (this.
|
|
374
|
+
if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw') {
|
|
302
375
|
leftclicked = true;
|
|
303
376
|
lines = [];
|
|
304
377
|
Is_Painting = true;
|
|
@@ -318,10 +391,20 @@ export class DrawToolCore extends CommToolsData {
|
|
|
318
391
|
}
|
|
319
392
|
this.nrrd_states.drawStartPos.x = e.offsetX;
|
|
320
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
|
+
}
|
|
321
404
|
this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
|
|
322
405
|
this.protectedData.canvases.drawingCanvas.addEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
|
|
323
406
|
}
|
|
324
|
-
else if (this.
|
|
407
|
+
else if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled()) {
|
|
325
408
|
this.nrrd_states.cursorPageX =
|
|
326
409
|
e.offsetX / this.nrrd_states.sizeFoctor;
|
|
327
410
|
this.nrrd_states.cursorPageY =
|
|
@@ -329,10 +412,10 @@ export class DrawToolCore extends CommToolsData {
|
|
|
329
412
|
this.enableCrosshair();
|
|
330
413
|
this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
|
|
331
414
|
}
|
|
332
|
-
else if (this.gui_states.sphere && !this.
|
|
415
|
+
else if (this.gui_states.sphere && !((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
|
|
333
416
|
sphere(e);
|
|
334
417
|
}
|
|
335
|
-
else if (this.gui_states.calculator && !this.
|
|
418
|
+
else if (this.gui_states.calculator && !((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.isCrosshairEnabled())) {
|
|
336
419
|
this.drawCalSphereDown(e.offsetX, e.offsetY, this.nrrd_states.currentIndex, this.gui_states.cal_distance);
|
|
337
420
|
}
|
|
338
421
|
}
|
|
@@ -350,8 +433,17 @@ export class DrawToolCore extends CommToolsData {
|
|
|
350
433
|
return;
|
|
351
434
|
}
|
|
352
435
|
};
|
|
353
|
-
//
|
|
354
|
-
|
|
436
|
+
// Route pointerdown through EventRouter for centralized event management
|
|
437
|
+
// The handler is still the existing logic, just registered through EventRouter
|
|
438
|
+
if (this.eventRouter) {
|
|
439
|
+
this.eventRouter.setPointerDownHandler((e) => {
|
|
440
|
+
this.drawingPrameters.handleOnDrawingMouseDown(e);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// Fallback for legacy mode without EventRouter
|
|
445
|
+
this.protectedData.canvases.drawingCanvas.addEventListener("pointerdown", this.drawingPrameters.handleOnDrawingMouseDown, true);
|
|
446
|
+
}
|
|
355
447
|
const sphere = (e) => {
|
|
356
448
|
// set sphere size
|
|
357
449
|
this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
@@ -372,48 +464,36 @@ export class DrawToolCore extends CommToolsData {
|
|
|
372
464
|
this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleSphereWheel, true);
|
|
373
465
|
this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
|
|
374
466
|
};
|
|
375
|
-
const
|
|
467
|
+
const redrawPreviousImageToLayerCtx = (ctx) => {
|
|
376
468
|
var _a;
|
|
377
|
-
|
|
378
|
-
switch (label) {
|
|
379
|
-
case "label1":
|
|
380
|
-
paintImages = this.protectedData.maskData.paintImagesLabel1;
|
|
381
|
-
break;
|
|
382
|
-
case "label2":
|
|
383
|
-
paintImages = this.protectedData.maskData.paintImagesLabel2;
|
|
384
|
-
break;
|
|
385
|
-
case "label3":
|
|
386
|
-
paintImages = this.protectedData.maskData.paintImagesLabel3;
|
|
387
|
-
break;
|
|
388
|
-
default:
|
|
389
|
-
paintImages = this.protectedData.maskData.paintImages;
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
const tempPreImg = (_a = this.filterDrawedImage(this.protectedData.axis, this.nrrd_states.currentIndex, paintImages)) === null || _a === void 0 ? void 0 : _a.image;
|
|
469
|
+
const tempPreImg = (_a = this.filterDrawedImage(this.protectedData.axis, this.nrrd_states.currentIndex)) === null || _a === void 0 ? void 0 : _a.image;
|
|
393
470
|
this.protectedData.canvases.emptyCanvas.width =
|
|
394
471
|
this.protectedData.canvases.emptyCanvas.width;
|
|
395
|
-
if (tempPreImg
|
|
472
|
+
if (tempPreImg) {
|
|
396
473
|
this.protectedData.previousDrawingImage = tempPreImg;
|
|
397
474
|
}
|
|
398
475
|
this.protectedData.ctxes.emptyCtx.putImageData(tempPreImg, 0, 0);
|
|
399
|
-
//
|
|
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;
|
|
400
480
|
ctx.drawImage(this.protectedData.canvases.emptyCanvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
401
481
|
};
|
|
402
482
|
this.drawingPrameters.handleOnDrawingMouseUp = (e) => {
|
|
483
|
+
var _a, _b, _c, _d;
|
|
403
484
|
if (e.button === 0) {
|
|
404
|
-
if (this.
|
|
485
|
+
if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw' || Is_Painting) {
|
|
405
486
|
leftclicked = false;
|
|
406
487
|
let { ctx, canvas } = this.setCurrentLayer();
|
|
407
488
|
ctx.closePath();
|
|
408
489
|
this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
|
|
409
490
|
if (!this.gui_states.Eraser) {
|
|
410
|
-
if (this.gui_states.
|
|
411
|
-
|
|
412
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.width;
|
|
491
|
+
if (this.gui_states.pencil) {
|
|
492
|
+
// Clear only the current layer canvas (NOT master)
|
|
413
493
|
canvas.width = canvas.width;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
//
|
|
494
|
+
// Redraw previous layer data from volume
|
|
495
|
+
redrawPreviousImageToLayerCtx(ctx);
|
|
496
|
+
// Draw new pencil strokes on current layer canvas
|
|
417
497
|
ctx.beginPath();
|
|
418
498
|
ctx.moveTo(lines[0].x, lines[0].y);
|
|
419
499
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -423,36 +503,37 @@ export class DrawToolCore extends CommToolsData {
|
|
|
423
503
|
ctx.lineWidth = 1;
|
|
424
504
|
ctx.fillStyle = this.gui_states.fillColor;
|
|
425
505
|
ctx.fill();
|
|
426
|
-
//
|
|
427
|
-
this.
|
|
506
|
+
// Composite ALL layers to master (not just current layer)
|
|
507
|
+
this.compositeAllLayers();
|
|
428
508
|
}
|
|
429
509
|
}
|
|
430
510
|
this.protectedData.previousDrawingImage =
|
|
431
511
|
this.protectedData.ctxes.drawingLayerMasterCtx.getImageData(0, 0, this.protectedData.canvases.drawingCanvasLayerMaster.width, this.protectedData.canvases.drawingCanvasLayerMaster.height);
|
|
432
|
-
this.storeAllImages(this.nrrd_states.currentIndex, this.gui_states.
|
|
512
|
+
this.storeAllImages(this.nrrd_states.currentIndex, this.gui_states.layer);
|
|
433
513
|
if (this.gui_states.Eraser) {
|
|
434
|
-
const
|
|
435
|
-
this.storeEachLayerImage(this.nrrd_states.currentIndex,
|
|
436
|
-
this.storeEachLayerImage(this.nrrd_states.currentIndex,
|
|
514
|
+
const restLayers = this.getRestLayer();
|
|
515
|
+
this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[0]);
|
|
516
|
+
this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[1]);
|
|
437
517
|
}
|
|
438
518
|
Is_Painting = false;
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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;
|
|
456
537
|
}
|
|
457
538
|
// add wheel after pointer up
|
|
458
539
|
this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel, {
|
|
@@ -460,7 +541,7 @@ export class DrawToolCore extends CommToolsData {
|
|
|
460
541
|
});
|
|
461
542
|
}
|
|
462
543
|
else if (this.gui_states.sphere &&
|
|
463
|
-
!this.
|
|
544
|
+
!((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
|
|
464
545
|
// plan B
|
|
465
546
|
// findout all index in the sphere radius range in Axial view
|
|
466
547
|
if (this.nrrd_states.spherePlanB) {
|
|
@@ -480,12 +561,12 @@ export class DrawToolCore extends CommToolsData {
|
|
|
480
561
|
this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
|
|
481
562
|
}
|
|
482
563
|
else if ((this.gui_states.sphere || this.gui_states.calculator) &&
|
|
483
|
-
this.
|
|
564
|
+
((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
|
|
484
565
|
this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
485
566
|
this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
|
|
486
567
|
}
|
|
487
568
|
else if (this.gui_states.calculator &&
|
|
488
|
-
!this.
|
|
569
|
+
!((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.isCrosshairEnabled())) {
|
|
489
570
|
// When mouse up
|
|
490
571
|
this.drawCalSphereUp();
|
|
491
572
|
}
|
|
@@ -504,13 +585,12 @@ export class DrawToolCore extends CommToolsData {
|
|
|
504
585
|
else {
|
|
505
586
|
return;
|
|
506
587
|
}
|
|
507
|
-
if (!this.gui_states.
|
|
588
|
+
if (!this.gui_states.pencil) {
|
|
508
589
|
this.setIsDrawFalse(100);
|
|
509
590
|
}
|
|
510
591
|
};
|
|
511
592
|
this.protectedData.canvases.drawingCanvas.addEventListener("pointerleave", (e) => {
|
|
512
593
|
Is_Painting = false;
|
|
513
|
-
// (this.sceneIn as copperScene).controls.enabled = true;
|
|
514
594
|
if (leftclicked) {
|
|
515
595
|
leftclicked = false;
|
|
516
596
|
this.protectedData.ctxes.drawingLayerMasterCtx.closePath();
|
|
@@ -523,11 +603,12 @@ export class DrawToolCore extends CommToolsData {
|
|
|
523
603
|
this.protectedData.canvases.drawingCanvas.removeEventListener("pointermove", this.drawingPrameters.handleOnPanMouseMove);
|
|
524
604
|
}
|
|
525
605
|
this.setIsDrawFalse(100);
|
|
526
|
-
if (this.gui_states.
|
|
606
|
+
if (this.gui_states.pencil) {
|
|
527
607
|
this.setIsDrawFalse(1000);
|
|
528
608
|
}
|
|
529
609
|
});
|
|
530
610
|
this.start = () => {
|
|
611
|
+
var _a, _b;
|
|
531
612
|
if (this.gui_states.readyToUpdate) {
|
|
532
613
|
this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
533
614
|
this.protectedData.ctxes.drawingCtx.globalAlpha =
|
|
@@ -535,16 +616,17 @@ export class DrawToolCore extends CommToolsData {
|
|
|
535
616
|
if (this.protectedData.Is_Draw) {
|
|
536
617
|
this.protectedData.ctxes.drawingLayerMasterCtx.lineCap = "round";
|
|
537
618
|
this.protectedData.ctxes.drawingLayerMasterCtx.globalAlpha = 1;
|
|
538
|
-
this.protectedData.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
this.protectedData.ctxes.drawingLayerThreeCtx.lineCap = "round";
|
|
543
|
-
this.protectedData.ctxes.drawingLayerThreeCtx.globalAlpha = 1;
|
|
619
|
+
for (const [, target] of this.protectedData.layerTargets) {
|
|
620
|
+
target.ctx.lineCap = "round";
|
|
621
|
+
target.ctx.globalAlpha = 1;
|
|
622
|
+
}
|
|
544
623
|
}
|
|
545
624
|
else {
|
|
546
|
-
|
|
547
|
-
|
|
625
|
+
// Use EventRouter mode for mutually exclusive crosshair vs draw rendering
|
|
626
|
+
const currentMode = (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode();
|
|
627
|
+
if (currentMode === 'draw') {
|
|
628
|
+
// Draw mode: show brush circle preview
|
|
629
|
+
if (!this.gui_states.pencil &&
|
|
548
630
|
!this.gui_states.Eraser &&
|
|
549
631
|
this.nrrd_states.Mouse_Over) {
|
|
550
632
|
this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
@@ -557,7 +639,8 @@ export class DrawToolCore extends CommToolsData {
|
|
|
557
639
|
this.protectedData.ctxes.drawingCtx.stroke();
|
|
558
640
|
}
|
|
559
641
|
}
|
|
560
|
-
if (this.
|
|
642
|
+
else if (currentMode === 'crosshair' || ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
|
|
643
|
+
// Crosshair mode: show red cross lines (mutually exclusive with draw)
|
|
561
644
|
this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
562
645
|
const ex = this.nrrd_states.cursorPageX * this.nrrd_states.sizeFoctor;
|
|
563
646
|
const ey = this.nrrd_states.cursorPageY * this.nrrd_states.sizeFoctor;
|
|
@@ -565,6 +648,8 @@ export class DrawToolCore extends CommToolsData {
|
|
|
565
648
|
this.drawLine(0, ey, this.nrrd_states.changedHeight, ey);
|
|
566
649
|
}
|
|
567
650
|
}
|
|
651
|
+
// globalAlpha was set to gui_states.globalAlpha at the top of start().
|
|
652
|
+
// Master stores full-alpha pixels; transparency applied here only.
|
|
568
653
|
this.protectedData.ctxes.drawingCtx.drawImage(this.protectedData.canvases.drawingCanvasLayerMaster, 0, 0);
|
|
569
654
|
}
|
|
570
655
|
else {
|
|
@@ -575,7 +660,7 @@ export class DrawToolCore extends CommToolsData {
|
|
|
575
660
|
drawLinesOnLayer(ctx, x, y) {
|
|
576
661
|
ctx.beginPath();
|
|
577
662
|
ctx.moveTo(this.nrrd_states.drawStartPos.x, this.nrrd_states.drawStartPos.y);
|
|
578
|
-
if (this.gui_states.
|
|
663
|
+
if (this.gui_states.pencil) {
|
|
579
664
|
ctx.strokeStyle = this.gui_states.color;
|
|
580
665
|
ctx.lineWidth = this.gui_states.lineWidth;
|
|
581
666
|
}
|
|
@@ -589,8 +674,10 @@ export class DrawToolCore extends CommToolsData {
|
|
|
589
674
|
}
|
|
590
675
|
paintOnCanvasLayer(x, y) {
|
|
591
676
|
let { ctx, canvas } = this.setCurrentLayer();
|
|
677
|
+
// Draw only on the current layer canvas (not master directly)
|
|
592
678
|
this.drawLinesOnLayer(ctx, x, y);
|
|
593
|
-
|
|
679
|
+
// Composite all layers to master to preserve other layers' data
|
|
680
|
+
this.compositeAllLayers();
|
|
594
681
|
// reset drawing start position to current position.
|
|
595
682
|
this.nrrd_states.drawStartPos.x = x;
|
|
596
683
|
this.nrrd_states.drawStartPos.y = y;
|
|
@@ -624,15 +711,13 @@ export class DrawToolCore extends CommToolsData {
|
|
|
624
711
|
* it should be hide, so we don't need to add it to mainAreaContainer
|
|
625
712
|
*/
|
|
626
713
|
this.protectedData.canvases.drawingCanvasLayerMaster.width =
|
|
627
|
-
this.
|
|
628
|
-
this.protectedData.canvases.drawingCanvasLayerTwo.width =
|
|
629
|
-
this.protectedData.canvases.drawingCanvasLayerThree.width =
|
|
630
|
-
this.nrrd_states.changedWidth;
|
|
714
|
+
this.nrrd_states.changedWidth;
|
|
631
715
|
this.protectedData.canvases.drawingCanvasLayerMaster.height =
|
|
632
|
-
this.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
716
|
+
this.nrrd_states.changedHeight;
|
|
717
|
+
for (const [, target] of this.protectedData.layerTargets) {
|
|
718
|
+
target.canvas.width = this.nrrd_states.changedWidth;
|
|
719
|
+
target.canvas.height = this.nrrd_states.changedHeight;
|
|
720
|
+
}
|
|
636
721
|
/**
|
|
637
722
|
* display and drawing canvas container
|
|
638
723
|
*/
|
|
@@ -646,67 +731,11 @@ export class DrawToolCore extends CommToolsData {
|
|
|
646
731
|
this.mainAreaContainer.appendChild(this.protectedData.canvases.drawingCanvas);
|
|
647
732
|
}
|
|
648
733
|
useEraser() {
|
|
649
|
-
|
|
650
|
-
var calcWidth = radius - this.nrrd_states.stepClear;
|
|
651
|
-
var calcHeight = Math.sqrt(radius * radius - calcWidth * calcWidth);
|
|
652
|
-
var posX = x - calcWidth;
|
|
653
|
-
var posY = y - calcHeight;
|
|
654
|
-
var widthX = 2 * calcWidth;
|
|
655
|
-
var heightY = 2 * calcHeight;
|
|
656
|
-
if (this.nrrd_states.stepClear <= radius) {
|
|
657
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.clearRect(posX, posY, widthX, heightY);
|
|
658
|
-
this.protectedData.ctxes.drawingLayerOneCtx.clearRect(posX, posY, widthX, heightY);
|
|
659
|
-
this.protectedData.ctxes.drawingLayerTwoCtx.clearRect(posX, posY, widthX, heightY);
|
|
660
|
-
this.protectedData.ctxes.drawingLayerThreeCtx.clearRect(posX, posY, widthX, heightY);
|
|
661
|
-
this.nrrd_states.stepClear += 1;
|
|
662
|
-
clearArc(x, y, radius);
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
return clearArc;
|
|
734
|
+
return this.eraserTool.createClearArc();
|
|
666
735
|
}
|
|
667
736
|
// drawing canvas mouse zoom wheel
|
|
668
737
|
configMouseZoomWheel() {
|
|
669
|
-
|
|
670
|
-
const handleMouseZoomSliceWheelMove = (e) => {
|
|
671
|
-
if (this.protectedData.Is_Shift_Pressed) {
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
e.preventDefault();
|
|
675
|
-
// this.nrrd_states.originWidth;
|
|
676
|
-
const delta = e.detail ? e.detail > 0 : e.wheelDelta < 0;
|
|
677
|
-
this.protectedData.Is_Draw = true;
|
|
678
|
-
var rect = this.container.getBoundingClientRect();
|
|
679
|
-
const ratioL = (e.clientX - rect.left -
|
|
680
|
-
this.mainAreaContainer.offsetLeft -
|
|
681
|
-
this.protectedData.canvases.drawingCanvas.offsetLeft) /
|
|
682
|
-
this.protectedData.canvases.drawingCanvas.offsetWidth;
|
|
683
|
-
const ratioT = (e.clientY - rect.top -
|
|
684
|
-
this.mainAreaContainer.offsetTop -
|
|
685
|
-
this.protectedData.canvases.drawingCanvas.offsetTop) /
|
|
686
|
-
this.protectedData.canvases.drawingCanvas.offsetHeight;
|
|
687
|
-
const ratioDelta = !delta ? 1 + 0.1 : 1 - 0.1;
|
|
688
|
-
const w = this.protectedData.canvases.drawingCanvas.offsetWidth * ratioDelta;
|
|
689
|
-
const h = this.protectedData.canvases.drawingCanvas.offsetHeight * ratioDelta;
|
|
690
|
-
const l = Math.round(e.clientX - this.mainAreaContainer.offsetLeft - w * ratioL - rect.left);
|
|
691
|
-
const t = Math.round(e.clientY - this.mainAreaContainer.offsetTop - h * ratioT - rect.top);
|
|
692
|
-
moveDistance = w / this.nrrd_states.originWidth;
|
|
693
|
-
if (moveDistance > 8) {
|
|
694
|
-
moveDistance = 8;
|
|
695
|
-
// this.resetPaintAreaUIPosition();
|
|
696
|
-
}
|
|
697
|
-
else if (moveDistance < 1) {
|
|
698
|
-
moveDistance = 1;
|
|
699
|
-
this.resetPaintAreaUIPosition();
|
|
700
|
-
this.resizePaintArea(moveDistance);
|
|
701
|
-
}
|
|
702
|
-
else {
|
|
703
|
-
this.resetPaintAreaUIPosition(l, t);
|
|
704
|
-
this.resizePaintArea(moveDistance);
|
|
705
|
-
}
|
|
706
|
-
this.setIsDrawFalse(1000);
|
|
707
|
-
this.nrrd_states.sizeFoctor = moveDistance;
|
|
708
|
-
};
|
|
709
|
-
return handleMouseZoomSliceWheelMove;
|
|
738
|
+
return this.zoomTool.configMouseZoomWheel();
|
|
710
739
|
}
|
|
711
740
|
configMouseSliceWheel() {
|
|
712
741
|
/**
|
|
@@ -716,742 +745,205 @@ export class DrawToolCore extends CommToolsData {
|
|
|
716
745
|
throw new Error("Child class must implement abstract redrawDisplayCanvas, currently you can find it in NrrdTools.");
|
|
717
746
|
}
|
|
718
747
|
enableCrosshair() {
|
|
719
|
-
this.
|
|
720
|
-
switch (this.protectedData.axis) {
|
|
721
|
-
case "x":
|
|
722
|
-
this.cursorPage.x.updated = true;
|
|
723
|
-
this.cursorPage.y.updated = false;
|
|
724
|
-
this.cursorPage.z.updated = false;
|
|
725
|
-
break;
|
|
726
|
-
case "y":
|
|
727
|
-
this.cursorPage.x.updated = false;
|
|
728
|
-
this.cursorPage.y.updated = true;
|
|
729
|
-
this.cursorPage.z.updated = false;
|
|
730
|
-
break;
|
|
731
|
-
case "z":
|
|
732
|
-
this.cursorPage.x.updated = false;
|
|
733
|
-
this.cursorPage.y.updated = false;
|
|
734
|
-
this.cursorPage.z.updated = true;
|
|
735
|
-
break;
|
|
736
|
-
}
|
|
748
|
+
this.crosshairTool.enableCrosshair();
|
|
737
749
|
}
|
|
738
750
|
drawImageOnEmptyImage(canvas) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
751
|
+
const ctx = this.protectedData.ctxes.emptyCtx;
|
|
752
|
+
const w = this.protectedData.canvases.emptyCanvas.width;
|
|
753
|
+
const h = this.protectedData.canvases.emptyCanvas.height;
|
|
754
|
+
// No flip needed: the layer canvas screen coordinates already match the
|
|
755
|
+
// Three.js source coordinate system used by MaskVolume. Applying a flip
|
|
756
|
+
// here would invert cross-axis slice indices (e.g. coronal slice 220
|
|
757
|
+
// becoming 228 when total=448).
|
|
758
|
+
ctx.imageSmoothingEnabled = false;
|
|
759
|
+
ctx.drawImage(canvas, 0, 0, w, h);
|
|
760
|
+
}
|
|
761
|
+
/****************************Sphere functions (delegated to SphereTool)****************************************************/
|
|
748
762
|
drawCalculatorSphereOnEachViews(axis) {
|
|
749
|
-
|
|
750
|
-
this.setSphereCanvasSize(axis);
|
|
751
|
-
// get drawingSphere canvas for storing
|
|
752
|
-
const ctx = this.protectedData.ctxes.drawingSphereCtx;
|
|
753
|
-
const canvas = this.protectedData.canvases.drawingSphereCanvas;
|
|
754
|
-
let tumourPosition = !!this.nrrd_states.tumourSphereOrigin ? Object.assign(this.getSpherePosition(this.nrrd_states.tumourSphereOrigin, axis), { color: this.nrrd_states.tumourColor }) : null;
|
|
755
|
-
let skinPosition = !!this.nrrd_states.skinSphereOrigin ? Object.assign(this.getSpherePosition(this.nrrd_states.skinSphereOrigin, axis), { color: this.nrrd_states.skinColor }) : null;
|
|
756
|
-
let ribcagePosition = !!this.nrrd_states.ribSphereOrigin ? Object.assign(this.getSpherePosition(this.nrrd_states.ribSphereOrigin, axis), { color: this.nrrd_states.ribcageColor }) : null;
|
|
757
|
-
let nipplePosition = !!this.nrrd_states.nippleSphereOrigin ? Object.assign(this.getSpherePosition(this.nrrd_states.nippleSphereOrigin, axis), { color: this.nrrd_states.nippleColor }) : null;
|
|
758
|
-
let positionGroup = [];
|
|
759
|
-
if (!!tumourPosition)
|
|
760
|
-
positionGroup.push(tumourPosition);
|
|
761
|
-
if (!!skinPosition)
|
|
762
|
-
positionGroup.push(skinPosition);
|
|
763
|
-
if (!!ribcagePosition)
|
|
764
|
-
positionGroup.push(ribcagePosition);
|
|
765
|
-
if (!!nipplePosition)
|
|
766
|
-
positionGroup.push(nipplePosition);
|
|
767
|
-
let copyPosition = JSON.parse(JSON.stringify(positionGroup));
|
|
768
|
-
let rePositionGroup = [];
|
|
769
|
-
// group same slice points
|
|
770
|
-
positionGroup.forEach((p) => {
|
|
771
|
-
let temp = [];
|
|
772
|
-
let sameIndex = [];
|
|
773
|
-
for (let i = 0; i < copyPosition.length; i++) {
|
|
774
|
-
if (p.z == copyPosition[i].z) {
|
|
775
|
-
temp.push(copyPosition[i]);
|
|
776
|
-
sameIndex.push(i);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
sameIndex.reverse();
|
|
780
|
-
sameIndex.forEach(i => copyPosition.splice(i, 1));
|
|
781
|
-
if (temp.length > 0)
|
|
782
|
-
rePositionGroup.push(temp);
|
|
783
|
-
if (copyPosition.length == 0) {
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
rePositionGroup.forEach((group) => {
|
|
788
|
-
group.forEach(p => {
|
|
789
|
-
this.drawSphereCore(ctx, p.x, p.y, this.nrrd_states.sphereRadius, p.color);
|
|
790
|
-
});
|
|
791
|
-
this.storeSphereImages(group[0].z, axis);
|
|
792
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
/****************************Sphere functions****************************************************/
|
|
796
|
-
// for sphere
|
|
797
|
-
storeSphereImages(index, axis) {
|
|
798
|
-
this.setEmptyCanvasSize(axis);
|
|
799
|
-
this.drawImageOnEmptyImage(this.protectedData.canvases.drawingSphereCanvas);
|
|
800
|
-
let imageData = this.protectedData.ctxes.emptyCtx.getImageData(0, 0, this.protectedData.canvases.emptyCanvas.width, this.protectedData.canvases.emptyCanvas.height);
|
|
801
|
-
this.storeImageToAxis(index, this.protectedData.maskData.paintImages, imageData, axis);
|
|
763
|
+
this.sphereTool.drawCalculatorSphereOnEachViews(axis);
|
|
802
764
|
}
|
|
803
765
|
drawSphereOnEachViews(decay, axis) {
|
|
804
|
-
|
|
805
|
-
this.setSphereCanvasSize(axis);
|
|
806
|
-
const mouseX = this.nrrd_states.sphereOrigin[axis][0];
|
|
807
|
-
const mouseY = this.nrrd_states.sphereOrigin[axis][1];
|
|
808
|
-
const originIndex = this.nrrd_states.sphereOrigin[axis][2];
|
|
809
|
-
const preIndex = originIndex - decay;
|
|
810
|
-
const nextIndex = originIndex + decay;
|
|
811
|
-
const ctx = this.protectedData.ctxes.drawingSphereCtx;
|
|
812
|
-
const canvas = this.protectedData.canvases.drawingSphereCanvas;
|
|
813
|
-
if (preIndex === nextIndex) {
|
|
814
|
-
this.drawSphereCore(ctx, mouseX, mouseY, this.nrrd_states.sphereRadius, this.gui_states.fillColor);
|
|
815
|
-
this.storeSphereImages(preIndex, axis);
|
|
816
|
-
}
|
|
817
|
-
else {
|
|
818
|
-
this.drawSphereCore(ctx, mouseX, mouseY, (this.nrrd_states.sphereRadius - decay), this.gui_states.fillColor);
|
|
819
|
-
this.drawImageOnEmptyImage(canvas);
|
|
820
|
-
this.storeSphereImages(preIndex, axis);
|
|
821
|
-
this.storeSphereImages(nextIndex, axis);
|
|
822
|
-
}
|
|
823
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
766
|
+
this.sphereTool.drawSphereOnEachViews(decay, axis);
|
|
824
767
|
}
|
|
825
|
-
/**
|
|
826
|
-
*
|
|
827
|
-
* @param ctx draw sphere canvas ctx
|
|
828
|
-
* @param x width must be match the origin size, size factor 1, ignore the size factor
|
|
829
|
-
* @param y height must be match the origin size, size factor 1, ignore the size factor
|
|
830
|
-
* @param radius radius must be match the origin size, size factor 1, ignore the size factor
|
|
831
|
-
* @param color sphere color
|
|
832
|
-
*/
|
|
833
|
-
drawSphereCore(ctx, x, y, radius, color) {
|
|
834
|
-
ctx.beginPath();
|
|
835
|
-
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
|
836
|
-
ctx.fillStyle = color;
|
|
837
|
-
ctx.fill();
|
|
838
|
-
ctx.closePath();
|
|
839
|
-
}
|
|
840
|
-
setSphereCanvasSize(axis) {
|
|
841
|
-
switch (!!axis ? axis : this.protectedData.axis) {
|
|
842
|
-
case "x":
|
|
843
|
-
this.protectedData.canvases.drawingSphereCanvas.width =
|
|
844
|
-
this.nrrd_states.nrrd_z_mm;
|
|
845
|
-
this.protectedData.canvases.drawingSphereCanvas.height =
|
|
846
|
-
this.nrrd_states.nrrd_y_mm;
|
|
847
|
-
break;
|
|
848
|
-
case "y":
|
|
849
|
-
this.protectedData.canvases.drawingSphereCanvas.width =
|
|
850
|
-
this.nrrd_states.nrrd_x_mm;
|
|
851
|
-
this.protectedData.canvases.drawingSphereCanvas.height =
|
|
852
|
-
this.nrrd_states.nrrd_z_mm;
|
|
853
|
-
break;
|
|
854
|
-
case "z":
|
|
855
|
-
this.protectedData.canvases.drawingSphereCanvas.width =
|
|
856
|
-
this.nrrd_states.nrrd_x_mm;
|
|
857
|
-
this.protectedData.canvases.drawingSphereCanvas.height =
|
|
858
|
-
this.nrrd_states.nrrd_y_mm;
|
|
859
|
-
break;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
// drawing canvas mouse shpere wheel
|
|
863
768
|
configMouseSphereWheel() {
|
|
864
|
-
|
|
865
|
-
e.preventDefault();
|
|
866
|
-
if (e.deltaY < 0) {
|
|
867
|
-
this.nrrd_states.sphereRadius += 1;
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
this.nrrd_states.sphereRadius -= 1;
|
|
871
|
-
}
|
|
872
|
-
// limited the radius max and min
|
|
873
|
-
this.nrrd_states.sphereRadius = Math.max(1, Math.min(this.nrrd_states.sphereRadius, 50));
|
|
874
|
-
// get mouse position
|
|
875
|
-
const mouseX = this.nrrd_states.sphereOrigin[this.protectedData.axis][0];
|
|
876
|
-
const mouseY = this.nrrd_states.sphereOrigin[this.protectedData.axis][1];
|
|
877
|
-
this.drawSphere(mouseX, mouseY, this.nrrd_states.sphereRadius);
|
|
878
|
-
};
|
|
879
|
-
return sphereEvent;
|
|
769
|
+
return this.sphereTool.configMouseSphereWheel();
|
|
880
770
|
}
|
|
881
771
|
drawCalculatorSphere(radius) {
|
|
882
|
-
|
|
883
|
-
const [canvas, ctx] = this.clearSphereCanvas();
|
|
884
|
-
if (!!this.nrrd_states.tumourSphereOrigin && this.nrrd_states.tumourSphereOrigin[this.protectedData.axis][2] === this.nrrd_states.currentIndex) {
|
|
885
|
-
this.drawSphereCore(ctx, this.nrrd_states.tumourSphereOrigin[this.protectedData.axis][0], this.nrrd_states.tumourSphereOrigin[this.protectedData.axis][1], radius, this.nrrd_states.tumourColor);
|
|
886
|
-
}
|
|
887
|
-
if (!!this.nrrd_states.skinSphereOrigin && this.nrrd_states.skinSphereOrigin[this.protectedData.axis][2] === this.nrrd_states.currentIndex) {
|
|
888
|
-
this.drawSphereCore(ctx, this.nrrd_states.skinSphereOrigin[this.protectedData.axis][0], this.nrrd_states.skinSphereOrigin[this.protectedData.axis][1], radius, this.nrrd_states.skinColor);
|
|
889
|
-
}
|
|
890
|
-
if (!!this.nrrd_states.ribSphereOrigin && this.nrrd_states.ribSphereOrigin[this.protectedData.axis][2] === this.nrrd_states.currentIndex) {
|
|
891
|
-
this.drawSphereCore(ctx, this.nrrd_states.ribSphereOrigin[this.protectedData.axis][0], this.nrrd_states.ribSphereOrigin[this.protectedData.axis][1], radius, this.nrrd_states.ribcageColor);
|
|
892
|
-
}
|
|
893
|
-
if (!!this.nrrd_states.nippleSphereOrigin && this.nrrd_states.nippleSphereOrigin[this.protectedData.axis][2] === this.nrrd_states.currentIndex) {
|
|
894
|
-
this.drawSphereCore(ctx, this.nrrd_states.nippleSphereOrigin[this.protectedData.axis][0], this.nrrd_states.nippleSphereOrigin[this.protectedData.axis][1], radius, this.nrrd_states.nippleColor);
|
|
895
|
-
}
|
|
896
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.drawImage(canvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
772
|
+
this.sphereTool.drawCalculatorSphere(radius);
|
|
897
773
|
}
|
|
898
774
|
drawSphere(mouseX, mouseY, radius) {
|
|
899
|
-
|
|
900
|
-
// clear canvas
|
|
901
|
-
const [canvas, ctx] = this.clearSphereCanvas();
|
|
902
|
-
this.drawSphereCore(ctx, mouseX, mouseY, radius, this.gui_states.fillColor);
|
|
903
|
-
console.log("when drawing shpere on canvas");
|
|
904
|
-
console.log("Line 1256 mastercanvasesize:", this.protectedData.canvases.drawingCanvasLayerMaster.width, this.protectedData.canvases.drawingCanvasLayerMaster.height);
|
|
905
|
-
console.log("current xy:", mouseX, mouseY);
|
|
906
|
-
console.log("current changed width:", this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
907
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.drawImage(canvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
908
|
-
}
|
|
909
|
-
clearSphereCanvas() {
|
|
910
|
-
// this.protectedData.canvases.drawingSphereCanvas.width =
|
|
911
|
-
// this.protectedData.canvases.drawingCanvasLayerMaster.width;
|
|
912
|
-
// this.protectedData.canvases.drawingSphereCanvas.height =
|
|
913
|
-
// this.protectedData.canvases.drawingCanvasLayerMaster.height;
|
|
914
|
-
// clear drawingCanvasLayerMaster
|
|
915
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.width = this.protectedData.canvases.drawingCanvasLayerMaster.width;
|
|
916
|
-
// resize sphere canvas size to original size
|
|
917
|
-
this.protectedData.canvases.drawingSphereCanvas.width = this.protectedData.canvases.originCanvas.width;
|
|
918
|
-
this.protectedData.canvases.drawingSphereCanvas.height = this.protectedData.canvases.originCanvas.height;
|
|
919
|
-
const canvas = this.protectedData.canvases.drawingSphereCanvas;
|
|
920
|
-
const ctx = this.protectedData.ctxes.drawingSphereCtx;
|
|
921
|
-
return [canvas, ctx];
|
|
775
|
+
this.sphereTool.drawSphere(mouseX, mouseY, radius);
|
|
922
776
|
}
|
|
923
777
|
/**
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
* When we switch into different axis' views, we need to convert current view's the depth to the pixel distance in other views width or height, and convert the current view's width or height from pixel distance to mm distance as other views' depth (slice index) in general.
|
|
928
|
-
*
|
|
929
|
-
* Then as for the crosshair (Cursor Inspector), we also need to convert the cursor point (x, y, z) to other views' (x, y, z).
|
|
930
|
-
*
|
|
931
|
-
* @param from "x" | "y" | "z", current view axis, "x: sagittle, y: coronal, z: axial".
|
|
932
|
-
* @param to "x" | "y" | "z", target view axis (where you want jump to), "x: sagittle, y: coronal, z: axial".
|
|
933
|
-
* @param cursorNumX number, cursor point x on current axis's slice. (pixel distance)
|
|
934
|
-
* @param cursorNumY number, cursor point y on current axis's slice. (pixel distance)
|
|
935
|
-
* @param currentSliceIndex number, current axis's slice's index/depth. (mm distance)
|
|
936
|
-
* @returns
|
|
937
|
-
*/
|
|
778
|
+
* Convert cursor point between axis views.
|
|
779
|
+
* Delegated to CrosshairTool.
|
|
780
|
+
*/
|
|
938
781
|
convertCursorPoint(from, to, cursorNumX, cursorNumY, currentSliceIndex) {
|
|
939
|
-
|
|
940
|
-
const dimensions = nrrd.dimensions;
|
|
941
|
-
const ratios = nrrd.ratios;
|
|
942
|
-
const { nrrd_x_mm, nrrd_y_mm, nrrd_z_mm } = nrrd;
|
|
943
|
-
let currentIndex = 0;
|
|
944
|
-
let oldIndex = 0;
|
|
945
|
-
let convertCursorNumX = 0;
|
|
946
|
-
let convertCursorNumY = 0;
|
|
947
|
-
const convertIndex = {
|
|
948
|
-
x: {
|
|
949
|
-
y: (val) => Math.ceil((val / nrrd_x_mm) * dimensions[0]),
|
|
950
|
-
z: (val) => Math.ceil((val / nrrd_z_mm) * dimensions[2]),
|
|
951
|
-
},
|
|
952
|
-
y: {
|
|
953
|
-
x: (val) => Math.ceil((val / nrrd_y_mm) * dimensions[1]),
|
|
954
|
-
z: (val) => Math.ceil((1 - val / nrrd_z_mm) * dimensions[2]),
|
|
955
|
-
},
|
|
956
|
-
z: {
|
|
957
|
-
x: (val) => Math.ceil((val / nrrd_x_mm) * dimensions[0]),
|
|
958
|
-
y: (val) => Math.ceil((val / nrrd_y_mm) * dimensions[1]),
|
|
959
|
-
},
|
|
960
|
-
};
|
|
961
|
-
const convertCursor = {
|
|
962
|
-
x: {
|
|
963
|
-
y: (sliceIndex) => Math.ceil((sliceIndex / dimensions[0]) * nrrd_x_mm),
|
|
964
|
-
z: (sliceIndex) => Math.ceil((sliceIndex / dimensions[0]) * nrrd_x_mm),
|
|
965
|
-
},
|
|
966
|
-
y: {
|
|
967
|
-
x: (sliceIndex) => Math.ceil((sliceIndex / dimensions[1]) * nrrd_y_mm),
|
|
968
|
-
z: (sliceIndex) => Math.ceil((sliceIndex / dimensions[1]) * nrrd_y_mm),
|
|
969
|
-
},
|
|
970
|
-
z: {
|
|
971
|
-
x: (sliceIndex) => Math.ceil((sliceIndex / dimensions[2]) * nrrd_z_mm),
|
|
972
|
-
y: (sliceIndex) => Math.ceil((1 - sliceIndex / dimensions[2]) * nrrd_z_mm),
|
|
973
|
-
},
|
|
974
|
-
};
|
|
975
|
-
if (from === to) {
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
if (from === "z" && to === "x") {
|
|
979
|
-
currentIndex = convertIndex[from][to](cursorNumX);
|
|
980
|
-
oldIndex = currentIndex * ratios[to];
|
|
981
|
-
convertCursorNumX = convertCursor[from][to](currentSliceIndex);
|
|
982
|
-
convertCursorNumY = cursorNumY;
|
|
983
|
-
}
|
|
984
|
-
else if (from === "y" && to === "x") {
|
|
985
|
-
currentIndex = convertIndex[from][to](cursorNumX);
|
|
986
|
-
oldIndex = currentIndex * ratios.x;
|
|
987
|
-
convertCursorNumY = convertCursor[from][to](currentSliceIndex);
|
|
988
|
-
convertCursorNumX = dimensions[2] * ratios.z - cursorNumY;
|
|
989
|
-
}
|
|
990
|
-
else if (from === "z" && to === "y") {
|
|
991
|
-
currentIndex = convertIndex[from][to](cursorNumY);
|
|
992
|
-
oldIndex = currentIndex * ratios[to];
|
|
993
|
-
convertCursorNumY = convertCursor[from][to](currentSliceIndex);
|
|
994
|
-
convertCursorNumX = cursorNumX;
|
|
995
|
-
}
|
|
996
|
-
else if (from === "x" && to === "y") {
|
|
997
|
-
currentIndex = convertIndex[from][to](cursorNumY);
|
|
998
|
-
oldIndex = currentIndex * ratios[to];
|
|
999
|
-
convertCursorNumX = convertCursor[from][to](currentSliceIndex);
|
|
1000
|
-
convertCursorNumY = dimensions[2] * ratios.z - cursorNumX;
|
|
1001
|
-
}
|
|
1002
|
-
else if (from === "x" && to === "z") {
|
|
1003
|
-
currentIndex = convertIndex[from][to](cursorNumX);
|
|
1004
|
-
oldIndex = currentIndex * ratios[to];
|
|
1005
|
-
convertCursorNumX = convertCursor[from][to](currentSliceIndex);
|
|
1006
|
-
convertCursorNumY = cursorNumY;
|
|
1007
|
-
}
|
|
1008
|
-
else if (from === "y" && to === "z") {
|
|
1009
|
-
currentIndex = convertIndex[from][to](cursorNumY);
|
|
1010
|
-
oldIndex = currentIndex * ratios.z;
|
|
1011
|
-
convertCursorNumY = convertCursor[from][to](currentSliceIndex);
|
|
1012
|
-
convertCursorNumX = cursorNumX;
|
|
1013
|
-
}
|
|
1014
|
-
else {
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
return { currentIndex, oldIndex, convertCursorNumX, convertCursorNumY };
|
|
782
|
+
return this.crosshairTool.convertCursorPoint(from, to, cursorNumX, cursorNumY, currentSliceIndex);
|
|
1018
783
|
}
|
|
1019
784
|
setUpSphereOrigins(mouseX, mouseY, sliceIndex) {
|
|
1020
|
-
|
|
1021
|
-
const convertObj = this.convertCursorPoint(from, to, mouseX, mouseY, sliceIndex);
|
|
1022
|
-
return {
|
|
1023
|
-
convertCursorNumX: convertObj === null || convertObj === void 0 ? void 0 : convertObj.convertCursorNumX,
|
|
1024
|
-
convertCursorNumY: convertObj === null || convertObj === void 0 ? void 0 : convertObj.convertCursorNumY,
|
|
1025
|
-
currentIndex: convertObj === null || convertObj === void 0 ? void 0 : convertObj.currentIndex,
|
|
1026
|
-
};
|
|
1027
|
-
};
|
|
1028
|
-
const axisConversions = {
|
|
1029
|
-
x: { axisTo1: "y", axisTo2: "z" },
|
|
1030
|
-
y: { axisTo1: "z", axisTo2: "x" },
|
|
1031
|
-
z: { axisTo1: "x", axisTo2: "y" },
|
|
1032
|
-
};
|
|
1033
|
-
const { axisTo1, axisTo2 } = axisConversions[this.protectedData.axis];
|
|
1034
|
-
this.nrrd_states.sphereOrigin[axisTo1] = [
|
|
1035
|
-
convertCursor(this.protectedData.axis, axisTo1).convertCursorNumX,
|
|
1036
|
-
convertCursor(this.protectedData.axis, axisTo1).convertCursorNumY,
|
|
1037
|
-
convertCursor(this.protectedData.axis, axisTo1).currentIndex,
|
|
1038
|
-
];
|
|
1039
|
-
this.nrrd_states.sphereOrigin[axisTo2] = [
|
|
1040
|
-
convertCursor(this.protectedData.axis, axisTo2).convertCursorNumX,
|
|
1041
|
-
convertCursor(this.protectedData.axis, axisTo2).convertCursorNumY,
|
|
1042
|
-
convertCursor(this.protectedData.axis, axisTo2).currentIndex,
|
|
1043
|
-
];
|
|
1044
|
-
}
|
|
1045
|
-
/****************************label div controls****************************************************/
|
|
1046
|
-
getRestLabel() {
|
|
1047
|
-
const labels = this.nrrd_states.labels;
|
|
1048
|
-
const restLabel = labels.filter((item) => {
|
|
1049
|
-
return item !== this.gui_states.label;
|
|
1050
|
-
});
|
|
1051
|
-
return restLabel;
|
|
785
|
+
this.crosshairTool.setUpSphereOrigins(mouseX, mouseY, sliceIndex);
|
|
1052
786
|
}
|
|
1053
|
-
/****************************
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
787
|
+
/****************************layer div controls****************************************************/
|
|
788
|
+
getRestLayer() {
|
|
789
|
+
const layers = this.nrrd_states.layers;
|
|
790
|
+
const restLayer = layers.filter((item) => {
|
|
791
|
+
return item !== this.gui_states.layer;
|
|
1057
792
|
});
|
|
793
|
+
return restLayer;
|
|
1058
794
|
}
|
|
795
|
+
/**************************** Undo/Redo functions (Phase 6 — Delta-based) ****************************/
|
|
1059
796
|
/**
|
|
1060
|
-
* Clear mask on current slice canvas
|
|
797
|
+
* Clear mask on current slice canvas.
|
|
798
|
+
*
|
|
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.
|
|
1061
802
|
*/
|
|
1062
803
|
clearPaint() {
|
|
1063
804
|
this.protectedData.Is_Draw = true;
|
|
1064
|
-
this.resetLayerCanvas();
|
|
1065
805
|
this.protectedData.canvases.originCanvas.width =
|
|
1066
806
|
this.protectedData.canvases.originCanvas.width;
|
|
1067
807
|
this.protectedData.mainPreSlices.repaint.call(this.protectedData.mainPreSlices);
|
|
1068
808
|
this.protectedData.previousDrawingImage =
|
|
1069
809
|
this.protectedData.ctxes.emptyCtx.createImageData(1, 1);
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
if (
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
ctx.drawImage(image, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
if (undo.layers.label1.length > 0) {
|
|
1099
|
-
const image = undo.layers.label1[undo.layers.label1.length - 1];
|
|
1100
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.drawImage(image, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
1101
|
-
}
|
|
1102
|
-
if (undo.layers.label2.length > 0) {
|
|
1103
|
-
const image = undo.layers.label2[undo.layers.label2.length - 1];
|
|
1104
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.drawImage(image, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
1105
|
-
}
|
|
1106
|
-
if (undo.layers.label3.length > 0) {
|
|
1107
|
-
const image = undo.layers.label3[undo.layers.label3.length - 1];
|
|
1108
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.drawImage(image, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
810
|
+
// Clear only the active layer's MaskVolume slice and record undo delta
|
|
811
|
+
try {
|
|
812
|
+
const axis = this.protectedData.axis;
|
|
813
|
+
const idx = this.nrrd_states.currentIndex;
|
|
814
|
+
const activeLayer = this.gui_states.layer;
|
|
815
|
+
const vol = this.getVolumeForLayer(activeLayer);
|
|
816
|
+
// Capture old slice for undo before clearing
|
|
817
|
+
const oldSlice = vol.getSliceUint8(idx, axis).data.slice();
|
|
818
|
+
// Clear only the active layer's MaskVolume slice
|
|
819
|
+
vol.clearSlice(idx, axis);
|
|
820
|
+
// New (all-zero) slice for undo newSlice
|
|
821
|
+
const { data: newSlice, width, height } = vol.getSliceUint8(idx, axis);
|
|
822
|
+
// Push clearPaint delta to UndoManager (supports undo)
|
|
823
|
+
this.undoManager.push({
|
|
824
|
+
layerId: activeLayer,
|
|
825
|
+
axis,
|
|
826
|
+
sliceIndex: idx,
|
|
827
|
+
oldSlice,
|
|
828
|
+
newSlice: newSlice.slice(),
|
|
829
|
+
});
|
|
830
|
+
// 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
|
|
834
|
+
);
|
|
1109
835
|
}
|
|
1110
|
-
this.protectedData.previousDrawingImage =
|
|
1111
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.getImageData(0, 0, this.protectedData.canvases.drawingCanvasLayerMaster.width, this.protectedData.canvases.drawingCanvasLayerMaster.height);
|
|
1112
|
-
this.storeAllImages(this.nrrd_states.currentIndex, this.gui_states.label);
|
|
1113
|
-
this.setIsDrawFalse(1000);
|
|
1114
836
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
storeImageToAxis(index, paintedImages, imageData, axis) {
|
|
1118
|
-
var _a, _b, _c;
|
|
1119
|
-
let temp = {
|
|
1120
|
-
index,
|
|
1121
|
-
image: imageData,
|
|
1122
|
-
};
|
|
1123
|
-
let drawedImage;
|
|
1124
|
-
switch (!!axis ? axis : this.protectedData.axis) {
|
|
1125
|
-
case "x":
|
|
1126
|
-
drawedImage = this.filterDrawedImage("x", index, paintedImages);
|
|
1127
|
-
drawedImage
|
|
1128
|
-
? (drawedImage.image = imageData)
|
|
1129
|
-
: (_a = paintedImages.x) === null || _a === void 0 ? void 0 : _a.push(temp);
|
|
1130
|
-
break;
|
|
1131
|
-
case "y":
|
|
1132
|
-
drawedImage = this.filterDrawedImage("y", index, paintedImages);
|
|
1133
|
-
drawedImage
|
|
1134
|
-
? (drawedImage.image = imageData)
|
|
1135
|
-
: (_b = paintedImages.y) === null || _b === void 0 ? void 0 : _b.push(temp);
|
|
1136
|
-
break;
|
|
1137
|
-
case "z":
|
|
1138
|
-
drawedImage = this.filterDrawedImage("z", index, paintedImages);
|
|
1139
|
-
drawedImage
|
|
1140
|
-
? (drawedImage.image = imageData)
|
|
1141
|
-
: (_c = paintedImages.z) === null || _c === void 0 ? void 0 : _c.push(temp);
|
|
1142
|
-
break;
|
|
837
|
+
catch (_a) {
|
|
838
|
+
// Volume not ready (1×1×1 placeholder)
|
|
1143
839
|
}
|
|
840
|
+
// Re-render ALL layers from MaskVolume to canvas (rebuilds visuals from source of truth)
|
|
841
|
+
this.resetLayerCanvas();
|
|
842
|
+
const buffer = this.getOrCreateSliceBuffer(this.protectedData.axis);
|
|
843
|
+
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) {
|
|
847
|
+
const target = this.protectedData.layerTargets.get(layerId);
|
|
848
|
+
if (!target)
|
|
849
|
+
continue;
|
|
850
|
+
target.ctx.clearRect(0, 0, w, h);
|
|
851
|
+
this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.currentIndex, buffer, target.ctx, w, h);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
this.compositeAllLayers();
|
|
855
|
+
this.setIsDrawFalse(1000);
|
|
1144
856
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
857
|
+
/**
|
|
858
|
+
* Undo the last drawing operation on the active layer.
|
|
859
|
+
* Restores the MaskVolume slice to its pre-draw state, re-renders
|
|
860
|
+
* the canvas, and notifies the backend.
|
|
861
|
+
*/
|
|
862
|
+
undoLastPainting() {
|
|
863
|
+
const delta = this.undoManager.undo();
|
|
864
|
+
if (!delta)
|
|
865
|
+
return;
|
|
866
|
+
try {
|
|
867
|
+
const vol = this.getVolumeForLayer(delta.layerId);
|
|
868
|
+
vol.setSliceUint8(delta.sliceIndex, delta.oldSlice, delta.axis);
|
|
1151
869
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
switch (this.protectedData.axis) {
|
|
1155
|
-
case "x":
|
|
1156
|
-
const maskData_x = this.checkSharedPlaceSlice(this.nrrd_states.nrrd_x_pixel, this.nrrd_states.nrrd_y_pixel, imageData);
|
|
1157
|
-
const marked_a_x = this.sliceArrayV(maskData_x, this.nrrd_states.nrrd_y_pixel, this.nrrd_states.nrrd_z_pixel);
|
|
1158
|
-
const marked_b_x = this.sliceArrayH(maskData_x, this.nrrd_states.nrrd_y_pixel, this.nrrd_states.nrrd_z_pixel);
|
|
1159
|
-
// const ratio_a_x =
|
|
1160
|
-
// this.nrrd_states.nrrd_z / this.nrrd_states.dimensions[2];
|
|
1161
|
-
// const ratio_b_x =
|
|
1162
|
-
// this.nrrd_states.nrrd_y / this.nrrd_states.dimensions[1];
|
|
1163
|
-
const convertXIndex = index;
|
|
1164
|
-
// from x the target z will replace the col pixel
|
|
1165
|
-
this.replaceVerticalColPixels(this.protectedData.maskData.paintImages.z, this.nrrd_states.dimensions[2],
|
|
1166
|
-
// this.nrrd_states.ratios.z,
|
|
1167
|
-
1, marked_a_x, this.nrrd_states.nrrd_x_pixel, convertXIndex);
|
|
1168
|
-
// from x the target y will replace the col pixel
|
|
1169
|
-
this.replaceVerticalColPixels(this.protectedData.maskData.paintImages.y, this.nrrd_states.dimensions[1],
|
|
1170
|
-
// this.nrrd_states.ratios.y,
|
|
1171
|
-
1, marked_b_x, this.nrrd_states.nrrd_x_pixel, convertXIndex);
|
|
1172
|
-
break;
|
|
1173
|
-
case "y":
|
|
1174
|
-
const maskData_y = this.checkSharedPlaceSlice(this.nrrd_states.nrrd_x_pixel, this.nrrd_states.nrrd_y_pixel, imageData);
|
|
1175
|
-
const marked_a_y = this.sliceArrayV(maskData_y, this.nrrd_states.nrrd_z_pixel, this.nrrd_states.nrrd_x_pixel);
|
|
1176
|
-
const marked_b_y = this.sliceArrayH(maskData_y, this.nrrd_states.nrrd_z_pixel, this.nrrd_states.nrrd_x_pixel);
|
|
1177
|
-
// const ratio_a_y =
|
|
1178
|
-
// this.nrrd_states.nrrd_x / this.nrrd_states.dimensions[0];
|
|
1179
|
-
// const ratio_b_y =
|
|
1180
|
-
// this.nrrd_states.nrrd_z / this.nrrd_states.dimensions[2];
|
|
1181
|
-
const convertYIndex = index;
|
|
1182
|
-
this.replaceHorizontalRowPixels(this.protectedData.maskData.paintImages.x, this.nrrd_states.dimensions[0],
|
|
1183
|
-
// this.nrrd_states.ratios.x,
|
|
1184
|
-
1, marked_a_y, this.nrrd_states.nrrd_z_pixel, convertYIndex);
|
|
1185
|
-
this.replaceHorizontalRowPixels(this.protectedData.maskData.paintImages.z, this.nrrd_states.dimensions[2],
|
|
1186
|
-
// this.nrrd_states.ratios.z,
|
|
1187
|
-
1, marked_b_y, this.nrrd_states.nrrd_x_pixel, convertYIndex);
|
|
1188
|
-
break;
|
|
1189
|
-
case "z":
|
|
1190
|
-
// for x slices get cols' pixels
|
|
1191
|
-
// for y slices get rows' pixels
|
|
1192
|
-
// 1. slice z 的 y轴对应了slice y的index,所以我们可以通过slice z 确定在y轴上那些行是有pixels的,我们就可以将它的y坐标(或者是行号)对应到slice y的index,并将该index下的marked image提取出来。
|
|
1193
|
-
// 2. 接着我们可以通过当前slice z 的index,来确定marked image 需要替换或重组的 行 pixel array。
|
|
1194
|
-
const maskData_z = this.checkSharedPlaceSlice(this.nrrd_states.nrrd_x_pixel, this.nrrd_states.nrrd_y_pixel, imageData);
|
|
1195
|
-
// 1. get slice z's each row's and col's pixel as a 2d array.
|
|
1196
|
-
// 1.1 get the cols' 2d array for slice x
|
|
1197
|
-
const marked_a_z = this.sliceArrayV(maskData_z, this.nrrd_states.nrrd_y_pixel, this.nrrd_states.nrrd_x_pixel);
|
|
1198
|
-
// 1.2 get the rows' 2d array for slice y
|
|
1199
|
-
const marked_b_z = this.sliceArrayH(maskData_z, this.nrrd_states.nrrd_y_pixel, this.nrrd_states.nrrd_x_pixel);
|
|
1200
|
-
// 1.3 get x axis ratio for converting, to match the number slice x with the slice z's x axis pixel number.
|
|
1201
|
-
// const ratio_a_z =
|
|
1202
|
-
// this.nrrd_states.nrrd_x / this.nrrd_states.dimensions[0];
|
|
1203
|
-
// // 1.4 get y axis ratio for converting
|
|
1204
|
-
// const ratio_b_z =
|
|
1205
|
-
// this.nrrd_states.nrrd_y / this.nrrd_states.dimensions[1];
|
|
1206
|
-
// 1.5 To identify which row/col data should be replace
|
|
1207
|
-
const convertZIndex = index;
|
|
1208
|
-
// 2. Mapping coordinates
|
|
1209
|
-
// from z the target x will replace the col pixel
|
|
1210
|
-
this.replaceVerticalColPixels(this.protectedData.maskData.paintImages.x, this.nrrd_states.dimensions[0],
|
|
1211
|
-
// this.nrrd_states.ratios.x,
|
|
1212
|
-
1, marked_a_z, this.nrrd_states.nrrd_z_pixel, convertZIndex);
|
|
1213
|
-
// from z the target y will replace row pixel
|
|
1214
|
-
this.replaceHorizontalRowPixels(this.protectedData.maskData.paintImages.y, this.nrrd_states.dimensions[1],
|
|
1215
|
-
// this.nrrd_states.ratios.y,
|
|
1216
|
-
1, marked_b_z, this.nrrd_states.nrrd_x_pixel, convertZIndex);
|
|
1217
|
-
break;
|
|
870
|
+
catch (_a) {
|
|
871
|
+
return; // Volume not ready
|
|
1218
872
|
}
|
|
1219
|
-
this.
|
|
1220
|
-
if (
|
|
1221
|
-
this.
|
|
873
|
+
this.protectedData.Is_Draw = true;
|
|
874
|
+
if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.currentIndex) {
|
|
875
|
+
this.applyUndoRedoToCanvas(delta.layerId);
|
|
1222
876
|
}
|
|
1223
|
-
}
|
|
1224
|
-
storeImageToLabel(index, canvas, paintedImages) {
|
|
1225
877
|
if (!this.nrrd_states.loadMaskJson) {
|
|
1226
|
-
this.
|
|
1227
|
-
|
|
878
|
+
const { data: sliceData, width, height } = this.getVolumeForLayer(delta.layerId)
|
|
879
|
+
.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);
|
|
1228
881
|
}
|
|
1229
|
-
|
|
1230
|
-
this.storeImageToAxis(index, paintedImages, imageData);
|
|
1231
|
-
// this.setEmptyCanvasSize()
|
|
1232
|
-
return imageData;
|
|
882
|
+
this.setIsDrawFalse(1000);
|
|
1233
883
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
case "label3":
|
|
1247
|
-
imageData = this.storeImageToLabel(index, this.protectedData.canvases.drawingCanvasLayerThree, this.protectedData.maskData.paintImagesLabel3);
|
|
1248
|
-
break;
|
|
1249
|
-
}
|
|
1250
|
-
// callback function to return the painted image
|
|
1251
|
-
if (!this.nrrd_states.loadMaskJson && this.protectedData.axis == "z") {
|
|
1252
|
-
this.nrrd_states.getMask(imageData, this.nrrd_states.currentIndex, label, this.nrrd_states.nrrd_x_pixel, this.nrrd_states.nrrd_y_pixel, this.nrrd_states.clearAllFlag);
|
|
884
|
+
/**
|
|
885
|
+
* Redo the last undone operation on the active layer.
|
|
886
|
+
* Reapplies the MaskVolume slice to its post-draw state, re-renders
|
|
887
|
+
* the canvas, and notifies the backend.
|
|
888
|
+
*/
|
|
889
|
+
redoLastPainting() {
|
|
890
|
+
const delta = this.undoManager.redo();
|
|
891
|
+
if (!delta)
|
|
892
|
+
return;
|
|
893
|
+
try {
|
|
894
|
+
const vol = this.getVolumeForLayer(delta.layerId);
|
|
895
|
+
vol.setSliceUint8(delta.sliceIndex, delta.newSlice, delta.axis);
|
|
1253
896
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
sliceArrayH(arr, row, col) {
|
|
1257
|
-
const arr2D = [];
|
|
1258
|
-
for (let i = 0; i < row; i++) {
|
|
1259
|
-
const start = i * col * 4;
|
|
1260
|
-
const end = (i + 1) * col * 4;
|
|
1261
|
-
const temp = arr.slice(start, end);
|
|
1262
|
-
arr2D.push(temp);
|
|
897
|
+
catch (_a) {
|
|
898
|
+
return; // Volume not ready
|
|
1263
899
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
const arr2D = [];
|
|
1268
|
-
const base = col * 4;
|
|
1269
|
-
for (let i = 0; i < col; i++) {
|
|
1270
|
-
const temp = [];
|
|
1271
|
-
for (let j = 0; j < row; j++) {
|
|
1272
|
-
const index = base * j + i * 4;
|
|
1273
|
-
temp.push(arr[index]);
|
|
1274
|
-
temp.push(arr[index + 1]);
|
|
1275
|
-
temp.push(arr[index + 2]);
|
|
1276
|
-
temp.push(arr[index + 3]);
|
|
1277
|
-
}
|
|
1278
|
-
arr2D.push(temp);
|
|
900
|
+
this.protectedData.Is_Draw = true;
|
|
901
|
+
if (delta.axis === this.protectedData.axis && delta.sliceIndex === this.nrrd_states.currentIndex) {
|
|
902
|
+
this.applyUndoRedoToCanvas(delta.layerId);
|
|
1279
903
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
* @param paintImageArray : the target view slice's marked images array
|
|
1285
|
-
* @param length : the target view slice's dimention (total slice index num)
|
|
1286
|
-
* @param ratio : the target slice image's width/height ratio of its dimention length
|
|
1287
|
-
* @param markedArr : current painted image's vertical 2d Array
|
|
1288
|
-
* @param targetWidth : the target image width
|
|
1289
|
-
* @param convertIndex : Mapping current image's index to target slice image's width/height pixel start point
|
|
1290
|
-
*/
|
|
1291
|
-
replaceVerticalColPixels(paintImageArray, length, ratio, markedArr, targetWidth, convertIndex) {
|
|
1292
|
-
for (let i = 0, len = length; i < len; i++) {
|
|
1293
|
-
const index = Math.floor(i * ratio);
|
|
1294
|
-
const convertImageArray = paintImageArray[i].image.data;
|
|
1295
|
-
const mark_data = markedArr[index];
|
|
1296
|
-
const base_a = targetWidth * 4;
|
|
1297
|
-
for (let j = 0, len = mark_data.length; j < len; j += 4) {
|
|
1298
|
-
const start = (j / 4) * base_a + convertIndex * 4;
|
|
1299
|
-
convertImageArray[start] = mark_data[j];
|
|
1300
|
-
convertImageArray[start + 1] = mark_data[j + 1];
|
|
1301
|
-
convertImageArray[start + 2] = mark_data[j + 2];
|
|
1302
|
-
convertImageArray[start + 3] = mark_data[j + 3];
|
|
1303
|
-
}
|
|
904
|
+
if (!this.nrrd_states.loadMaskJson) {
|
|
905
|
+
const { data: sliceData, width, height } = this.getVolumeForLayer(delta.layerId)
|
|
906
|
+
.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);
|
|
1304
908
|
}
|
|
909
|
+
this.setIsDrawFalse(1000);
|
|
1305
910
|
}
|
|
1306
911
|
/**
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
* @param length : the target view slice's dimention (total slice index num)
|
|
1310
|
-
* @param ratio : the target slice image's width/height ratio of its dimention length
|
|
1311
|
-
* @param markedArr : current painted image's horizontal 2d Array
|
|
1312
|
-
* @param targetWidth : the target image width
|
|
1313
|
-
* @param convertIndex : Mapping current image's index to target slice image's width/height pixel start point
|
|
912
|
+
* Re-render a layer canvas from MaskVolume and composite to master.
|
|
913
|
+
* Called after writing oldSlice/newSlice back to the volume during undo/redo.
|
|
1314
914
|
*/
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
915
|
+
applyUndoRedoToCanvas(layerId) {
|
|
916
|
+
let target = this.protectedData.layerTargets.get(layerId);
|
|
917
|
+
if (!target) {
|
|
918
|
+
const firstId = this.nrrd_states.layers[0];
|
|
919
|
+
target = this.protectedData.layerTargets.get(firstId);
|
|
920
|
+
}
|
|
921
|
+
const { ctx, canvas } = target;
|
|
922
|
+
// Clear and re-render the affected layer canvas from MaskVolume
|
|
923
|
+
canvas.width = canvas.width;
|
|
924
|
+
const buffer = this.getOrCreateSliceBuffer(this.protectedData.axis);
|
|
925
|
+
if (buffer) {
|
|
926
|
+
this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.currentIndex, buffer, ctx, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
1324
927
|
}
|
|
928
|
+
// Re-composite all layers to master
|
|
929
|
+
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);
|
|
1325
933
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
if (this.nrrd_states.sharedPlace.z.includes(this.nrrd_states.currentIndex)) {
|
|
1330
|
-
const sharedPlaceArr = this.findSliceInSharedPlace();
|
|
1331
|
-
sharedPlaceArr.push(imageData);
|
|
1332
|
-
if (sharedPlaceArr.length > 0) {
|
|
1333
|
-
for (let i = 0; i < sharedPlaceArr.length; i++) {
|
|
1334
|
-
this.replaceArray(maskData, sharedPlaceArr[i].data);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
else {
|
|
1339
|
-
maskData = imageData.data;
|
|
1340
|
-
}
|
|
1341
|
-
return maskData;
|
|
934
|
+
/****************************Store images (delegated to ImageStoreHelper)****************************************************/
|
|
935
|
+
storeAllImages(index, layer) {
|
|
936
|
+
this.imageStoreHelper.storeAllImages(index, layer);
|
|
1342
937
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
for (let i = 0, len = replaceArr.length; i < len; i++) {
|
|
1346
|
-
if (replaceArr[i] === 0 || mainArr[i] !== 0) {
|
|
1347
|
-
continue;
|
|
1348
|
-
}
|
|
1349
|
-
else {
|
|
1350
|
-
mainArr[i] = replaceArr[i];
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
938
|
+
storeImageToLayer(index, canvas) {
|
|
939
|
+
return this.imageStoreHelper.storeImageToLayer(index, canvas);
|
|
1353
940
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
const base = Math.floor(this.nrrd_states.currentIndex *
|
|
1357
|
-
this.nrrd_states.ratios[this.protectedData.axis]);
|
|
1358
|
-
for (let i = 1; i <= 3; i++) {
|
|
1359
|
-
const index = this.nrrd_states.currentIndex - i;
|
|
1360
|
-
if (index < this.nrrd_states.minIndex) {
|
|
1361
|
-
break;
|
|
1362
|
-
}
|
|
1363
|
-
else {
|
|
1364
|
-
const newIndex = Math.floor(index * this.nrrd_states.ratios[this.protectedData.axis]);
|
|
1365
|
-
if (newIndex === base) {
|
|
1366
|
-
sharedPlaceImages.push(this.protectedData.maskData.paintImages[this.protectedData.axis][index].image);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
for (let i = 1; i <= 3; i++) {
|
|
1371
|
-
const index = this.nrrd_states.currentIndex + i;
|
|
1372
|
-
if (index > this.nrrd_states.maxIndex) {
|
|
1373
|
-
break;
|
|
1374
|
-
}
|
|
1375
|
-
else {
|
|
1376
|
-
const newIndex = Math.floor(index * this.nrrd_states.ratios[this.protectedData.axis]);
|
|
1377
|
-
if (newIndex === base) {
|
|
1378
|
-
sharedPlaceImages.push(this.protectedData.maskData.paintImages[this.protectedData.axis][index].image);
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
return sharedPlaceImages;
|
|
941
|
+
storeEachLayerImage(index, layer) {
|
|
942
|
+
this.imageStoreHelper.storeEachLayerImage(index, layer);
|
|
1383
943
|
}
|
|
1384
|
-
/******************************** Utils gui related functions ***************************************/
|
|
1385
|
-
/**
|
|
1386
|
-
* Set up root container events fns for drag function
|
|
1387
|
-
* @param callback
|
|
1388
|
-
*/
|
|
944
|
+
/******************************** Utils gui related functions (delegated to ContrastTool) ***************************************/
|
|
1389
945
|
setupConrastEvents(callback) {
|
|
1390
|
-
this.
|
|
1391
|
-
this.contrastEventPrameters.h = this.container.offsetHeight;
|
|
1392
|
-
this.contrastEventPrameters.handleOnContrastMouseDown = (ev) => {
|
|
1393
|
-
if (ev.button === 0) {
|
|
1394
|
-
this.contrastEventPrameters.x = ev.offsetX / this.contrastEventPrameters.x;
|
|
1395
|
-
this.contrastEventPrameters.y = ev.offsetY / this.contrastEventPrameters.h;
|
|
1396
|
-
this.container.addEventListener("pointermove", this.contrastEventPrameters.handleOnContrastMouseMove);
|
|
1397
|
-
}
|
|
1398
|
-
};
|
|
1399
|
-
this.contrastEventPrameters.handleOnContrastMouseUp = (ev) => {
|
|
1400
|
-
this.container.removeEventListener("pointermove", this.contrastEventPrameters.handleOnContrastMouseMove);
|
|
1401
|
-
};
|
|
1402
|
-
this.contrastEventPrameters.handleOnContrastMouseMove = throttle((ev) => {
|
|
1403
|
-
if (this.contrastEventPrameters.y - ev.offsetY / this.contrastEventPrameters.h >= 0) {
|
|
1404
|
-
this.contrastEventPrameters.move_y = -Math.ceil((this.contrastEventPrameters.y - ev.offsetY / this.contrastEventPrameters.h) * 10);
|
|
1405
|
-
}
|
|
1406
|
-
else {
|
|
1407
|
-
this.contrastEventPrameters.move_y = -Math.floor((this.contrastEventPrameters.y - ev.offsetY / this.contrastEventPrameters.h) * 10);
|
|
1408
|
-
}
|
|
1409
|
-
if (this.contrastEventPrameters.move_y !== 0 && Math.abs(this.contrastEventPrameters.move_y) === 1) {
|
|
1410
|
-
callback(this.contrastEventPrameters.move_y, "vertical");
|
|
1411
|
-
}
|
|
1412
|
-
if (this.contrastEventPrameters.x - ev.offsetX / this.contrastEventPrameters.w >= 0) {
|
|
1413
|
-
this.contrastEventPrameters.move_x = -Math.ceil((this.contrastEventPrameters.x - ev.offsetX / this.contrastEventPrameters.w) * 10);
|
|
1414
|
-
}
|
|
1415
|
-
else {
|
|
1416
|
-
this.contrastEventPrameters.move_x = -Math.floor((this.contrastEventPrameters.x - ev.offsetX / this.contrastEventPrameters.w) * 10);
|
|
1417
|
-
}
|
|
1418
|
-
if (this.contrastEventPrameters.move_x !== 0 && Math.abs(this.contrastEventPrameters.move_x) === 1) {
|
|
1419
|
-
callback(this.contrastEventPrameters.move_x, "horizental");
|
|
1420
|
-
}
|
|
1421
|
-
this.contrastEventPrameters.x = ev.offsetX / this.contrastEventPrameters.w;
|
|
1422
|
-
this.contrastEventPrameters.y = ev.offsetY / this.contrastEventPrameters.h;
|
|
1423
|
-
}, 100);
|
|
1424
|
-
}
|
|
1425
|
-
updateSlicesContrast(value, flag) {
|
|
1426
|
-
switch (flag) {
|
|
1427
|
-
case "lowerThreshold":
|
|
1428
|
-
this.protectedData.displaySlices.forEach((slice, index) => {
|
|
1429
|
-
slice.volume.lowerThreshold = value;
|
|
1430
|
-
});
|
|
1431
|
-
break;
|
|
1432
|
-
case "upperThreshold":
|
|
1433
|
-
this.protectedData.displaySlices.forEach((slice, index) => {
|
|
1434
|
-
slice.volume.upperThreshold = value;
|
|
1435
|
-
});
|
|
1436
|
-
break;
|
|
1437
|
-
case "windowLow":
|
|
1438
|
-
this.protectedData.displaySlices.forEach((slice, index) => {
|
|
1439
|
-
slice.volume.windowLow = value;
|
|
1440
|
-
});
|
|
1441
|
-
break;
|
|
1442
|
-
case "windowHigh":
|
|
1443
|
-
this.protectedData.displaySlices.forEach((slice, index) => {
|
|
1444
|
-
slice.volume.windowHigh = value;
|
|
1445
|
-
});
|
|
1446
|
-
break;
|
|
1447
|
-
}
|
|
1448
|
-
this.repraintCurrentContrastSlice();
|
|
1449
|
-
}
|
|
1450
|
-
repraintCurrentContrastSlice() {
|
|
1451
|
-
this.setSyncsliceNum();
|
|
1452
|
-
this.protectedData.displaySlices.forEach((slice, index) => {
|
|
1453
|
-
slice.repaint.call(slice);
|
|
1454
|
-
});
|
|
946
|
+
this.contrastTool.setupConrastEvents(callback);
|
|
1455
947
|
}
|
|
1456
948
|
}
|
|
1457
949
|
//# sourceMappingURL=DrawToolCore.js.map
|