copper3d 3.0.2 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -1028
- package/dist/Utils/segmentation/CanvasState.d.ts +31 -0
- package/dist/Utils/segmentation/CanvasState.js +168 -0
- package/dist/Utils/segmentation/CanvasState.js.map +1 -0
- package/dist/Utils/segmentation/DragOperator.d.ts +1 -1
- package/dist/Utils/segmentation/DragOperator.js +2 -4
- package/dist/Utils/segmentation/DragOperator.js.map +1 -1
- package/dist/Utils/segmentation/DrawToolCore.d.ts +49 -27
- package/dist/Utils/segmentation/DrawToolCore.js +275 -344
- package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
- package/dist/Utils/segmentation/NrrdTools.d.ts +92 -467
- package/dist/Utils/segmentation/NrrdTools.js +499 -1341
- package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
- package/dist/Utils/segmentation/RenderingUtils.d.ts +73 -0
- package/dist/Utils/segmentation/RenderingUtils.js +186 -0
- package/dist/Utils/segmentation/RenderingUtils.js.map +1 -0
- package/dist/Utils/segmentation/core/MaskVolume.d.ts +1 -1
- package/dist/Utils/segmentation/core/MaskVolume.js +1 -1
- package/dist/Utils/segmentation/core/index.d.ts +1 -1
- package/dist/Utils/segmentation/core/index.js.map +1 -1
- package/dist/Utils/segmentation/core/types.d.ts +386 -0
- package/dist/Utils/segmentation/core/types.js +1 -0
- package/dist/Utils/segmentation/core/types.js.map +1 -1
- package/dist/Utils/segmentation/coreTools/GuiState.d.ts +1 -1
- package/dist/Utils/segmentation/coreTools/GuiState.js +1 -0
- package/dist/Utils/segmentation/coreTools/GuiState.js.map +1 -1
- package/dist/Utils/segmentation/coreTools/NrrdState.d.ts +1 -1
- package/dist/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
- package/dist/Utils/segmentation/coreTools/gui.d.ts +1 -1
- package/dist/Utils/segmentation/tools/BaseTool.d.ts +1 -1
- package/dist/Utils/segmentation/tools/ContrastTool.d.ts +3 -6
- package/dist/Utils/segmentation/tools/ContrastTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/CrosshairTool.d.ts +11 -1
- package/dist/Utils/segmentation/tools/CrosshairTool.js +25 -0
- package/dist/Utils/segmentation/tools/CrosshairTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/DataLoader.d.ts +33 -0
- package/dist/Utils/segmentation/tools/DataLoader.js +159 -0
- package/dist/Utils/segmentation/tools/DataLoader.js.map +1 -0
- package/dist/Utils/segmentation/tools/DragSliceTool.d.ts +3 -12
- package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/DrawingTool.d.ts +18 -26
- package/dist/Utils/segmentation/tools/DrawingTool.js +48 -0
- package/dist/Utils/segmentation/tools/DrawingTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +2 -5
- package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -1
- package/dist/Utils/segmentation/tools/LayerChannelManager.d.ts +56 -0
- package/dist/Utils/segmentation/tools/LayerChannelManager.js +188 -0
- package/dist/Utils/segmentation/tools/LayerChannelManager.js.map +1 -0
- package/dist/Utils/segmentation/tools/PanTool.d.ts +2 -8
- package/dist/Utils/segmentation/tools/PanTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/SliceRenderPipeline.d.ts +80 -0
- package/dist/Utils/segmentation/tools/SliceRenderPipeline.js +355 -0
- package/dist/Utils/segmentation/tools/SliceRenderPipeline.js.map +1 -0
- package/dist/Utils/segmentation/tools/SphereTool.d.ts +25 -19
- package/dist/Utils/segmentation/tools/SphereTool.js +61 -7
- package/dist/Utils/segmentation/tools/SphereTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/ToolHost.d.ts +74 -0
- package/dist/Utils/segmentation/tools/ToolHost.js +10 -0
- package/dist/Utils/segmentation/tools/ToolHost.js.map +1 -0
- package/dist/Utils/segmentation/tools/ZoomTool.d.ts +2 -6
- package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/index.d.ts +5 -7
- package/dist/Utils/segmentation/tools/index.js +6 -0
- package/dist/Utils/segmentation/tools/index.js.map +1 -1
- package/dist/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
- package/dist/bundle.esm.js +2146 -2359
- package/dist/bundle.umd.js +2146 -2359
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/types/Utils/segmentation/CanvasState.d.ts +31 -0
- package/dist/types/Utils/segmentation/DragOperator.d.ts +1 -1
- package/dist/types/Utils/segmentation/DrawToolCore.d.ts +49 -27
- package/dist/types/Utils/segmentation/NrrdTools.d.ts +92 -467
- package/dist/types/Utils/segmentation/RenderingUtils.d.ts +73 -0
- package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +1 -1
- package/dist/types/Utils/segmentation/core/index.d.ts +1 -1
- package/dist/types/Utils/segmentation/core/types.d.ts +386 -0
- package/dist/types/Utils/segmentation/coreTools/GuiState.d.ts +1 -1
- package/dist/types/Utils/segmentation/coreTools/NrrdState.d.ts +1 -1
- package/dist/types/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
- package/dist/types/Utils/segmentation/coreTools/gui.d.ts +1 -1
- package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +1 -1
- package/dist/types/Utils/segmentation/tools/ContrastTool.d.ts +3 -6
- package/dist/types/Utils/segmentation/tools/CrosshairTool.d.ts +11 -1
- package/dist/types/Utils/segmentation/tools/DataLoader.d.ts +33 -0
- package/dist/types/Utils/segmentation/tools/DragSliceTool.d.ts +3 -12
- package/dist/types/Utils/segmentation/tools/DrawingTool.d.ts +18 -26
- package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +2 -5
- package/dist/types/Utils/segmentation/tools/LayerChannelManager.d.ts +56 -0
- package/dist/types/Utils/segmentation/tools/PanTool.d.ts +2 -8
- package/dist/types/Utils/segmentation/tools/SliceRenderPipeline.d.ts +80 -0
- package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +25 -19
- package/dist/types/Utils/segmentation/tools/ToolHost.d.ts +74 -0
- package/dist/types/Utils/segmentation/tools/ZoomTool.d.ts +2 -6
- package/dist/types/Utils/segmentation/tools/index.d.ts +5 -7
- package/dist/types/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
- package/dist/types/index.d.ts +3 -3
- package/package.json +1 -1
- package/dist/Utils/segmentation/CommToolsData.d.ts +0 -192
- package/dist/Utils/segmentation/CommToolsData.js +0 -474
- package/dist/Utils/segmentation/CommToolsData.js.map +0 -1
- package/dist/Utils/segmentation/coreTools/coreType.d.ts +0 -389
- package/dist/Utils/segmentation/coreTools/coreType.js +0 -3
- package/dist/Utils/segmentation/coreTools/coreType.js.map +0 -1
- package/dist/types/Utils/segmentation/CommToolsData.d.ts +0 -192
- package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +0 -389
|
@@ -2,11 +2,23 @@ import { setupGui } from "./coreTools/gui";
|
|
|
2
2
|
import { autoFocusDiv, enableDownload } from "./coreTools/divControlTools";
|
|
3
3
|
import { DragOperator } from "./DragOperator";
|
|
4
4
|
import { DrawToolCore } from "./DrawToolCore";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { CanvasState } from "./CanvasState";
|
|
6
|
+
import { MaskVolume } from "./core";
|
|
7
|
+
import { LayerChannelManager } from "./tools/LayerChannelManager";
|
|
8
|
+
import { SliceRenderPipeline } from "./tools/SliceRenderPipeline";
|
|
9
|
+
import { DataLoader } from "./tools/DataLoader";
|
|
7
10
|
/**
|
|
8
11
|
* Core NRRD annotation tool for medical image segmentation.
|
|
9
12
|
*
|
|
13
|
+
* Acts as a **Facade** that delegates to composed modules:
|
|
14
|
+
* - {@link CanvasState} — pure state container
|
|
15
|
+
* - {@link DrawToolCore} — tool orchestration, event routing
|
|
16
|
+
* - {@link LayerChannelManager} — layer/channel/sphere-type management
|
|
17
|
+
* - {@link SliceRenderPipeline} — slice rendering, canvas flip, mask reload
|
|
18
|
+
* - {@link DataLoader} — NRRD slice loading, legacy mask loading, NIfTI loading
|
|
19
|
+
*
|
|
20
|
+
* No longer extends DrawToolCore — uses composition instead.
|
|
21
|
+
*
|
|
10
22
|
* @example
|
|
11
23
|
* ```ts
|
|
12
24
|
* // Default 3 layers: layer1, layer2, layer3
|
|
@@ -16,10 +28,11 @@ import { SPHERE_CHANNEL_MAP, SPHERE_LABELS } from "./tools/SphereTool";
|
|
|
16
28
|
* const tools = new NrrdTools(container, { layers: ["layer1", "layer2"] });
|
|
17
29
|
* ```
|
|
18
30
|
*/
|
|
19
|
-
export class NrrdTools
|
|
31
|
+
export class NrrdTools {
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
33
|
+
// 1. Constructor + Module Initialisation
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
20
35
|
constructor(container, options) {
|
|
21
|
-
super(container, options);
|
|
22
|
-
this.initState = true;
|
|
23
36
|
this._sliceRAFId = null;
|
|
24
37
|
this._pendingSliceStep = 0;
|
|
25
38
|
/** Whether calculator mode is active (not part of gui_states interface) */
|
|
@@ -27,47 +40,91 @@ export class NrrdTools extends DrawToolCore {
|
|
|
27
40
|
/** Stored closure callbacks from gui.ts setupGui() */
|
|
28
41
|
this.guiCallbacks = null;
|
|
29
42
|
this.container = container;
|
|
43
|
+
// Create shared state
|
|
44
|
+
const mainAreaContainer = document.createElement("div");
|
|
45
|
+
this.state = new CanvasState(container, mainAreaContainer, options);
|
|
46
|
+
// Create DrawToolCore with shared state
|
|
47
|
+
this.drawCore = new DrawToolCore(container, this.state);
|
|
48
|
+
// Wire DrawToolCore's overridable methods to NrrdTools implementations
|
|
49
|
+
this.wireDrawCoreMethods();
|
|
50
|
+
// Wire RenderingUtils' setEmptyCanvasSize callback
|
|
51
|
+
this.drawCore.renderer.setEmptyCanvasSize = (axis) => this.setEmptyCanvasSize(axis);
|
|
30
52
|
this.init();
|
|
31
|
-
this.dragOperator = new DragOperator(this.container, this.nrrd_states, this.gui_states, this.protectedData, this.drawingPrameters, this.setSyncsliceNum.bind(this), this.setIsDrawFalse.bind(this), this.flipDisplayImageByAxis.bind(this), this.setEmptyCanvasSize.bind(this), this.getOrCreateSliceBuffer.bind(this), this.renderSliceToCanvas.bind(this));
|
|
53
|
+
this.dragOperator = new DragOperator(this.container, this.state.nrrd_states, this.state.gui_states, this.state.protectedData, this.drawCore.drawingPrameters, this.setSyncsliceNum.bind(this), this.setIsDrawFalse.bind(this), this.flipDisplayImageByAxis.bind(this), this.setEmptyCanvasSize.bind(this), this.drawCore.renderer.getOrCreateSliceBuffer.bind(this.drawCore.renderer), this.drawCore.renderer.renderSliceToCanvas.bind(this.drawCore.renderer));
|
|
32
54
|
// Inject EventRouter into DragOperator for centralized event handling
|
|
33
|
-
if (this.eventRouter) {
|
|
34
|
-
this.dragOperator.setEventRouter(this.eventRouter);
|
|
55
|
+
if (this.drawCore.eventRouter) {
|
|
56
|
+
this.dragOperator.setEventRouter(this.drawCore.eventRouter);
|
|
35
57
|
}
|
|
36
58
|
// Wire sphere overlay refresh callback into DragOperator → DragSliceTool
|
|
37
59
|
this.dragOperator.setRefreshSphereOverlay(() => this.refreshSphereOverlay());
|
|
60
|
+
// Initialize extracted modules
|
|
61
|
+
this.initNrrdToolsModules();
|
|
38
62
|
}
|
|
39
63
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
64
|
+
* Wire DrawToolCore's overridable methods to NrrdTools implementations.
|
|
65
|
+
* This replaces the old inheritance approach.
|
|
42
66
|
*/
|
|
43
|
-
|
|
44
|
-
this.
|
|
67
|
+
wireDrawCoreMethods() {
|
|
68
|
+
this.drawCore.setIsDrawFalse = (target) => this.setIsDrawFalse(target);
|
|
69
|
+
this.drawCore.setSyncsliceNum = () => this.setSyncsliceNum();
|
|
70
|
+
this.drawCore.resetPaintAreaUIPosition = (l, t) => this.resetPaintAreaUIPosition(l, t);
|
|
71
|
+
this.drawCore.resizePaintArea = (factor) => this.resizePaintArea(factor);
|
|
72
|
+
this.drawCore.setEmptyCanvasSize = (axis) => this.setEmptyCanvasSize(axis);
|
|
73
|
+
this.drawCore.flipDisplayImageByAxis = () => this.flipDisplayImageByAxis();
|
|
74
|
+
this.drawCore.updateOriginAndChangedWH = () => this.updateOriginAndChangedWH();
|
|
75
|
+
this.drawCore.resetLayerCanvas = () => this.resetLayerCanvas();
|
|
76
|
+
this.drawCore.redrawDisplayCanvas = () => this.redrawDisplayCanvas();
|
|
77
|
+
this.drawCore.enterSphereMode = () => this.enterSphereMode();
|
|
78
|
+
this.drawCore.exitSphereMode = () => this.exitSphereMode();
|
|
79
|
+
this.drawCore.configMouseSliceWheel = () => this.configMouseSliceWheel();
|
|
45
80
|
}
|
|
46
81
|
/**
|
|
47
|
-
*
|
|
48
|
-
* @param size number
|
|
82
|
+
* A initialise function for nrrd_tools
|
|
49
83
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.baseCanvasesSize = 1;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
this.baseCanvasesSize = size;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
setDisplaySliceIndexPanel(panel) {
|
|
62
|
-
this.dragOperator.setShowDragNumberDiv(panel);
|
|
84
|
+
init() {
|
|
85
|
+
this.state.protectedData.mainAreaContainer.classList.add("copper3D_drawingCanvasContainer");
|
|
86
|
+
this.container.appendChild(this.state.protectedData.mainAreaContainer);
|
|
87
|
+
autoFocusDiv(this.container);
|
|
88
|
+
this.setShowInMainArea();
|
|
63
89
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
initNrrdToolsModules() {
|
|
91
|
+
const toolCtx = {
|
|
92
|
+
nrrd_states: this.state.nrrd_states,
|
|
93
|
+
gui_states: this.state.gui_states,
|
|
94
|
+
protectedData: this.state.protectedData,
|
|
95
|
+
cursorPage: this.state.cursorPage,
|
|
96
|
+
callbacks: this.state.annotationCallbacks,
|
|
97
|
+
};
|
|
98
|
+
this.layerChannelManager = new LayerChannelManager(toolCtx, {
|
|
99
|
+
reloadMasksFromVolume: () => this.reloadMasksFromVolume(),
|
|
100
|
+
getVolumeForLayer: (layer) => this.drawCore.renderer.getVolumeForLayer(layer),
|
|
101
|
+
onChannelColorChanged: (layerId, ch, color) => this.state.annotationCallbacks.onChannelColorChanged(layerId, ch, color),
|
|
102
|
+
});
|
|
103
|
+
this.sliceRenderPipeline = new SliceRenderPipeline(toolCtx, {
|
|
104
|
+
compositeAllLayers: () => this.drawCore.renderer.compositeAllLayers(),
|
|
105
|
+
getOrCreateSliceBuffer: (axis) => this.drawCore.renderer.getOrCreateSliceBuffer(axis),
|
|
106
|
+
renderSliceToCanvas: (layer, axis, sliceIndex, buffer, targetCtx, w, h) => this.drawCore.renderer.renderSliceToCanvas(layer, axis, sliceIndex, buffer, targetCtx, w, h),
|
|
107
|
+
getVolumeForLayer: (layer) => this.drawCore.renderer.getVolumeForLayer(layer),
|
|
108
|
+
refreshSphereOverlay: () => this.refreshSphereOverlay(),
|
|
109
|
+
syncGuiParameterSettings: () => this.syncGuiParameterSettings(),
|
|
110
|
+
repraintCurrentContrastSlice: () => this.drawCore.repraintCurrentContrastSlice(),
|
|
111
|
+
clearUndoHistory: () => this.drawCore.undoManager.clearAll(),
|
|
112
|
+
updateShowNumDiv: (contrastNum) => this.dragOperator.updateShowNumDiv(contrastNum),
|
|
113
|
+
updateCurrentContrastSlice: () => this.dragOperator.updateCurrentContrastSlice(),
|
|
114
|
+
});
|
|
115
|
+
this.dataLoader = new DataLoader(toolCtx, {
|
|
116
|
+
invalidateSliceBuffer: () => this.drawCore.renderer.invalidateSliceBuffer(),
|
|
117
|
+
setDisplaySlicesBaseOnAxis: () => this.sliceRenderPipeline.setDisplaySlicesBaseOnAxis(),
|
|
118
|
+
afterLoadSlice: () => this.sliceRenderPipeline.afterLoadSlice(),
|
|
119
|
+
setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
|
|
120
|
+
syncLayerSliceData: (index, mode) => this.drawCore.syncLayerSliceData(index, mode),
|
|
121
|
+
reloadMasksFromVolume: () => this.reloadMasksFromVolume(),
|
|
122
|
+
resetZoom: () => this.executeAction("resetZoom"),
|
|
123
|
+
});
|
|
70
124
|
}
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
+
// 2. GUI Setup
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
71
128
|
/**
|
|
72
129
|
* Set up GUI for drawing panel
|
|
73
130
|
* @param gui GUI
|
|
@@ -78,35 +135,35 @@ export class NrrdTools extends DrawToolCore {
|
|
|
78
135
|
const guiOptions = {
|
|
79
136
|
modeFolder,
|
|
80
137
|
dragOperator: this.dragOperator,
|
|
81
|
-
gui_states: this.gui_states,
|
|
82
|
-
nrrd_states: this.nrrd_states,
|
|
83
|
-
drawingCanvas: this.protectedData.canvases.drawingCanvas,
|
|
84
|
-
drawingPrameters: this.drawingPrameters,
|
|
85
|
-
eraserUrls: this.eraserUrls,
|
|
86
|
-
pencilUrls: this.pencilUrls,
|
|
87
|
-
mainPreSlices: this.protectedData.mainPreSlices,
|
|
88
|
-
protectedData: this.protectedData,
|
|
138
|
+
gui_states: this.state.gui_states,
|
|
139
|
+
nrrd_states: this.state.nrrd_states,
|
|
140
|
+
drawingCanvas: this.state.protectedData.canvases.drawingCanvas,
|
|
141
|
+
drawingPrameters: this.drawCore.drawingPrameters,
|
|
142
|
+
eraserUrls: this.drawCore.eraserUrls,
|
|
143
|
+
pencilUrls: this.drawCore.pencilUrls,
|
|
144
|
+
mainPreSlices: this.state.protectedData.mainPreSlices,
|
|
145
|
+
protectedData: this.state.protectedData,
|
|
89
146
|
removeDragMode: this.dragOperator.removeDragMode,
|
|
90
147
|
configDragMode: this.dragOperator.configDragMode,
|
|
91
|
-
clearActiveLayer: this.clearActiveLayer,
|
|
92
|
-
clearActiveSlice: this.clearActiveSlice,
|
|
93
|
-
updateSlicesContrast: this.updateSlicesContrast,
|
|
94
|
-
setMainAreaSize: this.setMainAreaSize,
|
|
95
|
-
resetPaintAreaUIPosition: this.resetPaintAreaUIPosition,
|
|
96
|
-
resizePaintArea: this.resizePaintArea,
|
|
97
|
-
repraintCurrentContrastSlice: this.repraintCurrentContrastSlice,
|
|
98
|
-
setSyncsliceNum: this.setSyncsliceNum,
|
|
99
|
-
resetLayerCanvas: this.resetLayerCanvas,
|
|
100
|
-
redrawDisplayCanvas: this.redrawDisplayCanvas,
|
|
101
|
-
flipDisplayImageByAxis: this.flipDisplayImageByAxis,
|
|
102
|
-
setEmptyCanvasSize: this.setEmptyCanvasSize,
|
|
103
|
-
syncLayerSliceData: this.syncLayerSliceData,
|
|
104
|
-
drawImageOnEmptyImage: this.drawImageOnEmptyImage,
|
|
105
|
-
getRestLayer: this.getRestLayer,
|
|
106
|
-
setIsDrawFalse: this.setIsDrawFalse,
|
|
107
|
-
getVolumeForLayer: this.getVolumeForLayer.bind(this),
|
|
108
|
-
undoLastPainting: this.undoLastPainting.bind(this),
|
|
109
|
-
redoLastPainting: this.redoLastPainting.bind(this),
|
|
148
|
+
clearActiveLayer: this.clearActiveLayer.bind(this),
|
|
149
|
+
clearActiveSlice: this.drawCore.clearActiveSlice.bind(this.drawCore),
|
|
150
|
+
updateSlicesContrast: this.drawCore.updateSlicesContrast,
|
|
151
|
+
setMainAreaSize: this.setMainAreaSize.bind(this),
|
|
152
|
+
resetPaintAreaUIPosition: this.resetPaintAreaUIPosition.bind(this),
|
|
153
|
+
resizePaintArea: this.resizePaintArea.bind(this),
|
|
154
|
+
repraintCurrentContrastSlice: this.drawCore.repraintCurrentContrastSlice,
|
|
155
|
+
setSyncsliceNum: this.setSyncsliceNum.bind(this),
|
|
156
|
+
resetLayerCanvas: this.resetLayerCanvas.bind(this),
|
|
157
|
+
redrawDisplayCanvas: this.redrawDisplayCanvas.bind(this),
|
|
158
|
+
flipDisplayImageByAxis: this.flipDisplayImageByAxis.bind(this),
|
|
159
|
+
setEmptyCanvasSize: this.setEmptyCanvasSize.bind(this),
|
|
160
|
+
syncLayerSliceData: this.drawCore.syncLayerSliceData.bind(this.drawCore),
|
|
161
|
+
drawImageOnEmptyImage: this.drawCore.drawImageOnEmptyImage.bind(this.drawCore),
|
|
162
|
+
getRestLayer: this.drawCore.getRestLayer.bind(this.drawCore),
|
|
163
|
+
setIsDrawFalse: this.setIsDrawFalse.bind(this),
|
|
164
|
+
getVolumeForLayer: this.drawCore.renderer.getVolumeForLayer.bind(this.drawCore.renderer),
|
|
165
|
+
undoLastPainting: this.drawCore.undoLastPainting.bind(this.drawCore),
|
|
166
|
+
redoLastPainting: this.drawCore.redoLastPainting.bind(this.drawCore),
|
|
110
167
|
resetZoom: () => this.executeAction("resetZoom"),
|
|
111
168
|
downloadCurrentMask: () => this.executeAction("downloadCurrentMask"),
|
|
112
169
|
};
|
|
@@ -125,195 +182,106 @@ export class NrrdTools extends DrawToolCore {
|
|
|
125
182
|
}
|
|
126
183
|
/**
|
|
127
184
|
* Sync guiParameterSettings with current volume metadata.
|
|
128
|
-
* Called internally when slices are loaded or switched.
|
|
129
185
|
*/
|
|
130
186
|
syncGuiParameterSettings() {
|
|
131
|
-
if (this.guiParameterSettings && this.protectedData.mainPreSlices) {
|
|
132
|
-
this.guiParameterSettings.windowHigh.value = this.guiParameterSettings.windowLow.value = this.protectedData.mainPreSlices.volume;
|
|
133
|
-
this.guiParameterSettings.windowHigh.max = this.guiParameterSettings.windowLow.max = this.protectedData.mainPreSlices.volume.max;
|
|
134
|
-
this.guiParameterSettings.windowHigh.min = this.guiParameterSettings.windowLow.min = this.protectedData.mainPreSlices.volume.min;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
// ── Layer & Channel Management API ─────────────────────────────────────
|
|
138
|
-
/**
|
|
139
|
-
* Set the active layer and update fillColor/brushColor to match the active channel.
|
|
140
|
-
*/
|
|
141
|
-
setActiveLayer(layerId) {
|
|
142
|
-
this.gui_states.layerChannel.layer = layerId;
|
|
143
|
-
this.syncBrushColor();
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Set the active channel (1-8) and update fillColor/brushColor.
|
|
147
|
-
*/
|
|
148
|
-
setActiveChannel(channel) {
|
|
149
|
-
this.gui_states.layerChannel.activeChannel = channel;
|
|
150
|
-
this.syncBrushColor();
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Set the active sphere type for the SphereTool.
|
|
154
|
-
* Replaces direct mutation of `gui_states.activeSphereType`.
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* ```ts
|
|
158
|
-
* nrrdTools.setActiveSphereType('nipple');
|
|
159
|
-
* ```
|
|
160
|
-
*/
|
|
161
|
-
setActiveSphereType(type) {
|
|
162
|
-
this.gui_states.mode.activeSphereType = type;
|
|
163
|
-
// Apply color side-effect: update fillColor/brushColor from sphere channel map
|
|
164
|
-
const mapping = SPHERE_CHANNEL_MAP[type];
|
|
165
|
-
if (mapping) {
|
|
166
|
-
const volume = this.getVolumeForLayer(mapping.layer);
|
|
167
|
-
const color = volume
|
|
168
|
-
? rgbaToHex(volume.getChannelColor(mapping.channel))
|
|
169
|
-
: (CHANNEL_HEX_COLORS[mapping.channel] || '#00ff00');
|
|
170
|
-
this.gui_states.drawing.fillColor = color;
|
|
171
|
-
this.gui_states.drawing.brushColor = color;
|
|
187
|
+
if (this.guiParameterSettings && this.state.protectedData.mainPreSlices) {
|
|
188
|
+
this.guiParameterSettings.windowHigh.value = this.guiParameterSettings.windowLow.value = this.state.protectedData.mainPreSlices.volume;
|
|
189
|
+
this.guiParameterSettings.windowHigh.max = this.guiParameterSettings.windowLow.max = this.state.protectedData.mainPreSlices.volume.max;
|
|
190
|
+
this.guiParameterSettings.windowHigh.min = this.guiParameterSettings.windowLow.min = this.state.protectedData.mainPreSlices.volume.min;
|
|
172
191
|
}
|
|
173
192
|
}
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
194
|
+
// 3. Public API — Mode / Slider / Color / Action
|
|
195
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
174
196
|
/**
|
|
175
|
-
*
|
|
176
|
-
*/
|
|
177
|
-
getActiveSphereType() {
|
|
178
|
-
return this.gui_states.mode.activeSphereType;
|
|
179
|
-
}
|
|
180
|
-
// ── Phase 1: GUI API — Mode Management ──────────────────────────────────
|
|
181
|
-
/**
|
|
182
|
-
* Set the current tool mode. Handles deactivation of the previous mode
|
|
183
|
-
* and activation of the new mode, including all gui.ts side-effects.
|
|
184
|
-
*
|
|
185
|
-
* Replaces direct mutation of `guiSettings.value.guiState["pencil"]` etc.
|
|
186
|
-
* from Vue components.
|
|
197
|
+
* Set the current tool mode.
|
|
187
198
|
*/
|
|
188
199
|
setMode(mode) {
|
|
189
200
|
if (!this.guiCallbacks)
|
|
190
201
|
return;
|
|
191
|
-
const prevSphere = this.gui_states.mode.sphere;
|
|
202
|
+
const prevSphere = this.state.gui_states.mode.sphere;
|
|
192
203
|
const prevCalculator = this._calculatorActive;
|
|
193
|
-
|
|
194
|
-
this.gui_states.mode.
|
|
195
|
-
this.gui_states.mode.
|
|
196
|
-
this.gui_states.mode.sphere = false;
|
|
204
|
+
this.state.gui_states.mode.pencil = false;
|
|
205
|
+
this.state.gui_states.mode.eraser = false;
|
|
206
|
+
this.state.gui_states.mode.sphere = false;
|
|
197
207
|
this._calculatorActive = false;
|
|
198
|
-
// Activate new mode
|
|
199
208
|
switch (mode) {
|
|
200
209
|
case "pencil":
|
|
201
|
-
this.gui_states.mode.pencil = true;
|
|
210
|
+
this.state.gui_states.mode.pencil = true;
|
|
202
211
|
this.guiCallbacks.updatePencilState();
|
|
203
212
|
break;
|
|
204
213
|
case "brush":
|
|
205
|
-
// brush = pencil off + eraser off (default brush mode)
|
|
206
214
|
this.guiCallbacks.updatePencilState();
|
|
207
215
|
break;
|
|
208
216
|
case "eraser":
|
|
209
|
-
this.gui_states.mode.eraser = true;
|
|
217
|
+
this.state.gui_states.mode.eraser = true;
|
|
210
218
|
this.guiCallbacks.updateEraserState();
|
|
211
219
|
break;
|
|
212
220
|
case "sphere":
|
|
213
|
-
this.gui_states.mode.sphere = true;
|
|
221
|
+
this.state.gui_states.mode.sphere = true;
|
|
214
222
|
break;
|
|
215
223
|
case "calculator":
|
|
216
|
-
this.gui_states.mode.sphere = true;
|
|
224
|
+
this.state.gui_states.mode.sphere = true;
|
|
217
225
|
this._calculatorActive = true;
|
|
218
226
|
break;
|
|
219
227
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.guiCallbacks.updateSphereState(); // exits sphere mode
|
|
228
|
+
if (prevSphere && !this.state.gui_states.mode.sphere) {
|
|
229
|
+
this.guiCallbacks.updateSphereState();
|
|
223
230
|
}
|
|
224
|
-
if (!prevSphere && this.gui_states.mode.sphere) {
|
|
225
|
-
this.guiCallbacks.updateSphereState();
|
|
231
|
+
if (!prevSphere && this.state.gui_states.mode.sphere) {
|
|
232
|
+
this.guiCallbacks.updateSphereState();
|
|
226
233
|
}
|
|
227
|
-
// Handle calculator exit side-effect
|
|
228
234
|
if (prevCalculator && !this._calculatorActive) {
|
|
229
|
-
// calculator was exited
|
|
235
|
+
// calculator was exited
|
|
230
236
|
}
|
|
231
237
|
}
|
|
232
|
-
/**
|
|
233
|
-
* Get the current tool mode based on gui_states flags.
|
|
234
|
-
*/
|
|
235
238
|
getMode() {
|
|
236
239
|
if (this._calculatorActive)
|
|
237
240
|
return "calculator";
|
|
238
|
-
if (this.gui_states.mode.sphere)
|
|
241
|
+
if (this.state.gui_states.mode.sphere)
|
|
239
242
|
return "sphere";
|
|
240
|
-
if (this.gui_states.mode.eraser)
|
|
243
|
+
if (this.state.gui_states.mode.eraser)
|
|
241
244
|
return "eraser";
|
|
242
|
-
if (this.gui_states.mode.pencil)
|
|
245
|
+
if (this.state.gui_states.mode.pencil)
|
|
243
246
|
return "pencil";
|
|
244
247
|
return "brush";
|
|
245
248
|
}
|
|
246
|
-
/**
|
|
247
|
-
* Check if calculator mode is active.
|
|
248
|
-
*/
|
|
249
249
|
isCalculatorActive() {
|
|
250
250
|
return this._calculatorActive;
|
|
251
251
|
}
|
|
252
|
-
// ── Phase 1: GUI API — Slider Methods ───────────────────────────────────
|
|
253
|
-
/**
|
|
254
|
-
* Set mask overlay opacity.
|
|
255
|
-
* @param value Opacity value [0.1, 1]
|
|
256
|
-
*/
|
|
257
252
|
setOpacity(value) {
|
|
258
|
-
this.gui_states.drawing.globalAlpha = Math.max(0.1, Math.min(1, value));
|
|
253
|
+
this.state.gui_states.drawing.globalAlpha = Math.max(0.1, Math.min(1, value));
|
|
259
254
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Get the current mask overlay opacity.
|
|
262
|
-
*/
|
|
263
255
|
getOpacity() {
|
|
264
|
-
return this.gui_states.drawing.globalAlpha;
|
|
256
|
+
return this.state.gui_states.drawing.globalAlpha;
|
|
265
257
|
}
|
|
266
|
-
/**
|
|
267
|
-
* Set brush and eraser size, and trigger cursor update.
|
|
268
|
-
* @param size Brush size [5, 50]
|
|
269
|
-
*/
|
|
270
258
|
setBrushSize(size) {
|
|
271
259
|
var _a;
|
|
272
|
-
this.gui_states.drawing.brushAndEraserSize = Math.max(5, Math.min(50, size));
|
|
260
|
+
this.state.gui_states.drawing.brushAndEraserSize = Math.max(5, Math.min(50, size));
|
|
273
261
|
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.updateBrushAndEraserSize();
|
|
274
262
|
}
|
|
275
|
-
/**
|
|
276
|
-
* Get the current brush/eraser size.
|
|
277
|
-
*/
|
|
278
263
|
getBrushSize() {
|
|
279
|
-
return this.gui_states.drawing.brushAndEraserSize;
|
|
264
|
+
return this.state.gui_states.drawing.brushAndEraserSize;
|
|
280
265
|
}
|
|
281
|
-
/**
|
|
282
|
-
* Set window high (image contrast) value.
|
|
283
|
-
* Call finishWindowAdjustment() when the user finishes dragging.
|
|
284
|
-
*/
|
|
285
266
|
setWindowHigh(value) {
|
|
286
267
|
var _a;
|
|
287
|
-
this.gui_states.viewConfig.readyToUpdate = false;
|
|
268
|
+
this.state.gui_states.viewConfig.readyToUpdate = false;
|
|
288
269
|
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.updateWindowHigh(value);
|
|
289
270
|
}
|
|
290
|
-
/**
|
|
291
|
-
* Set window low (image center) value.
|
|
292
|
-
* Call finishWindowAdjustment() when the user finishes dragging.
|
|
293
|
-
*/
|
|
294
271
|
setWindowLow(value) {
|
|
295
272
|
var _a;
|
|
296
|
-
this.gui_states.viewConfig.readyToUpdate = false;
|
|
273
|
+
this.state.gui_states.viewConfig.readyToUpdate = false;
|
|
297
274
|
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.updateWindowLow(value);
|
|
298
275
|
}
|
|
299
|
-
/**
|
|
300
|
-
* Finish a window/contrast adjustment (repaint all contrast slices).
|
|
301
|
-
*/
|
|
302
276
|
finishWindowAdjustment() {
|
|
303
277
|
var _a;
|
|
304
278
|
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.finishContrastAdjustment();
|
|
305
279
|
}
|
|
306
|
-
/**
|
|
307
|
-
* Adjust contrast by delta, used for drag-based contrast adjustment.
|
|
308
|
-
* @param type "windowHigh" or "windowLow"
|
|
309
|
-
* @param delta Delta amount to adjust
|
|
310
|
-
*/
|
|
311
280
|
adjustContrast(type, delta) {
|
|
312
281
|
var _a, _b;
|
|
313
282
|
if (!this.guiParameterSettings)
|
|
314
283
|
return;
|
|
315
284
|
const setting = this.guiParameterSettings[type];
|
|
316
|
-
// setting.value is the volume object at runtime (typed as null in interface)
|
|
317
285
|
const vol = setting.value;
|
|
318
286
|
const currentValue = type === "windowHigh"
|
|
319
287
|
? ((_a = vol === null || vol === void 0 ? void 0 : vol.windowHigh) !== null && _a !== void 0 ? _a : 0)
|
|
@@ -328,56 +296,46 @@ export class NrrdTools extends DrawToolCore {
|
|
|
328
296
|
this.setWindowLow(newValue);
|
|
329
297
|
}
|
|
330
298
|
}
|
|
331
|
-
/**
|
|
332
|
-
* Get slider metadata for UI configuration.
|
|
333
|
-
* @param key Slider key: "globalAlpha", "brushAndEraserSize", "windowHigh", "windowLow"
|
|
334
|
-
* @returns IGuiMeta with min, max, step, and current value
|
|
335
|
-
*/
|
|
336
299
|
getSliderMeta(key) {
|
|
337
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
300
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
338
301
|
if (!this.guiParameterSettings)
|
|
339
302
|
return null;
|
|
303
|
+
// Per-layer alpha: read directly from layerOpacity state
|
|
304
|
+
if (key === "layerAlpha") {
|
|
305
|
+
const activeLayer = this.state.gui_states.layerChannel.layer;
|
|
306
|
+
return {
|
|
307
|
+
min: 0.1,
|
|
308
|
+
max: 1,
|
|
309
|
+
step: 0.01,
|
|
310
|
+
value: (_b = (_a = this.state.gui_states.layerChannel.layerOpacity) === null || _a === void 0 ? void 0 : _a[activeLayer]) !== null && _b !== void 0 ? _b : 1.0,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
340
313
|
const setting = this.guiParameterSettings[key];
|
|
341
314
|
if (!setting)
|
|
342
315
|
return null;
|
|
343
316
|
let value;
|
|
344
317
|
if (key === "windowHigh") {
|
|
345
|
-
value = (
|
|
318
|
+
value = (_d = (_c = setting.value) === null || _c === void 0 ? void 0 : _c.windowHigh) !== null && _d !== void 0 ? _d : 0;
|
|
346
319
|
}
|
|
347
320
|
else if (key === "windowLow") {
|
|
348
|
-
value = (
|
|
321
|
+
value = (_f = (_e = setting.value) === null || _e === void 0 ? void 0 : _e.windowLow) !== null && _f !== void 0 ? _f : 0;
|
|
349
322
|
}
|
|
350
323
|
else {
|
|
351
|
-
value = (
|
|
324
|
+
value = (_g = this.state.gui_states.drawing[key]) !== null && _g !== void 0 ? _g : 0;
|
|
352
325
|
}
|
|
353
326
|
return {
|
|
354
|
-
min: (
|
|
355
|
-
max: (
|
|
356
|
-
step: (
|
|
327
|
+
min: (_h = setting.min) !== null && _h !== void 0 ? _h : 0,
|
|
328
|
+
max: (_j = setting.max) !== null && _j !== void 0 ? _j : 100,
|
|
329
|
+
step: (_k = setting.step) !== null && _k !== void 0 ? _k : 1,
|
|
357
330
|
value,
|
|
358
331
|
};
|
|
359
332
|
}
|
|
360
|
-
// ── Phase 1: GUI API — Color & Action Methods ──────────────────────────
|
|
361
|
-
/**
|
|
362
|
-
* Set the pencil stroke color.
|
|
363
|
-
*/
|
|
364
333
|
setPencilColor(hex) {
|
|
365
|
-
this.gui_states.drawing.color = hex;
|
|
334
|
+
this.state.gui_states.drawing.color = hex;
|
|
366
335
|
}
|
|
367
|
-
/**
|
|
368
|
-
* Get the current pencil stroke color.
|
|
369
|
-
*/
|
|
370
336
|
getPencilColor() {
|
|
371
|
-
return this.gui_states.drawing.color;
|
|
337
|
+
return this.state.gui_states.drawing.color;
|
|
372
338
|
}
|
|
373
|
-
/**
|
|
374
|
-
* Execute a named UI action.
|
|
375
|
-
* @param action Action name
|
|
376
|
-
*
|
|
377
|
-
* Renamed from original gui_states methods:
|
|
378
|
-
* - "clearActiveSliceMask" (was "clear") — clear annotations on current slice
|
|
379
|
-
* - "clearActiveLayerMask" (was "clearAll") — clear annotations on all slices for active layer
|
|
380
|
-
*/
|
|
381
339
|
executeAction(action) {
|
|
382
340
|
switch (action) {
|
|
383
341
|
case "undo":
|
|
@@ -387,811 +345,237 @@ export class NrrdTools extends DrawToolCore {
|
|
|
387
345
|
this.redo();
|
|
388
346
|
break;
|
|
389
347
|
case "clearActiveSliceMask":
|
|
390
|
-
this.clearActiveSlice();
|
|
348
|
+
this.drawCore.clearActiveSlice();
|
|
391
349
|
break;
|
|
392
350
|
case "clearActiveLayerMask": {
|
|
393
351
|
const text = "Are you sure remove annotations on All slice?";
|
|
394
352
|
if (confirm(text) === true) {
|
|
395
|
-
this.nrrd_states.flags.clearAllFlag = true;
|
|
396
|
-
this.clearActiveSlice();
|
|
353
|
+
this.state.nrrd_states.flags.clearAllFlag = true;
|
|
354
|
+
this.drawCore.clearActiveSlice();
|
|
397
355
|
this.clearActiveLayer();
|
|
398
356
|
}
|
|
399
|
-
this.nrrd_states.flags.clearAllFlag = false;
|
|
357
|
+
this.state.nrrd_states.flags.clearAllFlag = false;
|
|
400
358
|
break;
|
|
401
359
|
}
|
|
402
360
|
case "resetZoom":
|
|
403
|
-
this.nrrd_states.view.sizeFactor = this.baseCanvasesSize;
|
|
404
|
-
this.gui_states.viewConfig.mainAreaSize = this.baseCanvasesSize;
|
|
405
|
-
this.resizePaintArea(this.nrrd_states.view.sizeFactor);
|
|
361
|
+
this.state.nrrd_states.view.sizeFactor = this.state.baseCanvasesSize;
|
|
362
|
+
this.state.gui_states.viewConfig.mainAreaSize = this.state.baseCanvasesSize;
|
|
363
|
+
this.resizePaintArea(this.state.nrrd_states.view.sizeFactor);
|
|
406
364
|
this.resetPaintAreaUIPosition();
|
|
407
365
|
break;
|
|
408
366
|
case "downloadCurrentMask": {
|
|
409
367
|
const config = {
|
|
410
|
-
axis: this.protectedData.axis,
|
|
411
|
-
currentSliceIndex: this.nrrd_states.view.currentSliceIndex,
|
|
412
|
-
drawingCanvas: this.protectedData.canvases.drawingCanvas,
|
|
413
|
-
originWidth: this.nrrd_states.image.originWidth,
|
|
414
|
-
originHeight: this.nrrd_states.image.originHeight,
|
|
368
|
+
axis: this.state.protectedData.axis,
|
|
369
|
+
currentSliceIndex: this.state.nrrd_states.view.currentSliceIndex,
|
|
370
|
+
drawingCanvas: this.state.protectedData.canvases.drawingCanvas,
|
|
371
|
+
originWidth: this.state.nrrd_states.image.originWidth,
|
|
372
|
+
originHeight: this.state.nrrd_states.image.originHeight,
|
|
415
373
|
};
|
|
416
374
|
enableDownload(config);
|
|
417
375
|
break;
|
|
418
376
|
}
|
|
419
377
|
}
|
|
420
378
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
*/
|
|
425
|
-
syncBrushColor() {
|
|
426
|
-
const channel = this.gui_states.layerChannel.activeChannel || 1;
|
|
427
|
-
const layer = this.gui_states.layerChannel.layer;
|
|
428
|
-
const volume = this.protectedData.maskData.volumes[layer];
|
|
429
|
-
if (volume) {
|
|
430
|
-
const hex = rgbaToHex(volume.getChannelColor(channel));
|
|
431
|
-
this.gui_states.drawing.fillColor = hex;
|
|
432
|
-
this.gui_states.drawing.brushColor = hex;
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
const hex = CHANNEL_HEX_COLORS[channel] || '#00ff00';
|
|
436
|
-
this.gui_states.drawing.fillColor = hex;
|
|
437
|
-
this.gui_states.drawing.brushColor = hex;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Get the currently active layer id.
|
|
442
|
-
*/
|
|
443
|
-
getActiveLayer() {
|
|
444
|
-
return this.gui_states.layerChannel.layer;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Get the currently active channel value.
|
|
448
|
-
*/
|
|
449
|
-
getActiveChannel() {
|
|
450
|
-
return this.gui_states.layerChannel.activeChannel;
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Set visibility of a layer and re-render.
|
|
454
|
-
*/
|
|
455
|
-
setLayerVisible(layerId, visible) {
|
|
456
|
-
this.gui_states.layerChannel.layerVisibility[layerId] = visible;
|
|
457
|
-
this.reloadMasksFromVolume();
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Check if a layer is visible.
|
|
461
|
-
*/
|
|
462
|
-
isLayerVisible(layerId) {
|
|
463
|
-
var _a;
|
|
464
|
-
return (_a = this.gui_states.layerChannel.layerVisibility[layerId]) !== null && _a !== void 0 ? _a : true;
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Set visibility of a specific channel within a layer and re-render.
|
|
468
|
-
*/
|
|
469
|
-
setChannelVisible(layerId, channel, visible) {
|
|
470
|
-
if (this.gui_states.layerChannel.channelVisibility[layerId]) {
|
|
471
|
-
this.gui_states.layerChannel.channelVisibility[layerId][channel] = visible;
|
|
472
|
-
}
|
|
473
|
-
this.reloadMasksFromVolume();
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Check if a specific channel within a layer is visible.
|
|
477
|
-
*/
|
|
478
|
-
isChannelVisible(layerId, channel) {
|
|
479
|
-
var _a, _b;
|
|
480
|
-
return (_b = (_a = this.gui_states.layerChannel.channelVisibility[layerId]) === null || _a === void 0 ? void 0 : _a[channel]) !== null && _b !== void 0 ? _b : true;
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Get the full layer visibility map.
|
|
484
|
-
*/
|
|
485
|
-
getLayerVisibility() {
|
|
486
|
-
return Object.assign({}, this.gui_states.layerChannel.layerVisibility);
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Get the full channel visibility map.
|
|
490
|
-
*/
|
|
491
|
-
getChannelVisibility() {
|
|
492
|
-
const result = {};
|
|
493
|
-
for (const layerId of this.nrrd_states.image.layers) {
|
|
494
|
-
result[layerId] = Object.assign({}, this.gui_states.layerChannel.channelVisibility[layerId]);
|
|
495
|
-
}
|
|
496
|
-
return result;
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Check if a specific layer contains any non-zero mask data.
|
|
500
|
-
*
|
|
501
|
-
* This is useful for determining if a layer has actual annotations
|
|
502
|
-
* before saving or exporting to avoid processing empty layers.
|
|
503
|
-
*
|
|
504
|
-
* @param layerId - The layer to check ('layer1', 'layer2', or 'layer3')
|
|
505
|
-
* @returns True if the layer has any non-zero voxel data, false if empty
|
|
506
|
-
*
|
|
507
|
-
* @example
|
|
508
|
-
* ```ts
|
|
509
|
-
* if (nrrdTools.hasLayerData('layer1')) {
|
|
510
|
-
* await useSaveMasks(caseId, 'layer1');
|
|
511
|
-
* }
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
|
-
hasLayerData(layerId) {
|
|
515
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
516
|
-
if (!volume) {
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
return volume.hasData();
|
|
520
|
-
}
|
|
521
|
-
// ── Custom Channel Color API ────────────────────────────────────────────
|
|
522
|
-
/**
|
|
523
|
-
* Set a custom color for a specific channel in a specific layer.
|
|
524
|
-
* Only affects this layer; other layers remain unchanged.
|
|
525
|
-
*
|
|
526
|
-
* @param layerId Layer to customize (e.g. 'layer1', 'layer2')
|
|
527
|
-
* @param channel Channel label (1-8)
|
|
528
|
-
* @param color RGBAColor object { r, g, b, a } (each 0-255)
|
|
529
|
-
*
|
|
530
|
-
* @example
|
|
531
|
-
* ```ts
|
|
532
|
-
* // Make channel 2 in layer2 orange
|
|
533
|
-
* nrrdTools.setChannelColor('layer2', 2, { r: 255, g: 128, b: 0, a: 255 });
|
|
534
|
-
* ```
|
|
535
|
-
*/
|
|
536
|
-
setChannelColor(layerId, channel, color) {
|
|
537
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
538
|
-
if (!volume) {
|
|
539
|
-
console.warn(`setChannelColor: unknown layer "${layerId}"`);
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
volume.setChannelColor(channel, color);
|
|
543
|
-
if (layerId === this.gui_states.layerChannel.layer && channel === this.gui_states.layerChannel.activeChannel) {
|
|
544
|
-
this.syncBrushColor();
|
|
545
|
-
}
|
|
546
|
-
this.reloadMasksFromVolume();
|
|
547
|
-
this.annotationCallbacks.onChannelColorChanged(layerId, channel, color);
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Get the current color for a specific channel in a specific layer.
|
|
551
|
-
*
|
|
552
|
-
* @param layerId Layer to query
|
|
553
|
-
* @param channel Channel label (1-8)
|
|
554
|
-
* @returns RGBAColor object (copy, safe to mutate)
|
|
555
|
-
*/
|
|
556
|
-
getChannelColor(layerId, channel) {
|
|
557
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
558
|
-
if (!volume) {
|
|
559
|
-
return { r: 0, g: 255, b: 0, a: 255 };
|
|
560
|
-
}
|
|
561
|
-
return volume.getChannelColor(channel);
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Get a hex color string for a channel in a layer (e.g. '#ff8000').
|
|
565
|
-
*/
|
|
566
|
-
getChannelHexColor(layerId, channel) {
|
|
567
|
-
return rgbaToHex(this.getChannelColor(layerId, channel));
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Get a CSS rgba() color string for a channel in a layer.
|
|
571
|
-
*/
|
|
572
|
-
getChannelCssColor(layerId, channel) {
|
|
573
|
-
return rgbaToCss(this.getChannelColor(layerId, channel));
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Batch-set multiple channel colors for a single layer (single re-render).
|
|
577
|
-
*
|
|
578
|
-
* @param layerId Layer to customize
|
|
579
|
-
* @param colorMap Partial map of channel -> RGBAColor
|
|
580
|
-
*
|
|
581
|
-
* @example
|
|
582
|
-
* ```ts
|
|
583
|
-
* nrrdTools.setChannelColors('layer2', {
|
|
584
|
-
* 2: { r: 255, g: 128, b: 0, a: 255 },
|
|
585
|
-
* 3: { r: 100, g: 200, b: 50, a: 255 },
|
|
586
|
-
* });
|
|
587
|
-
* ```
|
|
588
|
-
*/
|
|
589
|
-
setChannelColors(layerId, colorMap) {
|
|
590
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
591
|
-
if (!volume) {
|
|
592
|
-
console.warn(`setChannelColors: unknown layer "${layerId}"`);
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
for (const [ch, color] of Object.entries(colorMap)) {
|
|
596
|
-
volume.setChannelColor(Number(ch), color);
|
|
597
|
-
}
|
|
598
|
-
if (layerId === this.gui_states.layerChannel.layer) {
|
|
599
|
-
this.syncBrushColor();
|
|
600
|
-
}
|
|
601
|
-
this.reloadMasksFromVolume();
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Set the same color for a specific channel across ALL layers (single re-render).
|
|
605
|
-
*
|
|
606
|
-
* @param channel Channel label (1-8)
|
|
607
|
-
* @param color RGBAColor object
|
|
608
|
-
*/
|
|
609
|
-
setAllLayersChannelColor(channel, color) {
|
|
610
|
-
for (const layerId of this.nrrd_states.image.layers) {
|
|
611
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
612
|
-
if (volume) {
|
|
613
|
-
volume.setChannelColor(channel, color);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
if (channel === this.gui_states.layerChannel.activeChannel) {
|
|
617
|
-
this.syncBrushColor();
|
|
618
|
-
}
|
|
619
|
-
this.reloadMasksFromVolume();
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Reset channel colors to system defaults.
|
|
623
|
-
*
|
|
624
|
-
* @param layerId Optional. If omitted, resets all layers.
|
|
625
|
-
* @param channel Optional. If omitted, resets all channels in the layer(s).
|
|
626
|
-
*
|
|
627
|
-
* @example
|
|
628
|
-
* ```ts
|
|
629
|
-
* nrrdTools.resetChannelColors('layer2', 2); // Reset only ch2 in layer2
|
|
630
|
-
* nrrdTools.resetChannelColors('layer2'); // Reset all channels in layer2
|
|
631
|
-
* nrrdTools.resetChannelColors(); // Reset all layers
|
|
632
|
-
* ```
|
|
633
|
-
*/
|
|
634
|
-
resetChannelColors(layerId, channel) {
|
|
635
|
-
const layers = layerId ? [layerId] : this.nrrd_states.image.layers;
|
|
636
|
-
for (const lid of layers) {
|
|
637
|
-
const volume = this.protectedData.maskData.volumes[lid];
|
|
638
|
-
if (volume) {
|
|
639
|
-
volume.resetChannelColors(channel);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
this.syncBrushColor();
|
|
643
|
-
this.reloadMasksFromVolume();
|
|
644
|
-
}
|
|
645
|
-
// ── Keyboard & History API ──────────────────────────────────────────────
|
|
646
|
-
/**
|
|
647
|
-
* Programmatically trigger an undo operation.
|
|
648
|
-
*
|
|
649
|
-
* Equivalent to pressing Ctrl+Z. Reverts the most recent drawing stroke
|
|
650
|
-
* on the currently active layer and syncs the result to the backend via
|
|
651
|
-
* the `getMask` callback.
|
|
652
|
-
*
|
|
653
|
-
* @example
|
|
654
|
-
* ```ts
|
|
655
|
-
* undoBtn.addEventListener('click', () => nrrdTools.undo());
|
|
656
|
-
* ```
|
|
657
|
-
*/
|
|
379
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
380
|
+
// 4. Public API — Keyboard & History
|
|
381
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
658
382
|
undo() {
|
|
659
|
-
this.undoLastPainting();
|
|
383
|
+
this.drawCore.undoLastPainting();
|
|
660
384
|
}
|
|
661
|
-
/**
|
|
662
|
-
* Programmatically trigger a redo operation.
|
|
663
|
-
*
|
|
664
|
-
* Equivalent to pressing Ctrl+Y. Re-applies the most recently undone
|
|
665
|
-
* drawing stroke on the currently active layer and syncs the result to
|
|
666
|
-
* the backend via the `getMask` callback.
|
|
667
|
-
*
|
|
668
|
-
* @example
|
|
669
|
-
* ```ts
|
|
670
|
-
* redoBtn.addEventListener('click', () => nrrdTools.redo());
|
|
671
|
-
* ```
|
|
672
|
-
*/
|
|
673
385
|
redo() {
|
|
674
|
-
this.redoLastPainting();
|
|
386
|
+
this.drawCore.redoLastPainting();
|
|
675
387
|
}
|
|
676
|
-
/**
|
|
677
|
-
* Enter keyboard-configuration mode.
|
|
678
|
-
*
|
|
679
|
-
* While active, every keydown/keyup handler in DrawToolCore and DragOperator
|
|
680
|
-
* is suppressed so the user can press arbitrary keys in the settings dialog
|
|
681
|
-
* without accidentally triggering drawing, undo, or contrast shortcuts.
|
|
682
|
-
*
|
|
683
|
-
* Always pair with {@link exitKeyboardConfig} when the dialog closes.
|
|
684
|
-
*
|
|
685
|
-
* @example
|
|
686
|
-
* ```ts
|
|
687
|
-
* dialog.addEventListener('open', () => nrrdTools.enterKeyboardConfig());
|
|
688
|
-
* ```
|
|
689
|
-
*/
|
|
690
388
|
enterKeyboardConfig() {
|
|
691
|
-
this.
|
|
389
|
+
this.state.configKeyBoard = true;
|
|
692
390
|
}
|
|
693
|
-
/**
|
|
694
|
-
* Exit keyboard-configuration mode and resume normal shortcut handling.
|
|
695
|
-
*
|
|
696
|
-
* @example
|
|
697
|
-
* ```ts
|
|
698
|
-
* dialog.addEventListener('close', () => nrrdTools.exitKeyboardConfig());
|
|
699
|
-
* ```
|
|
700
|
-
*/
|
|
701
391
|
exitKeyboardConfig() {
|
|
702
|
-
this.
|
|
392
|
+
this.state.configKeyBoard = false;
|
|
703
393
|
}
|
|
704
|
-
/**
|
|
705
|
-
* Enable or disable the contrast window/level shortcut (Ctrl/Meta key).
|
|
706
|
-
*
|
|
707
|
-
* When disabled:
|
|
708
|
-
* - Holding Ctrl no longer enters contrast mode
|
|
709
|
-
* - If contrast mode is currently active it is immediately exited
|
|
710
|
-
* - All other Ctrl-based shortcuts (Ctrl+Z undo, Ctrl+Y redo) are
|
|
711
|
-
* unaffected because they are checked in the keydown handler before
|
|
712
|
-
* the contrast guard runs
|
|
713
|
-
*
|
|
714
|
-
* @param enabled - Pass `false` to disable, `true` to re-enable.
|
|
715
|
-
*
|
|
716
|
-
* @example
|
|
717
|
-
* ```ts
|
|
718
|
-
* // Disable contrast when sphere tool is active
|
|
719
|
-
* nrrdTools.setContrastShortcutEnabled(false);
|
|
720
|
-
*
|
|
721
|
-
* // Re-enable when returning to draw mode
|
|
722
|
-
* nrrdTools.setContrastShortcutEnabled(true);
|
|
723
|
-
* ```
|
|
724
|
-
*/
|
|
725
394
|
setContrastShortcutEnabled(enabled) {
|
|
726
395
|
var _a;
|
|
727
|
-
(_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.setContrastEnabled(enabled);
|
|
396
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setContrastEnabled(enabled);
|
|
728
397
|
}
|
|
729
|
-
/**
|
|
730
|
-
* Returns whether the contrast shortcut is currently enabled.
|
|
731
|
-
*/
|
|
732
398
|
isContrastShortcutEnabled() {
|
|
733
399
|
var _a, _b;
|
|
734
|
-
return (_b = (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()) !== null && _b !== void 0 ? _b : true;
|
|
400
|
+
return (_b = (_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()) !== null && _b !== void 0 ? _b : true;
|
|
735
401
|
}
|
|
736
|
-
/**
|
|
737
|
-
* Update keyboard shortcut bindings.
|
|
738
|
-
*
|
|
739
|
-
* Synchronises both the internal class field (read by every keydown handler)
|
|
740
|
-
* and the EventRouter's internal copy (used for modifier-key mode tracking)
|
|
741
|
-
* so the two never drift apart. If `mouseWheel` is changed the wheel event
|
|
742
|
-
* listener is automatically re-bound via {@link updateMouseWheelEvent}.
|
|
743
|
-
*
|
|
744
|
-
* Only the fields you supply are updated; omitted fields keep their
|
|
745
|
-
* current values.
|
|
746
|
-
*
|
|
747
|
-
* @param settings - Partial keyboard settings to override.
|
|
748
|
-
*
|
|
749
|
-
* @example
|
|
750
|
-
* ```ts
|
|
751
|
-
* nrrdTools.setKeyboardSettings({
|
|
752
|
-
* undo: 'z',
|
|
753
|
-
* redo: 'y',
|
|
754
|
-
* crosshair: 'c',
|
|
755
|
-
* mouseWheel: 'Scroll:Slice',
|
|
756
|
-
* });
|
|
757
|
-
* ```
|
|
758
|
-
*/
|
|
759
402
|
setKeyboardSettings(settings) {
|
|
760
403
|
var _a;
|
|
761
404
|
const mouseWheelChanged = settings.mouseWheel !== undefined
|
|
762
|
-
&& settings.mouseWheel !== this.
|
|
763
|
-
Object.assign(this.
|
|
764
|
-
(_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.setKeyboardSettings(settings);
|
|
405
|
+
&& settings.mouseWheel !== this.state.keyboardSettings.mouseWheel;
|
|
406
|
+
Object.assign(this.state.keyboardSettings, settings);
|
|
407
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setKeyboardSettings(settings);
|
|
765
408
|
if (mouseWheelChanged) {
|
|
766
409
|
this.updateMouseWheelEvent();
|
|
767
410
|
}
|
|
768
411
|
}
|
|
769
|
-
/**
|
|
770
|
-
* Get a snapshot of the current keyboard shortcut bindings.
|
|
771
|
-
*
|
|
772
|
-
* Returns a shallow copy so callers cannot accidentally mutate internal
|
|
773
|
-
* state. To update settings use {@link setKeyboardSettings} instead.
|
|
774
|
-
*
|
|
775
|
-
* @returns Current keyboard settings.
|
|
776
|
-
*
|
|
777
|
-
* @example
|
|
778
|
-
* ```ts
|
|
779
|
-
* const { undo, redo } = nrrdTools.getKeyboardSettings();
|
|
780
|
-
* console.log(`Undo: Ctrl+${undo}, Redo: Ctrl+${redo}`);
|
|
781
|
-
* ```
|
|
782
|
-
*/
|
|
783
412
|
getKeyboardSettings() {
|
|
784
|
-
return Object.assign({}, this.
|
|
413
|
+
return Object.assign({}, this.state.keyboardSettings);
|
|
785
414
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
this.
|
|
791
|
-
this.container.appendChild(this.protectedData.mainAreaContainer);
|
|
792
|
-
autoFocusDiv(this.container);
|
|
793
|
-
this.setShowInMainArea();
|
|
415
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
416
|
+
// 5. Public API — View Control (drag, zoom, pan, slice navigation)
|
|
417
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
418
|
+
drag(opts) {
|
|
419
|
+
this.dragOperator.drag(opts);
|
|
794
420
|
}
|
|
795
421
|
/**
|
|
796
|
-
*
|
|
797
|
-
* entry function
|
|
798
|
-
* * {
|
|
799
|
-
* x:slice,
|
|
800
|
-
* y:slice,
|
|
801
|
-
* z:slice
|
|
802
|
-
* }
|
|
803
|
-
*
|
|
804
|
-
* @param allSlices - all nrrd contrast slices
|
|
805
|
-
|
|
422
|
+
* Core drawing entry point.
|
|
806
423
|
*/
|
|
807
|
-
|
|
808
|
-
this.
|
|
809
|
-
const randomSlice = this.protectedData.allSlicesArray[0];
|
|
810
|
-
this.nrrd_states.image.nrrd_x_mm = randomSlice.z.canvas.width;
|
|
811
|
-
this.nrrd_states.image.nrrd_y_mm = randomSlice.z.canvas.height;
|
|
812
|
-
this.nrrd_states.image.nrrd_z_mm = randomSlice.x.canvas.width;
|
|
813
|
-
this.nrrd_states.image.nrrd_x_pixel = randomSlice.x.volume.dimensions[0];
|
|
814
|
-
this.nrrd_states.image.nrrd_y_pixel = randomSlice.x.volume.dimensions[1];
|
|
815
|
-
this.nrrd_states.image.nrrd_z_pixel = randomSlice.x.volume.dimensions[2];
|
|
816
|
-
this.nrrd_states.image.voxelSpacing = randomSlice.x.volume.spacing;
|
|
817
|
-
this.nrrd_states.image.ratios.x = randomSlice.x.volume.spacing[0];
|
|
818
|
-
this.nrrd_states.image.ratios.y = randomSlice.x.volume.spacing[1];
|
|
819
|
-
this.nrrd_states.image.ratios.z = randomSlice.x.volume.spacing[2];
|
|
820
|
-
this.nrrd_states.image.dimensions = randomSlice.x.volume.dimensions;
|
|
821
|
-
// Phase 2 Day 9: Re-initialize MaskVolume with real NRRD dimensions.
|
|
822
|
-
// This replaces the 1×1×1 placeholders from CommToolsData constructor
|
|
823
|
-
// and "turns on" all Day 7/8 volume read/write paths.
|
|
824
|
-
// Invalidate reusable buffer from previous dataset
|
|
825
|
-
this.invalidateSliceBuffer();
|
|
826
|
-
const [vw, vh, vd] = this.nrrd_states.image.dimensions;
|
|
827
|
-
this.protectedData.maskData.volumes = this.nrrd_states.image.layers.reduce((acc, id) => {
|
|
828
|
-
acc[id] = new MaskVolume(vw, vh, vd, 1);
|
|
829
|
-
return acc;
|
|
830
|
-
}, {});
|
|
831
|
-
// Create dedicated SphereMaskVolume for 3D sphere data.
|
|
832
|
-
// Separate from layer volumes to avoid polluting draw mask data.
|
|
833
|
-
// Cleared in reset() when switching cases.
|
|
834
|
-
this.nrrd_states.sphere.sphereMaskVolume = new MaskVolume(vw, vh, vd, 1);
|
|
835
|
-
// Derive sphere label colors from SPHERE_CHANNEL_MAP → MASK_CHANNEL_COLORS
|
|
836
|
-
// so that volume rendering matches the preview circle colors.
|
|
837
|
-
for (const [type, { channel }] of Object.entries(SPHERE_CHANNEL_MAP)) {
|
|
838
|
-
const label = SPHERE_LABELS[type];
|
|
839
|
-
const c = MASK_CHANNEL_COLORS[channel];
|
|
840
|
-
this.nrrd_states.sphere.sphereMaskVolume.setChannelColor(label, { r: c.r, g: c.g, b: c.b, a: c.a });
|
|
841
|
-
}
|
|
842
|
-
this.nrrd_states.image.spaceOrigin = randomSlice.x.volume.header.space_origin.map((item) => {
|
|
843
|
-
return item * 1;
|
|
844
|
-
});
|
|
845
|
-
this.protectedData.allSlicesArray.forEach((item, index) => {
|
|
846
|
-
item.x.contrastOrder = index;
|
|
847
|
-
item.y.contrastOrder = index;
|
|
848
|
-
item.z.contrastOrder = index;
|
|
849
|
-
});
|
|
850
|
-
// Phase 3: initPaintImages removed (MaskVolume initialized separately)
|
|
851
|
-
// this.initPaintImages(this.nrrd_states.image.dimensions);
|
|
852
|
-
// init displayslices array, the axis default is "z"
|
|
853
|
-
this.setDisplaySlicesBaseOnAxis();
|
|
854
|
-
this.afterLoadSlice();
|
|
855
|
-
}
|
|
856
|
-
loadingMaskByLayer(masks, index, imageData) {
|
|
857
|
-
let imageDataLable = this.protectedData.ctxes.emptyCtx.createImageData(this.nrrd_states.image.nrrd_x_pixel, this.nrrd_states.image.nrrd_y_pixel);
|
|
858
|
-
this.setEmptyCanvasSize();
|
|
859
|
-
for (let j = 0; j < masks[index].data.length; j++) {
|
|
860
|
-
imageDataLable.data[j] = masks[index].data[j];
|
|
861
|
-
imageData.data[j] += masks[index].data[j];
|
|
862
|
-
}
|
|
863
|
-
return imageDataLable;
|
|
864
|
-
}
|
|
865
|
-
// need to remove
|
|
866
|
-
setMasksData(masksData, loadingBar) {
|
|
867
|
-
if (!!masksData) {
|
|
868
|
-
this.nrrd_states.flags.loadingMaskData = true;
|
|
869
|
-
if (loadingBar) {
|
|
870
|
-
let { loadingContainer, progress } = loadingBar;
|
|
871
|
-
loadingContainer.style.display = "flex";
|
|
872
|
-
progress.innerText = "Loading masks data......";
|
|
873
|
-
}
|
|
874
|
-
this.setEmptyCanvasSize();
|
|
875
|
-
const len = masksData["layer1"].length;
|
|
876
|
-
for (let i = 0; i < len; i++) {
|
|
877
|
-
let imageData = this.protectedData.ctxes.emptyCtx.createImageData(this.nrrd_states.image.nrrd_x_pixel, this.nrrd_states.image.nrrd_y_pixel);
|
|
878
|
-
if (masksData["layer1"][i].data.length > 0) {
|
|
879
|
-
this.loadingMaskByLayer(masksData["layer1"], i, imageData);
|
|
880
|
-
}
|
|
881
|
-
if (masksData["layer2"][i].data.length > 0) {
|
|
882
|
-
this.loadingMaskByLayer(masksData["layer2"], i, imageData);
|
|
883
|
-
}
|
|
884
|
-
if (masksData["layer3"][i].data.length > 0) {
|
|
885
|
-
this.loadingMaskByLayer(masksData["layer3"], i, imageData);
|
|
886
|
-
}
|
|
887
|
-
this.setEmptyCanvasSize();
|
|
888
|
-
this.protectedData.ctxes.emptyCtx.putImageData(imageData, 0, 0);
|
|
889
|
-
this.syncLayerSliceData(i, "default");
|
|
890
|
-
}
|
|
891
|
-
this.nrrd_states.flags.loadingMaskData = false;
|
|
892
|
-
this.executeAction("resetZoom");
|
|
893
|
-
if (loadingBar) {
|
|
894
|
-
loadingBar.loadingContainer.style.display = "none";
|
|
895
|
-
}
|
|
896
|
-
}
|
|
424
|
+
draw(opts) {
|
|
425
|
+
this.drawCore.draw(opts);
|
|
897
426
|
}
|
|
898
427
|
/**
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
* Expects pre-extracted voxel bytes (e.g. from useNiftiVoxelData).
|
|
902
|
-
*
|
|
903
|
-
* @param layerVoxels Map of layer ID to raw voxel Uint8Array
|
|
904
|
-
* Keys should be 'layer1', 'layer2', 'layer3'
|
|
905
|
-
* @param loadingBar Optional loading bar UI
|
|
428
|
+
* Set the Draw Display Canvas base size
|
|
906
429
|
*/
|
|
907
|
-
|
|
908
|
-
if (
|
|
909
|
-
|
|
910
|
-
if (loadingBar) {
|
|
911
|
-
loadingBar.loadingContainer.style.display = "flex";
|
|
912
|
-
loadingBar.progress.innerText = "Loading mask layers from NIfTI...";
|
|
913
|
-
}
|
|
914
|
-
try {
|
|
915
|
-
for (const [layerId, rawData] of layerVoxels) {
|
|
916
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
917
|
-
if (!volume) {
|
|
918
|
-
console.warn(`setMasksFromNIfTI: unknown layer "${layerId}", skipping`);
|
|
919
|
-
continue;
|
|
920
|
-
}
|
|
921
|
-
const expectedLen = volume.getRawData().length;
|
|
922
|
-
// Ensure we copy exactly the right number of bytes
|
|
923
|
-
if (rawData.length >= expectedLen) {
|
|
924
|
-
volume.setRawData(rawData.slice(0, expectedLen));
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
const padded = new Uint8Array(expectedLen);
|
|
928
|
-
padded.set(rawData);
|
|
929
|
-
volume.setRawData(padded);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
// Reload the current slice from MaskVolume to canvas
|
|
933
|
-
this.reloadMasksFromVolume();
|
|
934
|
-
this.executeAction("resetZoom");
|
|
430
|
+
setBaseDrawDisplayCanvasesSize(size) {
|
|
431
|
+
if (size > 8) {
|
|
432
|
+
this.state.baseCanvasesSize = 8;
|
|
935
433
|
}
|
|
936
|
-
|
|
937
|
-
|
|
434
|
+
else if (size < 1 || typeof size !== "number") {
|
|
435
|
+
this.state.baseCanvasesSize = 1;
|
|
938
436
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
loadingBar.loadingContainer.style.display = "none";
|
|
942
|
-
}
|
|
437
|
+
else {
|
|
438
|
+
this.state.baseCanvasesSize = size;
|
|
943
439
|
}
|
|
944
440
|
}
|
|
945
|
-
|
|
946
|
-
this.
|
|
947
|
-
}
|
|
948
|
-
getCurrentImageDimension() {
|
|
949
|
-
return this.nrrd_states.image.dimensions;
|
|
950
|
-
}
|
|
951
|
-
getVoxelSpacing() {
|
|
952
|
-
return this.nrrd_states.image.voxelSpacing;
|
|
441
|
+
setDisplaySliceIndexPanel(panel) {
|
|
442
|
+
this.dragOperator.setShowDragNumberDiv(panel);
|
|
953
443
|
}
|
|
954
|
-
|
|
955
|
-
|
|
444
|
+
enableContrastDragEvents(callback) {
|
|
445
|
+
this.drawCore.setupConrastEvents(callback);
|
|
956
446
|
}
|
|
957
|
-
|
|
958
|
-
|
|
447
|
+
setEraserUrls(urls) {
|
|
448
|
+
this.drawCore.setEraserUrls(urls);
|
|
959
449
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
*
|
|
963
|
-
* Replicates the full mouse-down → mouse-up flow (handleSphereClick + pointerup)
|
|
964
|
-
* so that backend-supplied sphere data is stored identically to a manual click.
|
|
965
|
-
*
|
|
966
|
-
* @param x - X coordinate in unscaled (original) image space
|
|
967
|
-
* @param y - Y coordinate in unscaled (original) image space
|
|
968
|
-
* @param sliceIndex - Target slice index (z-axis)
|
|
969
|
-
* @param cal_position - Sphere type to place
|
|
970
|
-
*/
|
|
971
|
-
setCalculateDistanceSphere(x, y, sliceIndex, cal_position) {
|
|
972
|
-
this.nrrd_states.sphere.sphereRadius = 5;
|
|
973
|
-
// move to target slice
|
|
974
|
-
const steps = sliceIndex - this.nrrd_states.view.currentSliceIndex;
|
|
975
|
-
this.setSliceMoving(steps * this.protectedData.displaySlices.length);
|
|
976
|
-
// --- simulate mouse-down (mirrors DrawToolCore.handleSphereClick) ---
|
|
977
|
-
// if user has zoomed the panel, we need to consider the size factor
|
|
978
|
-
const mouseX = x * this.nrrd_states.view.sizeFactor;
|
|
979
|
-
const mouseY = y * this.nrrd_states.view.sizeFactor;
|
|
980
|
-
// 1. record origin on current axis
|
|
981
|
-
this.nrrd_states.sphere.sphereOrigin[this.protectedData.axis] = [
|
|
982
|
-
mouseX, mouseY, sliceIndex,
|
|
983
|
-
];
|
|
984
|
-
// compute origins for all 3 axes (crosshairTool is protected)
|
|
985
|
-
this.crosshairTool.setUpSphereOrigins(mouseX, mouseY, sliceIndex);
|
|
986
|
-
// 2. store a deep copy of the origin for the specific sphere type
|
|
987
|
-
const originCopy = JSON.parse(JSON.stringify(this.nrrd_states.sphere.sphereOrigin));
|
|
988
|
-
switch (cal_position) {
|
|
989
|
-
case "tumour":
|
|
990
|
-
this.nrrd_states.sphere.tumourSphereOrigin = originCopy;
|
|
991
|
-
break;
|
|
992
|
-
case "skin":
|
|
993
|
-
this.nrrd_states.sphere.skinSphereOrigin = originCopy;
|
|
994
|
-
break;
|
|
995
|
-
case "nipple":
|
|
996
|
-
this.nrrd_states.sphere.nippleSphereOrigin = originCopy;
|
|
997
|
-
break;
|
|
998
|
-
case "ribcage":
|
|
999
|
-
this.nrrd_states.sphere.ribSphereOrigin = originCopy;
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1002
|
-
// 3. draw sphere preview on canvas
|
|
1003
|
-
this.drawCalculatorSphere(this.nrrd_states.sphere.sphereRadius);
|
|
1004
|
-
// --- simulate mouse-up ---
|
|
1005
|
-
// 4. write all placed calculator spheres into sphereMaskVolume
|
|
1006
|
-
this.sphereTool.writeAllCalculatorSpheresToVolume();
|
|
1007
|
-
// 5. re-render sphere overlay from volume
|
|
1008
|
-
this.sphereTool.refreshSphereCanvas();
|
|
450
|
+
setPencilIconUrls(urls) {
|
|
451
|
+
this.drawCore.setPencilIconUrls(urls);
|
|
1009
452
|
}
|
|
1010
453
|
/**
|
|
1011
454
|
* Switch all contrast slices' orientation
|
|
1012
|
-
|
|
1013
|
-
* */
|
|
455
|
+
*/
|
|
1014
456
|
setSliceOrientation(axisTo) {
|
|
1015
457
|
var _a;
|
|
1016
458
|
let convetObj;
|
|
1017
|
-
if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isCrosshairEnabled()) || this.gui_states.mode.sphere) {
|
|
1018
|
-
if (this.protectedData.axis === "z") {
|
|
1019
|
-
this.cursorPage.z.index = this.nrrd_states.view.currentSliceIndex;
|
|
1020
|
-
this.cursorPage.z.cursorPageX = this.nrrd_states.interaction.cursorPageX;
|
|
1021
|
-
this.cursorPage.z.cursorPageY = this.nrrd_states.interaction.cursorPageY;
|
|
459
|
+
if (((_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.isCrosshairEnabled()) || this.state.gui_states.mode.sphere) {
|
|
460
|
+
if (this.state.protectedData.axis === "z") {
|
|
461
|
+
this.state.cursorPage.z.index = this.state.nrrd_states.view.currentSliceIndex;
|
|
462
|
+
this.state.cursorPage.z.cursorPageX = this.state.nrrd_states.interaction.cursorPageX;
|
|
463
|
+
this.state.cursorPage.z.cursorPageY = this.state.nrrd_states.interaction.cursorPageY;
|
|
1022
464
|
}
|
|
1023
|
-
else if (this.protectedData.axis === "x") {
|
|
1024
|
-
this.cursorPage.x.index = this.nrrd_states.view.currentSliceIndex;
|
|
1025
|
-
this.cursorPage.x.cursorPageX = this.nrrd_states.interaction.cursorPageX;
|
|
1026
|
-
this.cursorPage.x.cursorPageY = this.nrrd_states.interaction.cursorPageY;
|
|
465
|
+
else if (this.state.protectedData.axis === "x") {
|
|
466
|
+
this.state.cursorPage.x.index = this.state.nrrd_states.view.currentSliceIndex;
|
|
467
|
+
this.state.cursorPage.x.cursorPageX = this.state.nrrd_states.interaction.cursorPageX;
|
|
468
|
+
this.state.cursorPage.x.cursorPageY = this.state.nrrd_states.interaction.cursorPageY;
|
|
1027
469
|
}
|
|
1028
|
-
else if (this.protectedData.axis === "y") {
|
|
1029
|
-
this.cursorPage.y.index = this.nrrd_states.view.currentSliceIndex;
|
|
1030
|
-
this.cursorPage.y.cursorPageX = this.nrrd_states.interaction.cursorPageX;
|
|
1031
|
-
this.cursorPage.y.cursorPageY = this.nrrd_states.interaction.cursorPageY;
|
|
470
|
+
else if (this.state.protectedData.axis === "y") {
|
|
471
|
+
this.state.cursorPage.y.index = this.state.nrrd_states.view.currentSliceIndex;
|
|
472
|
+
this.state.cursorPage.y.cursorPageX = this.state.nrrd_states.interaction.cursorPageX;
|
|
473
|
+
this.state.cursorPage.y.cursorPageY = this.state.nrrd_states.interaction.cursorPageY;
|
|
1032
474
|
}
|
|
1033
475
|
if (axisTo === "z") {
|
|
1034
|
-
if (this.nrrd_states.interaction.isCursorSelect && !this.cursorPage.z.updated) {
|
|
1035
|
-
if (this.protectedData.axis === "x") {
|
|
1036
|
-
|
|
1037
|
-
convetObj = this.convertCursorPoint("x", "z", this.cursorPage.x.cursorPageX, this.cursorPage.x.cursorPageY, this.cursorPage.x.index);
|
|
476
|
+
if (this.state.nrrd_states.interaction.isCursorSelect && !this.state.cursorPage.z.updated) {
|
|
477
|
+
if (this.state.protectedData.axis === "x") {
|
|
478
|
+
convetObj = this.drawCore.convertCursorPoint("x", "z", this.state.cursorPage.x.cursorPageX, this.state.cursorPage.x.cursorPageY, this.state.cursorPage.x.index);
|
|
1038
479
|
}
|
|
1039
|
-
if (this.protectedData.axis === "y") {
|
|
1040
|
-
|
|
1041
|
-
convetObj = this.convertCursorPoint("y", "z", this.cursorPage.y.cursorPageX, this.cursorPage.y.cursorPageY, this.cursorPage.y.index);
|
|
480
|
+
if (this.state.protectedData.axis === "y") {
|
|
481
|
+
convetObj = this.drawCore.convertCursorPoint("y", "z", this.state.cursorPage.y.cursorPageX, this.state.cursorPage.y.cursorPageY, this.state.cursorPage.y.index);
|
|
1042
482
|
}
|
|
1043
483
|
}
|
|
1044
484
|
else {
|
|
1045
|
-
|
|
1046
|
-
this.nrrd_states.view.
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
this.nrrd_states.interaction.
|
|
1050
|
-
this.nrrd_states.interaction.cursorPageY = this.cursorPage.z.cursorPageY;
|
|
485
|
+
this.state.nrrd_states.view.currentSliceIndex = this.state.cursorPage.z.index;
|
|
486
|
+
this.state.nrrd_states.view.preSliceIndex =
|
|
487
|
+
this.state.cursorPage.z.index * this.state.nrrd_states.image.ratios.z;
|
|
488
|
+
this.state.nrrd_states.interaction.cursorPageX = this.state.cursorPage.z.cursorPageX;
|
|
489
|
+
this.state.nrrd_states.interaction.cursorPageY = this.state.cursorPage.z.cursorPageY;
|
|
1051
490
|
}
|
|
1052
491
|
}
|
|
1053
492
|
else if (axisTo === "x") {
|
|
1054
|
-
if (this.nrrd_states.interaction.isCursorSelect && !this.cursorPage.x.updated) {
|
|
1055
|
-
if (this.protectedData.axis === "z") {
|
|
1056
|
-
|
|
1057
|
-
convetObj = this.convertCursorPoint("z", "x", this.cursorPage.z.cursorPageX, this.cursorPage.z.cursorPageY, this.cursorPage.z.index);
|
|
493
|
+
if (this.state.nrrd_states.interaction.isCursorSelect && !this.state.cursorPage.x.updated) {
|
|
494
|
+
if (this.state.protectedData.axis === "z") {
|
|
495
|
+
convetObj = this.drawCore.convertCursorPoint("z", "x", this.state.cursorPage.z.cursorPageX, this.state.cursorPage.z.cursorPageY, this.state.cursorPage.z.index);
|
|
1058
496
|
}
|
|
1059
|
-
if (this.protectedData.axis === "y") {
|
|
1060
|
-
|
|
1061
|
-
convetObj = this.convertCursorPoint("y", "x", this.cursorPage.y.cursorPageX, this.cursorPage.y.cursorPageY, this.cursorPage.y.index);
|
|
497
|
+
if (this.state.protectedData.axis === "y") {
|
|
498
|
+
convetObj = this.drawCore.convertCursorPoint("y", "x", this.state.cursorPage.y.cursorPageX, this.state.cursorPage.y.cursorPageY, this.state.cursorPage.y.index);
|
|
1062
499
|
}
|
|
1063
500
|
}
|
|
1064
501
|
else {
|
|
1065
|
-
|
|
1066
|
-
this.nrrd_states.view.
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
this.nrrd_states.interaction.
|
|
1070
|
-
this.nrrd_states.interaction.cursorPageY = this.cursorPage.x.cursorPageY;
|
|
502
|
+
this.state.nrrd_states.view.currentSliceIndex = this.state.cursorPage.x.index;
|
|
503
|
+
this.state.nrrd_states.view.preSliceIndex =
|
|
504
|
+
this.state.cursorPage.x.index * this.state.nrrd_states.image.ratios.x;
|
|
505
|
+
this.state.nrrd_states.interaction.cursorPageX = this.state.cursorPage.x.cursorPageX;
|
|
506
|
+
this.state.nrrd_states.interaction.cursorPageY = this.state.cursorPage.x.cursorPageY;
|
|
1071
507
|
}
|
|
1072
508
|
}
|
|
1073
509
|
else if (axisTo === "y") {
|
|
1074
|
-
if (this.nrrd_states.interaction.isCursorSelect && !this.cursorPage.y.updated) {
|
|
1075
|
-
if (this.protectedData.axis === "z") {
|
|
1076
|
-
|
|
1077
|
-
convetObj = this.convertCursorPoint("z", "y", this.cursorPage.z.cursorPageX, this.cursorPage.z.cursorPageY, this.cursorPage.z.index);
|
|
510
|
+
if (this.state.nrrd_states.interaction.isCursorSelect && !this.state.cursorPage.y.updated) {
|
|
511
|
+
if (this.state.protectedData.axis === "z") {
|
|
512
|
+
convetObj = this.drawCore.convertCursorPoint("z", "y", this.state.cursorPage.z.cursorPageX, this.state.cursorPage.z.cursorPageY, this.state.cursorPage.z.index);
|
|
1078
513
|
}
|
|
1079
|
-
if (this.protectedData.axis === "x") {
|
|
1080
|
-
|
|
1081
|
-
convetObj = this.convertCursorPoint("x", "y", this.cursorPage.x.cursorPageX, this.cursorPage.x.cursorPageY, this.cursorPage.x.index);
|
|
514
|
+
if (this.state.protectedData.axis === "x") {
|
|
515
|
+
convetObj = this.drawCore.convertCursorPoint("x", "y", this.state.cursorPage.x.cursorPageX, this.state.cursorPage.x.cursorPageY, this.state.cursorPage.x.index);
|
|
1082
516
|
}
|
|
1083
517
|
}
|
|
1084
518
|
else {
|
|
1085
|
-
|
|
1086
|
-
this.nrrd_states.view.
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
this.nrrd_states.interaction.
|
|
1090
|
-
this.nrrd_states.interaction.cursorPageY = this.cursorPage.y.cursorPageY;
|
|
519
|
+
this.state.nrrd_states.view.currentSliceIndex = this.state.cursorPage.y.index;
|
|
520
|
+
this.state.nrrd_states.view.preSliceIndex =
|
|
521
|
+
this.state.cursorPage.y.index * this.state.nrrd_states.image.ratios.y;
|
|
522
|
+
this.state.nrrd_states.interaction.cursorPageX = this.state.cursorPage.y.cursorPageX;
|
|
523
|
+
this.state.nrrd_states.interaction.cursorPageY = this.state.cursorPage.y.cursorPageY;
|
|
1091
524
|
}
|
|
1092
525
|
}
|
|
1093
526
|
if (convetObj) {
|
|
1094
|
-
|
|
1095
|
-
this.nrrd_states.view.
|
|
1096
|
-
this.nrrd_states.
|
|
1097
|
-
this.nrrd_states.interaction.
|
|
1098
|
-
this.nrrd_states.interaction.cursorPageY = convetObj.convertCursorNumY;
|
|
527
|
+
this.state.nrrd_states.view.currentSliceIndex = convetObj.currentNewSliceIndex;
|
|
528
|
+
this.state.nrrd_states.view.preSliceIndex = convetObj.preSliceIndex;
|
|
529
|
+
this.state.nrrd_states.interaction.cursorPageX = convetObj.convertCursorNumX;
|
|
530
|
+
this.state.nrrd_states.interaction.cursorPageY = convetObj.convertCursorNumY;
|
|
1099
531
|
convetObj = undefined;
|
|
1100
532
|
switch (axisTo) {
|
|
1101
533
|
case "x":
|
|
1102
|
-
this.cursorPage.x.updated = true;
|
|
534
|
+
this.state.cursorPage.x.updated = true;
|
|
1103
535
|
break;
|
|
1104
536
|
case "y":
|
|
1105
|
-
this.cursorPage.y.updated = true;
|
|
537
|
+
this.state.cursorPage.y.updated = true;
|
|
1106
538
|
break;
|
|
1107
539
|
case "z":
|
|
1108
|
-
this.cursorPage.z.updated = true;
|
|
540
|
+
this.state.cursorPage.z.updated = true;
|
|
1109
541
|
break;
|
|
1110
542
|
}
|
|
1111
543
|
}
|
|
1112
|
-
if (this.cursorPage.x.updated &&
|
|
1113
|
-
this.cursorPage.y.updated &&
|
|
1114
|
-
this.cursorPage.z.updated) {
|
|
1115
|
-
|
|
1116
|
-
this.nrrd_states.interaction.isCursorSelect = false;
|
|
544
|
+
if (this.state.cursorPage.x.updated &&
|
|
545
|
+
this.state.cursorPage.y.updated &&
|
|
546
|
+
this.state.cursorPage.z.updated) {
|
|
547
|
+
this.state.nrrd_states.interaction.isCursorSelect = false;
|
|
1117
548
|
}
|
|
1118
549
|
}
|
|
1119
|
-
this.protectedData.axis = axisTo;
|
|
1120
|
-
this.resetDisplaySlicesStatus();
|
|
550
|
+
this.state.protectedData.axis = axisTo;
|
|
551
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
1121
552
|
}
|
|
1122
553
|
addSkip(index) {
|
|
1123
|
-
this.protectedData.skipSlicesDic[index] =
|
|
1124
|
-
this.protectedData.backUpDisplaySlices[index];
|
|
1125
|
-
if (index >= this.protectedData.displaySlices.length) {
|
|
1126
|
-
this.nrrd_states.view.contrastNum = this.protectedData.displaySlices.length;
|
|
554
|
+
this.state.protectedData.skipSlicesDic[index] =
|
|
555
|
+
this.state.protectedData.backUpDisplaySlices[index];
|
|
556
|
+
if (index >= this.state.protectedData.displaySlices.length) {
|
|
557
|
+
this.state.nrrd_states.view.contrastNum = this.state.protectedData.displaySlices.length;
|
|
1127
558
|
}
|
|
1128
559
|
else {
|
|
1129
|
-
this.nrrd_states.view.contrastNum = index;
|
|
560
|
+
this.state.nrrd_states.view.contrastNum = index;
|
|
1130
561
|
}
|
|
1131
|
-
this.resetDisplaySlicesStatus();
|
|
562
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
1132
563
|
}
|
|
1133
564
|
removeSkip(index) {
|
|
1134
|
-
this.protectedData.skipSlicesDic[index] = undefined;
|
|
1135
|
-
this.nrrd_states.view.contrastNum = 0;
|
|
1136
|
-
this.resetDisplaySlicesStatus();
|
|
1137
|
-
}
|
|
1138
|
-
/**
|
|
1139
|
-
* Reset the entire NrrdTools instance comprehensively.
|
|
1140
|
-
* This clears ALL data across ALL layers globally, resets the Canvas visuals,
|
|
1141
|
-
* undo/redo history, volume models, index parameters, and sphere overlays.
|
|
1142
|
-
* Primarily used when switching cases/datasets or when a completely fresh state is needed.
|
|
1143
|
-
* It is heavier than `clearActiveLayer` or `clearActiveSlice`.
|
|
1144
|
-
*/
|
|
1145
|
-
reset() {
|
|
1146
|
-
// To effectively reduce the js memory garbage
|
|
1147
|
-
this.protectedData.allSlicesArray.length = 0;
|
|
1148
|
-
this.protectedData.displaySlices.length = 0;
|
|
1149
|
-
// Phase 6: Clear all undo/redo stacks
|
|
1150
|
-
this.undoManager.clearAll();
|
|
1151
|
-
// Phase 3: Reset MaskVolume storage to 1×1×1 placeholders
|
|
1152
|
-
this.protectedData.maskData.volumes = this.nrrd_states.image.layers.reduce((acc, id) => {
|
|
1153
|
-
acc[id] = new MaskVolume(1, 1, 1, 1);
|
|
1154
|
-
return acc;
|
|
1155
|
-
}, {});
|
|
1156
|
-
// Clear dedicated SphereMaskVolume
|
|
1157
|
-
this.nrrd_states.sphere.sphereMaskVolume = null;
|
|
1158
|
-
// Invalidate reusable slice buffer
|
|
1159
|
-
this.invalidateSliceBuffer();
|
|
1160
|
-
this.clearDictionary(this.protectedData.skipSlicesDic);
|
|
1161
|
-
// this.nrrd_states.view.previousPanelL = this.nrrd_states.view.previousPanelT = -99999;
|
|
1162
|
-
this.protectedData.canvases.displayCanvas.style.left =
|
|
1163
|
-
this.protectedData.canvases.drawingCanvas.style.left = "";
|
|
1164
|
-
this.protectedData.canvases.displayCanvas.style.top =
|
|
1165
|
-
this.protectedData.canvases.drawingCanvas.style.top = "";
|
|
1166
|
-
this.protectedData.backUpDisplaySlices.length = 0;
|
|
1167
|
-
this.protectedData.mainPreSlices = undefined;
|
|
1168
|
-
this.protectedData.currentShowingSlice = undefined;
|
|
1169
|
-
this.initState = true;
|
|
1170
|
-
this.protectedData.axis = "z";
|
|
1171
|
-
this.nrrd_states.view.sizeFactor = this.baseCanvasesSize;
|
|
1172
|
-
this.gui_states.viewConfig.mainAreaSize = this.baseCanvasesSize;
|
|
1173
|
-
this.resetLayerCanvas();
|
|
1174
|
-
this.protectedData.canvases.drawingCanvas.width =
|
|
1175
|
-
this.protectedData.canvases.drawingCanvas.width;
|
|
1176
|
-
this.protectedData.canvases.displayCanvas.width =
|
|
1177
|
-
this.protectedData.canvases.displayCanvas.width;
|
|
1178
|
-
this.nrrd_states.sphere.tumourSphereOrigin = null;
|
|
1179
|
-
this.nrrd_states.sphere.ribSphereOrigin = null;
|
|
1180
|
-
this.nrrd_states.sphere.skinSphereOrigin = null;
|
|
1181
|
-
this.nrrd_states.sphere.nippleSphereOrigin = null;
|
|
565
|
+
this.state.protectedData.skipSlicesDic[index] = undefined;
|
|
566
|
+
this.state.nrrd_states.view.contrastNum = 0;
|
|
567
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
1182
568
|
}
|
|
1183
569
|
setSliceMoving(step) {
|
|
1184
|
-
if (this.protectedData.mainPreSlices) {
|
|
1185
|
-
// Accumulate steps so no keydown events are lost
|
|
570
|
+
if (this.state.protectedData.mainPreSlices) {
|
|
1186
571
|
this._pendingSliceStep += step;
|
|
1187
|
-
// RAF throttle: render at most once per frame, but apply ALL accumulated steps
|
|
1188
572
|
if (this._sliceRAFId !== null)
|
|
1189
573
|
return;
|
|
1190
574
|
this._sliceRAFId = requestAnimationFrame(() => {
|
|
1191
575
|
this._sliceRAFId = null;
|
|
1192
576
|
const totalStep = this._pendingSliceStep;
|
|
1193
577
|
this._pendingSliceStep = 0;
|
|
1194
|
-
this.protectedData.isDrawing = true;
|
|
578
|
+
this.state.protectedData.isDrawing = true;
|
|
1195
579
|
this.setSyncsliceNum();
|
|
1196
580
|
this.dragOperator.updateIndex(totalStep);
|
|
1197
581
|
this.setIsDrawFalse(1000);
|
|
@@ -1199,493 +583,237 @@ export class NrrdTools extends DrawToolCore {
|
|
|
1199
583
|
}
|
|
1200
584
|
}
|
|
1201
585
|
setMainAreaSize(factor) {
|
|
1202
|
-
this.nrrd_states.view.sizeFactor = factor;
|
|
1203
|
-
if (this.nrrd_states.view.sizeFactor >= 8) {
|
|
1204
|
-
this.nrrd_states.view.sizeFactor = 8;
|
|
586
|
+
this.state.nrrd_states.view.sizeFactor = factor;
|
|
587
|
+
if (this.state.nrrd_states.view.sizeFactor >= 8) {
|
|
588
|
+
this.state.nrrd_states.view.sizeFactor = 8;
|
|
1205
589
|
}
|
|
1206
|
-
else if (this.nrrd_states.view.sizeFactor <= 1) {
|
|
1207
|
-
this.nrrd_states.view.sizeFactor = 1;
|
|
590
|
+
else if (this.state.nrrd_states.view.sizeFactor <= 1) {
|
|
591
|
+
this.state.nrrd_states.view.sizeFactor = 1;
|
|
1208
592
|
}
|
|
1209
|
-
this.resizePaintArea(this.nrrd_states.view.sizeFactor);
|
|
593
|
+
this.resizePaintArea(this.state.nrrd_states.view.sizeFactor);
|
|
1210
594
|
this.resetPaintAreaUIPosition();
|
|
1211
|
-
|
|
595
|
+
}
|
|
596
|
+
switchAllSlicesArrayData(allSlices) {
|
|
597
|
+
this.state.protectedData.allSlicesArray.length = 0;
|
|
598
|
+
this.state.protectedData.allSlicesArray = [...allSlices];
|
|
599
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
600
|
+
}
|
|
601
|
+
appendLoadingbar(loadingbar) {
|
|
602
|
+
this.state.protectedData.mainAreaContainer.appendChild(loadingbar);
|
|
603
|
+
}
|
|
604
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
605
|
+
// 6. Public API — Data Getters
|
|
606
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
607
|
+
getCurrentImageDimension() {
|
|
608
|
+
return this.state.nrrd_states.image.dimensions;
|
|
609
|
+
}
|
|
610
|
+
getVoxelSpacing() {
|
|
611
|
+
return this.state.nrrd_states.image.voxelSpacing;
|
|
612
|
+
}
|
|
613
|
+
getSpaceOrigin() {
|
|
614
|
+
return this.state.nrrd_states.image.spaceOrigin;
|
|
615
|
+
}
|
|
616
|
+
getMaskData() {
|
|
617
|
+
return this.state.protectedData.maskData;
|
|
1212
618
|
}
|
|
1213
619
|
getContainer() {
|
|
1214
|
-
return this.protectedData.mainAreaContainer;
|
|
620
|
+
return this.state.protectedData.mainAreaContainer;
|
|
1215
621
|
}
|
|
1216
622
|
getDrawingCanvas() {
|
|
1217
|
-
return this.protectedData.canvases.drawingCanvas;
|
|
623
|
+
return this.state.protectedData.canvases.drawingCanvas;
|
|
1218
624
|
}
|
|
1219
625
|
getNrrdToolsSettings() {
|
|
1220
|
-
return this.nrrd_states;
|
|
626
|
+
return this.state.nrrd_states;
|
|
1221
627
|
}
|
|
1222
628
|
getMaxSliceNum() {
|
|
1223
|
-
if (this.nrrd_states.view.showContrast) {
|
|
629
|
+
if (this.state.nrrd_states.view.showContrast) {
|
|
1224
630
|
return [
|
|
1225
|
-
this.nrrd_states.view.maxIndex,
|
|
1226
|
-
this.nrrd_states.view.maxIndex * this.protectedData.displaySlices.length,
|
|
631
|
+
this.state.nrrd_states.view.maxIndex,
|
|
632
|
+
this.state.nrrd_states.view.maxIndex * this.state.protectedData.displaySlices.length,
|
|
1227
633
|
];
|
|
1228
634
|
}
|
|
1229
635
|
else {
|
|
1230
|
-
return [this.nrrd_states.view.maxIndex];
|
|
636
|
+
return [this.state.nrrd_states.view.maxIndex];
|
|
1231
637
|
}
|
|
1232
638
|
}
|
|
1233
639
|
getCurrentSlicesNumAndContrastNum() {
|
|
1234
640
|
return {
|
|
1235
|
-
currentSliceIndex: this.nrrd_states.view.currentSliceIndex,
|
|
1236
|
-
contrastIndex: this.nrrd_states.view.contrastNum,
|
|
641
|
+
currentSliceIndex: this.state.nrrd_states.view.currentSliceIndex,
|
|
642
|
+
contrastIndex: this.state.nrrd_states.view.contrastNum,
|
|
1237
643
|
};
|
|
1238
644
|
}
|
|
1239
645
|
getCurrentSliceIndex() {
|
|
1240
|
-
return Math.ceil(this.protectedData.mainPreSlices.index / this.nrrd_states.image.RSARatio);
|
|
646
|
+
return Math.ceil(this.state.protectedData.mainPreSlices.index / this.state.nrrd_states.image.RSARatio);
|
|
1241
647
|
}
|
|
1242
648
|
getIsShowContrastState() {
|
|
1243
|
-
return this.nrrd_states.view.showContrast;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
this.
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
this.
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
setMainPreSlice() {
|
|
1319
|
-
this.protectedData.mainPreSlices = this.protectedData.displaySlices[0];
|
|
1320
|
-
if (this.protectedData.mainPreSlices) {
|
|
1321
|
-
this.nrrd_states.image.RSARatio = this.protectedData.mainPreSlices.RSARatio;
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
setOriginCanvasAndPre() {
|
|
1325
|
-
if (this.protectedData.mainPreSlices) {
|
|
1326
|
-
if (this.nrrd_states.view.preSliceIndex > this.nrrd_states.view.maxIndex)
|
|
1327
|
-
this.nrrd_states.view.preSliceIndex = this.nrrd_states.view.maxIndex;
|
|
1328
|
-
if (this.initState) {
|
|
1329
|
-
this.nrrd_states.view.preSliceIndex =
|
|
1330
|
-
this.protectedData.mainPreSlices.initIndex *
|
|
1331
|
-
this.nrrd_states.image.RSARatio;
|
|
1332
|
-
this.nrrd_states.view.currentSliceIndex =
|
|
1333
|
-
this.protectedData.mainPreSlices.initIndex;
|
|
1334
|
-
}
|
|
1335
|
-
else {
|
|
1336
|
-
// !need to change
|
|
1337
|
-
// todo
|
|
1338
|
-
this.protectedData.mainPreSlices.index = this.nrrd_states.view.preSliceIndex;
|
|
1339
|
-
}
|
|
1340
|
-
this.protectedData.canvases.originCanvas =
|
|
1341
|
-
this.protectedData.mainPreSlices.canvas;
|
|
1342
|
-
this.updateOriginAndChangedWH();
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
afterLoadSlice() {
|
|
1346
|
-
this.setMainPreSlice();
|
|
1347
|
-
this.setOriginCanvasAndPre();
|
|
1348
|
-
this.protectedData.currentShowingSlice = this.protectedData.mainPreSlices;
|
|
1349
|
-
this.nrrd_states.view.preSliceIndex =
|
|
1350
|
-
this.protectedData.mainPreSlices.initIndex * this.nrrd_states.image.RSARatio;
|
|
1351
|
-
this.nrrd_states.view.currentSliceIndex = this.protectedData.mainPreSlices.initIndex;
|
|
1352
|
-
// Phase 6: Reset undo/redo stacks on new dataset load
|
|
1353
|
-
this.undoManager.clearAll();
|
|
1354
|
-
// compute max index
|
|
1355
|
-
this.updateMaxIndex();
|
|
1356
|
-
this.dragOperator.updateShowNumDiv(this.nrrd_states.view.contrastNum);
|
|
1357
|
-
this.syncGuiParameterSettings();
|
|
1358
|
-
this.initState = false;
|
|
1359
|
-
}
|
|
1360
|
-
updateMaxIndex() {
|
|
1361
|
-
if (this.protectedData.mainPreSlices) {
|
|
1362
|
-
this.nrrd_states.view.maxIndex = this.protectedData.mainPreSlices.MaxIndex;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
/**
|
|
1366
|
-
* Update the original canvas size, allow set to threejs load one (pixel distance not the mm).
|
|
1367
|
-
* Then update the changedWidth and changedHeight based on the sizeFactor.
|
|
1368
|
-
*/
|
|
1369
|
-
updateOriginAndChangedWH() {
|
|
1370
|
-
this.nrrd_states.image.originWidth =
|
|
1371
|
-
this.protectedData.canvases.originCanvas.width;
|
|
1372
|
-
this.nrrd_states.image.originHeight =
|
|
1373
|
-
this.protectedData.canvases.originCanvas.height;
|
|
1374
|
-
// Let resizePaintArea be the sole setter of changedWidth/changedHeight.
|
|
1375
|
-
// Setting them here would defeat the sizeChanged detection in resizePaintArea,
|
|
1376
|
-
// causing canvas elements to keep stale dimensions after axis switches.
|
|
1377
|
-
this.resizePaintArea(this.nrrd_states.view.sizeFactor);
|
|
1378
|
-
this.resetPaintAreaUIPosition();
|
|
1379
|
-
}
|
|
1380
|
-
/**
|
|
1381
|
-
* Keep all contrast slice index to same.
|
|
1382
|
-
* Synchronize the slice indexes of all the contrasts so that they are consistent with the main slice's index.
|
|
1383
|
-
*/
|
|
1384
|
-
setSyncsliceNum() {
|
|
1385
|
-
this.protectedData.displaySlices.forEach((slice, index) => {
|
|
1386
|
-
if (index !== 0) {
|
|
1387
|
-
slice.index = this.protectedData.mainPreSlices.index;
|
|
1388
|
-
}
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
appendLoadingbar(loadingbar) {
|
|
1392
|
-
this.protectedData.mainAreaContainer.appendChild(loadingbar);
|
|
1393
|
-
}
|
|
1394
|
-
/**
|
|
1395
|
-
* Clear all annotations on the currently active layer across its entire 3D volume.
|
|
1396
|
-
* This resets all voxels globally for the active layer's `MaskVolume` (depth, width, height)
|
|
1397
|
-
* and triggers the `onClearLayerVolume` event to sync the wiped volume to the backend.
|
|
1398
|
-
* It also clears the undo/redo stack for the active layer ONLY.
|
|
1399
|
-
* Other background layers are not impacted by this operation.
|
|
1400
|
-
*/
|
|
1401
|
-
clearActiveLayer() {
|
|
1402
|
-
// Phase 3 Task 3.1: Only clear the active layer's MaskVolume
|
|
1403
|
-
if (this.nrrd_states.image.dimensions.length === 3) {
|
|
1404
|
-
const [w, h, d] = this.nrrd_states.image.dimensions;
|
|
1405
|
-
const activeLayer = this.gui_states.layerChannel.layer;
|
|
1406
|
-
// Re-init only the active layer's MaskVolume
|
|
1407
|
-
this.protectedData.maskData.volumes[activeLayer] = new MaskVolume(w, h, d, 1);
|
|
1408
|
-
// Phase 6 Task 6.6: Clear undo/redo stack for this layer (volume too large to snapshot)
|
|
1409
|
-
this.undoManager.clearLayer(activeLayer);
|
|
1410
|
-
// Phase 3 Task 3.2: Notify external that this layer's volume was cleared
|
|
1411
|
-
this.annotationCallbacks.onLayerVolumeCleared(activeLayer);
|
|
1412
|
-
}
|
|
1413
|
-
// Invalidate reusable slice buffer
|
|
1414
|
-
this.invalidateSliceBuffer();
|
|
1415
|
-
// Reload all layers to canvas (restores other layers' visuals)
|
|
1416
|
-
this.reloadMasksFromVolume();
|
|
1417
|
-
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Reset the draw and display canvases layout after mouse pan.
|
|
1420
|
-
* If no params in, then center the draw and display canvases.
|
|
1421
|
-
* @param l number, Offset to the left
|
|
1422
|
-
* @param t number, Offset to the top
|
|
1423
|
-
*/
|
|
1424
|
-
resetPaintAreaUIPosition(l, t) {
|
|
1425
|
-
if (l && t) {
|
|
1426
|
-
this.protectedData.canvases.displayCanvas.style.left =
|
|
1427
|
-
this.protectedData.canvases.drawingCanvas.style.left = l + "px";
|
|
1428
|
-
this.protectedData.canvases.displayCanvas.style.top =
|
|
1429
|
-
this.protectedData.canvases.drawingCanvas.style.top = t + "px";
|
|
1430
|
-
}
|
|
1431
|
-
else {
|
|
1432
|
-
this.protectedData.canvases.displayCanvas.style.left =
|
|
1433
|
-
this.protectedData.canvases.drawingCanvas.style.left = "";
|
|
1434
|
-
this.protectedData.canvases.displayCanvas.style.top =
|
|
1435
|
-
this.protectedData.canvases.drawingCanvas.style.top = "";
|
|
1436
|
-
this.protectedData.mainAreaContainer.style.justifyContent = "center";
|
|
1437
|
-
this.protectedData.mainAreaContainer.style.alignItems = "center";
|
|
649
|
+
return this.state.nrrd_states.view.showContrast;
|
|
650
|
+
}
|
|
651
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
652
|
+
// 7. Delegated — LayerChannelManager
|
|
653
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
654
|
+
setActiveLayer(layerId) { this.layerChannelManager.setActiveLayer(layerId); }
|
|
655
|
+
getActiveLayer() { return this.layerChannelManager.getActiveLayer(); }
|
|
656
|
+
setActiveChannel(channel) { this.layerChannelManager.setActiveChannel(channel); }
|
|
657
|
+
getActiveChannel() { return this.layerChannelManager.getActiveChannel(); }
|
|
658
|
+
setActiveSphereType(type) { this.layerChannelManager.setActiveSphereType(type); }
|
|
659
|
+
getActiveSphereType() { return this.layerChannelManager.getActiveSphereType(); }
|
|
660
|
+
setLayerVisible(layerId, visible) { this.layerChannelManager.setLayerVisible(layerId, visible); }
|
|
661
|
+
isLayerVisible(layerId) { return this.layerChannelManager.isLayerVisible(layerId); }
|
|
662
|
+
setChannelVisible(layerId, channel, visible) { this.layerChannelManager.setChannelVisible(layerId, channel, visible); }
|
|
663
|
+
isChannelVisible(layerId, channel) { return this.layerChannelManager.isChannelVisible(layerId, channel); }
|
|
664
|
+
getLayerVisibility() { return this.layerChannelManager.getLayerVisibility(); }
|
|
665
|
+
getChannelVisibility() { return this.layerChannelManager.getChannelVisibility(); }
|
|
666
|
+
hasLayerData(layerId) { return this.layerChannelManager.hasLayerData(layerId); }
|
|
667
|
+
setChannelColor(layerId, channel, color) { this.layerChannelManager.setChannelColor(layerId, channel, color); }
|
|
668
|
+
getChannelColor(layerId, channel) { return this.layerChannelManager.getChannelColor(layerId, channel); }
|
|
669
|
+
getChannelHexColor(layerId, channel) { return this.layerChannelManager.getChannelHexColor(layerId, channel); }
|
|
670
|
+
getChannelCssColor(layerId, channel) { return this.layerChannelManager.getChannelCssColor(layerId, channel); }
|
|
671
|
+
setChannelColors(layerId, colorMap) { this.layerChannelManager.setChannelColors(layerId, colorMap); }
|
|
672
|
+
setAllLayersChannelColor(channel, color) { this.layerChannelManager.setAllLayersChannelColor(channel, color); }
|
|
673
|
+
resetChannelColors(layerId, channel) { this.layerChannelManager.resetChannelColors(layerId, channel); }
|
|
674
|
+
// Per-layer opacity
|
|
675
|
+
setLayerOpacity(layerId, opacity) { this.layerChannelManager.setLayerOpacity(layerId, opacity); }
|
|
676
|
+
getLayerOpacity(layerId) { return this.layerChannelManager.getLayerOpacity(layerId); }
|
|
677
|
+
getLayerOpacityMap() { return this.layerChannelManager.getLayerOpacityMap(); }
|
|
678
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
679
|
+
// 8. Delegated — SliceRenderPipeline
|
|
680
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
681
|
+
updateOriginAndChangedWH() { this.sliceRenderPipeline.updateOriginAndChangedWH(); }
|
|
682
|
+
setSyncsliceNum() { this.sliceRenderPipeline.setSyncsliceNum(); }
|
|
683
|
+
resetPaintAreaUIPosition(l, t) { this.sliceRenderPipeline.resetPaintAreaUIPosition(l, t); }
|
|
684
|
+
resetLayerCanvas() { this.sliceRenderPipeline.resetLayerCanvas(); }
|
|
685
|
+
redrawMianPreOnDisplayCanvas() { this.sliceRenderPipeline.redrawMianPreOnDisplayCanvas(); }
|
|
686
|
+
resizePaintArea(factor) { this.sliceRenderPipeline.resizePaintArea(factor); }
|
|
687
|
+
flipDisplayImageByAxis() { this.sliceRenderPipeline.flipDisplayImageByAxis(); }
|
|
688
|
+
setEmptyCanvasSize(axis) { this.sliceRenderPipeline.setEmptyCanvasSize(axis); }
|
|
689
|
+
redrawDisplayCanvas() { this.sliceRenderPipeline.redrawDisplayCanvas(); }
|
|
690
|
+
reloadMasksFromVolume() { this.sliceRenderPipeline.reloadMasksFromVolume(); }
|
|
691
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
692
|
+
// 9. Delegated — DataLoader
|
|
693
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
694
|
+
setAllSlices(allSlices) { this.dataLoader.setAllSlices(allSlices); }
|
|
695
|
+
setMasksData(masksData, loadingBar) { this.dataLoader.setMasksData(masksData, loadingBar); }
|
|
696
|
+
setMasksFromNIfTI(layerVoxels, loadingBar) { this.dataLoader.setMasksFromNIfTI(layerVoxels, loadingBar); }
|
|
697
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
698
|
+
// 10. Sphere Orchestration
|
|
699
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
700
|
+
setCalculateDistanceSphere(x, y, sliceIndex, cal_position) {
|
|
701
|
+
this.state.nrrd_states.sphere.sphereRadius = 5;
|
|
702
|
+
const steps = sliceIndex - this.state.nrrd_states.view.currentSliceIndex;
|
|
703
|
+
this.setSliceMoving(steps * this.state.protectedData.displaySlices.length);
|
|
704
|
+
const mouseX = x * this.state.nrrd_states.view.sizeFactor;
|
|
705
|
+
const mouseY = y * this.state.nrrd_states.view.sizeFactor;
|
|
706
|
+
this.state.nrrd_states.sphere.sphereOrigin[this.state.protectedData.axis] = [
|
|
707
|
+
mouseX, mouseY, sliceIndex,
|
|
708
|
+
];
|
|
709
|
+
this.drawCore.crosshairTool.setUpSphereOrigins(mouseX, mouseY, sliceIndex);
|
|
710
|
+
const originCopy = JSON.parse(JSON.stringify(this.state.nrrd_states.sphere.sphereOrigin));
|
|
711
|
+
switch (cal_position) {
|
|
712
|
+
case "tumour":
|
|
713
|
+
this.state.nrrd_states.sphere.tumourSphereOrigin = originCopy;
|
|
714
|
+
break;
|
|
715
|
+
case "skin":
|
|
716
|
+
this.state.nrrd_states.sphere.skinSphereOrigin = originCopy;
|
|
717
|
+
break;
|
|
718
|
+
case "nipple":
|
|
719
|
+
this.state.nrrd_states.sphere.nippleSphereOrigin = originCopy;
|
|
720
|
+
break;
|
|
721
|
+
case "ribcage":
|
|
722
|
+
this.state.nrrd_states.sphere.ribSphereOrigin = originCopy;
|
|
723
|
+
break;
|
|
1438
724
|
}
|
|
725
|
+
this.drawCore.drawCalculatorSphere(this.state.nrrd_states.sphere.sphereRadius);
|
|
726
|
+
this.drawCore.sphereTool.writeAllCalculatorSpheresToVolume();
|
|
727
|
+
this.drawCore.sphereTool.refreshSphereCanvas();
|
|
1439
728
|
}
|
|
1440
729
|
/**
|
|
1441
|
-
*
|
|
730
|
+
* Refresh sphere canvas from sphereMaskVolume for the current slice/axis.
|
|
1442
731
|
*/
|
|
1443
|
-
|
|
1444
|
-
this.
|
|
1445
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.width;
|
|
1446
|
-
for (const [, target] of this.protectedData.layerTargets) {
|
|
1447
|
-
target.canvas.width = target.canvas.width;
|
|
1448
|
-
}
|
|
732
|
+
refreshSphereOverlay() {
|
|
733
|
+
this.drawCore.refreshSphereOverlay();
|
|
1449
734
|
}
|
|
1450
735
|
/**
|
|
1451
736
|
* Enter sphere mode.
|
|
1452
|
-
*
|
|
1453
|
-
* Clears all layer canvases and the master composite canvas so that
|
|
1454
|
-
* only the sphere overlay is visible. Does NOT touch MaskVolume data.
|
|
1455
|
-
* Also disables drag mode to prevent slice dragging conflicts.
|
|
1456
|
-
*
|
|
1457
|
-
* Called when sphere mode is toggled on (keyboard shortcut or GUI).
|
|
1458
737
|
*/
|
|
1459
738
|
enterSphereMode() {
|
|
1460
739
|
var _a;
|
|
1461
|
-
// Disable left-click drag for slice navigation
|
|
1462
740
|
this.dragOperator.removeDragMode();
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
for (const layerId of this.nrrd_states.image.layers) {
|
|
1469
|
-
const target = this.protectedData.layerTargets.get(layerId);
|
|
741
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setGuiTool('sphere');
|
|
742
|
+
const w = this.state.nrrd_states.view.changedWidth;
|
|
743
|
+
const h = this.state.nrrd_states.view.changedHeight;
|
|
744
|
+
for (const layerId of this.state.nrrd_states.image.layers) {
|
|
745
|
+
const target = this.state.protectedData.layerTargets.get(layerId);
|
|
1470
746
|
if (target) {
|
|
1471
747
|
target.ctx.clearRect(0, 0, target.canvas.width, target.canvas.height);
|
|
1472
748
|
}
|
|
1473
749
|
}
|
|
1474
|
-
|
|
1475
|
-
this.protectedData.ctxes.drawingLayerMasterCtx.clearRect(0, 0, w, h);
|
|
1476
|
-
// Refresh sphere overlay from volume (shows existing sphere data on re-entry)
|
|
750
|
+
this.state.protectedData.ctxes.drawingLayerMasterCtx.clearRect(0, 0, w, h);
|
|
1477
751
|
this.refreshSphereOverlay();
|
|
1478
752
|
}
|
|
1479
753
|
/**
|
|
1480
754
|
* Exit sphere mode.
|
|
1481
|
-
*
|
|
1482
|
-
* Clears sphere overlay, restores all layer MaskVolume data onto
|
|
1483
|
-
* their canvases by temporarily lifting the sphere guard in
|
|
1484
|
-
* reloadMasksFromVolume and calling it.
|
|
1485
|
-
*
|
|
1486
|
-
* Called when sphere mode is toggled off (keyboard shortcut or GUI).
|
|
1487
755
|
*/
|
|
1488
756
|
exitSphereMode() {
|
|
1489
757
|
var _a;
|
|
1490
|
-
// Restore left-click drag for slice navigation
|
|
1491
758
|
this.dragOperator.configDragMode();
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
// Clear sphere canvas overlay
|
|
1495
|
-
this.protectedData.ctxes.drawingSphereCtx.clearRect(0, 0, this.protectedData.canvases.drawingSphereCanvas.width, this.protectedData.canvases.drawingSphereCanvas.height);
|
|
1496
|
-
// resetLayerCanvas clears the layer canvas elements
|
|
759
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setGuiTool('pencil');
|
|
760
|
+
this.state.protectedData.ctxes.drawingSphereCtx.clearRect(0, 0, this.state.protectedData.canvases.drawingSphereCanvas.width, this.state.protectedData.canvases.drawingSphereCanvas.height);
|
|
1497
761
|
this.resetLayerCanvas();
|
|
1498
|
-
// Temporarily lift sphere guard so reloadMasksFromVolume can run.
|
|
1499
|
-
// gui_states.sphere is already set to false by the caller before
|
|
1500
|
-
// calling this method, so reloadMasksFromVolume will proceed normally.
|
|
1501
762
|
this.reloadMasksFromVolume();
|
|
1502
763
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
this.
|
|
1508
|
-
this.
|
|
1509
|
-
|
|
1510
|
-
this.protectedData.
|
|
1511
|
-
|
|
1512
|
-
this.
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
/**
|
|
1519
|
-
* Resize the draw and display canvas size based on the input size factor number.
|
|
1520
|
-
* @param factor number
|
|
1521
|
-
*/
|
|
1522
|
-
resizePaintArea(factor) {
|
|
1523
|
-
const newWidth = Math.floor(this.nrrd_states.image.originWidth * factor);
|
|
1524
|
-
const newHeight = Math.floor(this.nrrd_states.image.originHeight * factor);
|
|
1525
|
-
const sizeChanged = newWidth !== this.nrrd_states.view.changedWidth ||
|
|
1526
|
-
newHeight !== this.nrrd_states.view.changedHeight;
|
|
1527
|
-
// Always clear display/drawing/origin canvases (needed for contrast updates)
|
|
1528
|
-
this.protectedData.canvases.originCanvas.width =
|
|
1529
|
-
this.protectedData.canvases.originCanvas.width;
|
|
1530
|
-
this.protectedData.canvases.displayCanvas.width =
|
|
1531
|
-
this.protectedData.canvases.displayCanvas.width;
|
|
1532
|
-
this.protectedData.canvases.drawingCanvas.width =
|
|
1533
|
-
this.protectedData.canvases.drawingCanvas.width;
|
|
1534
|
-
if (sizeChanged) {
|
|
1535
|
-
// Only clear and resize layer canvases when size actually changes.
|
|
1536
|
-
// Skipping this avoids the expensive reloadMasksFromVolume() call
|
|
1537
|
-
// during contrast toggle (where size stays the same).
|
|
1538
|
-
this.resetLayerCanvas();
|
|
1539
|
-
this.nrrd_states.view.changedWidth = newWidth;
|
|
1540
|
-
this.nrrd_states.view.changedHeight = newHeight;
|
|
1541
|
-
this.protectedData.canvases.displayCanvas.width = newWidth;
|
|
1542
|
-
this.protectedData.canvases.displayCanvas.height = newHeight;
|
|
1543
|
-
this.protectedData.canvases.drawingCanvas.width = newWidth;
|
|
1544
|
-
this.protectedData.canvases.drawingCanvas.height = newHeight;
|
|
1545
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.width = newWidth;
|
|
1546
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.height = newHeight;
|
|
1547
|
-
for (const [, target] of this.protectedData.layerTargets) {
|
|
1548
|
-
target.canvas.width = newWidth;
|
|
1549
|
-
target.canvas.height = newHeight;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
this.redrawDisplayCanvas();
|
|
1553
|
-
if (sizeChanged) {
|
|
1554
|
-
// Phase 3: Reload masks from MaskVolume only when canvas size changed
|
|
1555
|
-
this.reloadMasksFromVolume();
|
|
1556
|
-
}
|
|
1557
|
-
else {
|
|
1558
|
-
// Size unchanged (e.g. contrast toggle): layer canvases still have
|
|
1559
|
-
// valid data, just recomposite to master for the start() render loop.
|
|
1560
|
-
this.compositeAllLayers();
|
|
1561
|
-
}
|
|
1562
|
-
// Refresh sphere overlay from volume after resize/contrast change
|
|
1563
|
-
this.refreshSphereOverlay();
|
|
1564
|
-
}
|
|
1565
|
-
/**
|
|
1566
|
-
* Phase 3: Reload all mask layers from MaskVolume using buffer reuse
|
|
1567
|
-
* Replaces the old reloadMaskToLayer approach
|
|
1568
|
-
*/
|
|
1569
|
-
reloadMasksFromVolume() {
|
|
1570
|
-
// When sphere mode is active, do NOT redraw layer masks.
|
|
1571
|
-
// Layer mask data should remain hidden until the user exits sphere mode.
|
|
1572
|
-
if (this.gui_states.mode.sphere) {
|
|
1573
|
-
return;
|
|
1574
|
-
}
|
|
1575
|
-
const axis = this.protectedData.axis;
|
|
1576
|
-
let sliceIndex = this.nrrd_states.view.currentSliceIndex;
|
|
1577
|
-
// Clamp sliceIndex to valid range for current axis
|
|
1578
|
-
// (currentSliceIndex may not be updated yet when switching axes)
|
|
1579
|
-
try {
|
|
1580
|
-
const vol = this.getVolumeForLayer(this.nrrd_states.image.layers[0]);
|
|
1581
|
-
const dims = vol.getDimensions();
|
|
1582
|
-
const maxSlice = axis === "x" ? dims.width : axis === "y" ? dims.height : dims.depth;
|
|
1583
|
-
if (sliceIndex >= maxSlice)
|
|
1584
|
-
sliceIndex = maxSlice - 1;
|
|
1585
|
-
if (sliceIndex < 0)
|
|
1586
|
-
sliceIndex = 0;
|
|
1587
|
-
}
|
|
1588
|
-
catch ( /* volume not ready */_a) { /* volume not ready */ }
|
|
1589
|
-
// Get a single reusable buffer shared across all layer renders
|
|
1590
|
-
const buffer = this.getOrCreateSliceBuffer(axis);
|
|
1591
|
-
if (!buffer)
|
|
1592
|
-
return;
|
|
1593
|
-
const w = this.nrrd_states.view.changedWidth;
|
|
1594
|
-
const h = this.nrrd_states.view.changedHeight;
|
|
1595
|
-
// Clear and render each layer using the shared buffer
|
|
1596
|
-
for (const layerId of this.nrrd_states.image.layers) {
|
|
1597
|
-
const target = this.protectedData.layerTargets.get(layerId);
|
|
1598
|
-
if (!target)
|
|
1599
|
-
continue;
|
|
1600
|
-
target.ctx.clearRect(0, 0, w, h);
|
|
1601
|
-
this.renderSliceToCanvas(layerId, axis, sliceIndex, buffer, target.ctx, w, h);
|
|
1602
|
-
}
|
|
1603
|
-
// Composite all layers to master canvas
|
|
1604
|
-
this.compositeAllLayers();
|
|
1605
|
-
}
|
|
1606
|
-
// compositeAllLayers() is inherited from CommToolsData
|
|
1607
|
-
/**
|
|
1608
|
-
* flip the canvas to a correct position.
|
|
1609
|
-
* This is because the slice canvas from threejs is not in a correct 2D postion.
|
|
1610
|
-
* Thus, everytime when we redraw the display canvas, we need to flip to draw the origin canvas from threejs.
|
|
1611
|
-
* Under different axis(sagittal, Axial, Coronal), the flip orientation is different.
|
|
1612
|
-
*/
|
|
1613
|
-
flipDisplayImageByAxis() {
|
|
1614
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1615
|
-
if (this.protectedData.axis === "x") {
|
|
1616
|
-
(_a = this.protectedData.ctxes.displayCtx) === null || _a === void 0 ? void 0 : _a.scale(-1, -1);
|
|
1617
|
-
(_b = this.protectedData.ctxes.displayCtx) === null || _b === void 0 ? void 0 : _b.translate(-this.nrrd_states.view.changedWidth, -this.nrrd_states.view.changedHeight);
|
|
1618
|
-
}
|
|
1619
|
-
else if (this.protectedData.axis === "z") {
|
|
1620
|
-
(_c = this.protectedData.ctxes.displayCtx) === null || _c === void 0 ? void 0 : _c.scale(1, -1);
|
|
1621
|
-
(_d = this.protectedData.ctxes.displayCtx) === null || _d === void 0 ? void 0 : _d.translate(0, -this.nrrd_states.view.changedHeight);
|
|
1622
|
-
}
|
|
1623
|
-
else if (this.protectedData.axis === "y") {
|
|
1624
|
-
(_e = this.protectedData.ctxes.displayCtx) === null || _e === void 0 ? void 0 : _e.scale(1, -1);
|
|
1625
|
-
(_f = this.protectedData.ctxes.displayCtx) === null || _f === void 0 ? void 0 : _f.translate(0, -this.nrrd_states.view.changedHeight);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
clearDictionary(dic) {
|
|
1629
|
-
for (var key in dic) {
|
|
1630
|
-
delete dic[key];
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
/**
|
|
1634
|
-
* Set the empty canvas width and height based on the axis (pixel distance not the mm), to reduce duplicate codes.
|
|
1635
|
-
*
|
|
1636
|
-
* @param axis
|
|
1637
|
-
*/
|
|
1638
|
-
setEmptyCanvasSize(axis) {
|
|
1639
|
-
switch (!!axis ? axis : this.protectedData.axis) {
|
|
1640
|
-
case "x":
|
|
1641
|
-
this.protectedData.canvases.emptyCanvas.width =
|
|
1642
|
-
this.nrrd_states.image.nrrd_z_pixel;
|
|
1643
|
-
this.protectedData.canvases.emptyCanvas.height =
|
|
1644
|
-
this.nrrd_states.image.nrrd_y_pixel;
|
|
1645
|
-
break;
|
|
1646
|
-
case "y":
|
|
1647
|
-
this.protectedData.canvases.emptyCanvas.width =
|
|
1648
|
-
this.nrrd_states.image.nrrd_x_pixel;
|
|
1649
|
-
this.protectedData.canvases.emptyCanvas.height =
|
|
1650
|
-
this.nrrd_states.image.nrrd_z_pixel;
|
|
1651
|
-
break;
|
|
1652
|
-
case "z":
|
|
1653
|
-
this.protectedData.canvases.emptyCanvas.width =
|
|
1654
|
-
this.nrrd_states.image.nrrd_x_pixel;
|
|
1655
|
-
this.protectedData.canvases.emptyCanvas.height =
|
|
1656
|
-
this.nrrd_states.image.nrrd_y_pixel;
|
|
1657
|
-
break;
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
/******************************** redraw display canvas ***************************************/
|
|
1661
|
-
/**
|
|
1662
|
-
* Redraw current contrast image to display canvas.
|
|
1663
|
-
* It is more related to change the contrast slice image's window width or center.
|
|
1664
|
-
*/
|
|
1665
|
-
redrawDisplayCanvas() {
|
|
1666
|
-
var _a, _b, _c;
|
|
1667
|
-
this.dragOperator.updateCurrentContrastSlice();
|
|
1668
|
-
this.protectedData.canvases.displayCanvas.width =
|
|
1669
|
-
this.protectedData.canvases.displayCanvas.width;
|
|
1670
|
-
this.protectedData.canvases.displayCanvas.height =
|
|
1671
|
-
this.protectedData.canvases.displayCanvas.height;
|
|
1672
|
-
this.protectedData.canvases.originCanvas.width =
|
|
1673
|
-
this.protectedData.canvases.originCanvas.width;
|
|
1674
|
-
if (this.protectedData.currentShowingSlice) {
|
|
1675
|
-
this.protectedData.currentShowingSlice.repaint.call(this.protectedData.currentShowingSlice);
|
|
1676
|
-
(_a = this.protectedData.ctxes.displayCtx) === null || _a === void 0 ? void 0 : _a.save();
|
|
1677
|
-
this.flipDisplayImageByAxis();
|
|
1678
|
-
(_b = this.protectedData.ctxes.displayCtx) === null || _b === void 0 ? void 0 : _b.drawImage(this.protectedData.currentShowingSlice.canvas, 0, 0, this.nrrd_states.view.changedWidth, this.nrrd_states.view.changedHeight);
|
|
1679
|
-
(_c = this.protectedData.ctxes.displayCtx) === null || _c === void 0 ? void 0 : _c.restore();
|
|
1680
|
-
}
|
|
764
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
765
|
+
// 11. Clear / Reset
|
|
766
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
767
|
+
clearActiveLayer() {
|
|
768
|
+
if (this.state.nrrd_states.image.dimensions.length === 3) {
|
|
769
|
+
const [w, h, d] = this.state.nrrd_states.image.dimensions;
|
|
770
|
+
const activeLayer = this.state.gui_states.layerChannel.layer;
|
|
771
|
+
this.state.protectedData.maskData.volumes[activeLayer] = new MaskVolume(w, h, d, 1);
|
|
772
|
+
this.drawCore.undoManager.clearLayer(activeLayer);
|
|
773
|
+
this.state.annotationCallbacks.onLayerVolumeCleared(activeLayer);
|
|
774
|
+
}
|
|
775
|
+
this.drawCore.renderer.invalidateSliceBuffer();
|
|
776
|
+
this.reloadMasksFromVolume();
|
|
1681
777
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
778
|
+
reset() {
|
|
779
|
+
this.state.protectedData.allSlicesArray.length = 0;
|
|
780
|
+
this.state.protectedData.displaySlices.length = 0;
|
|
781
|
+
this.drawCore.undoManager.clearAll();
|
|
782
|
+
this.state.protectedData.maskData.volumes = this.state.nrrd_states.image.layers.reduce((acc, id) => {
|
|
783
|
+
acc[id] = new MaskVolume(1, 1, 1, 1);
|
|
784
|
+
return acc;
|
|
785
|
+
}, {});
|
|
786
|
+
this.state.nrrd_states.sphere.sphereMaskVolume = null;
|
|
787
|
+
this.drawCore.renderer.invalidateSliceBuffer();
|
|
788
|
+
this.clearDictionary(this.state.protectedData.skipSlicesDic);
|
|
789
|
+
this.state.protectedData.canvases.displayCanvas.style.left =
|
|
790
|
+
this.state.protectedData.canvases.drawingCanvas.style.left = "";
|
|
791
|
+
this.state.protectedData.canvases.displayCanvas.style.top =
|
|
792
|
+
this.state.protectedData.canvases.drawingCanvas.style.top = "";
|
|
793
|
+
this.state.protectedData.backUpDisplaySlices.length = 0;
|
|
794
|
+
this.state.protectedData.mainPreSlices = undefined;
|
|
795
|
+
this.state.protectedData.currentShowingSlice = undefined;
|
|
796
|
+
this.sliceRenderPipeline.resetInitState();
|
|
797
|
+
this.state.protectedData.axis = "z";
|
|
798
|
+
this.state.nrrd_states.view.sizeFactor = this.state.baseCanvasesSize;
|
|
799
|
+
this.state.gui_states.viewConfig.mainAreaSize = this.state.baseCanvasesSize;
|
|
800
|
+
this.resetLayerCanvas();
|
|
801
|
+
this.state.protectedData.canvases.drawingCanvas.width =
|
|
802
|
+
this.state.protectedData.canvases.drawingCanvas.width;
|
|
803
|
+
this.state.protectedData.canvases.displayCanvas.width =
|
|
804
|
+
this.state.protectedData.canvases.displayCanvas.width;
|
|
805
|
+
this.state.nrrd_states.sphere.tumourSphereOrigin = null;
|
|
806
|
+
this.state.nrrd_states.sphere.ribSphereOrigin = null;
|
|
807
|
+
this.state.nrrd_states.sphere.skinSphereOrigin = null;
|
|
808
|
+
this.state.nrrd_states.sphere.nippleSphereOrigin = null;
|
|
809
|
+
}
|
|
810
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
811
|
+
// 12. Internal — Input Events
|
|
812
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1685
813
|
configMouseSliceWheel() {
|
|
1686
814
|
const handleMouseZoomSliceWheelMove = (e) => {
|
|
1687
815
|
var _a;
|
|
1688
|
-
if ((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
|
|
816
|
+
if ((_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
|
|
1689
817
|
return;
|
|
1690
818
|
}
|
|
1691
819
|
e.preventDefault();
|
|
@@ -1698,23 +826,53 @@ export class NrrdTools extends DrawToolCore {
|
|
|
1698
826
|
};
|
|
1699
827
|
return handleMouseZoomSliceWheelMove;
|
|
1700
828
|
}
|
|
1701
|
-
/**
|
|
1702
|
-
* Update mouse wheel event.
|
|
1703
|
-
*/
|
|
1704
829
|
updateMouseWheelEvent() {
|
|
1705
|
-
this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
1706
|
-
switch (this.
|
|
830
|
+
this.state.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawCore.drawingPrameters.handleMouseZoomSliceWheel);
|
|
831
|
+
switch (this.state.keyboardSettings.mouseWheel) {
|
|
1707
832
|
case "Scroll:Zoom":
|
|
1708
|
-
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseZoomWheel();
|
|
833
|
+
this.drawCore.drawingPrameters.handleMouseZoomSliceWheel = this.drawCore.configMouseZoomWheel();
|
|
1709
834
|
break;
|
|
1710
835
|
case "Scroll:Slice":
|
|
1711
|
-
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseSliceWheel();
|
|
836
|
+
this.drawCore.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseSliceWheel();
|
|
1712
837
|
break;
|
|
1713
838
|
default:
|
|
1714
|
-
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseZoomWheel();
|
|
839
|
+
this.drawCore.drawingPrameters.handleMouseZoomSliceWheel = this.drawCore.configMouseZoomWheel();
|
|
1715
840
|
break;
|
|
1716
841
|
}
|
|
1717
|
-
this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
842
|
+
this.state.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawCore.drawingPrameters.handleMouseZoomSliceWheel);
|
|
843
|
+
}
|
|
844
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
845
|
+
// 13. Internal — Utility
|
|
846
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
847
|
+
setIsDrawFalse(target) {
|
|
848
|
+
this.preTimer = setTimeout(() => {
|
|
849
|
+
this.state.protectedData.isDrawing = false;
|
|
850
|
+
if (this.preTimer) {
|
|
851
|
+
window.clearTimeout(this.preTimer);
|
|
852
|
+
this.preTimer = undefined;
|
|
853
|
+
}
|
|
854
|
+
}, target);
|
|
855
|
+
}
|
|
856
|
+
setShowInMainArea() {
|
|
857
|
+
this.state.nrrd_states.view.showContrast = true;
|
|
858
|
+
}
|
|
859
|
+
clearDictionary(dic) {
|
|
860
|
+
for (var key in dic) {
|
|
861
|
+
delete dic[key];
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Get the DrawToolCore's start() render loop callback.
|
|
866
|
+
* This is called by DragSliceTool's requestAnimationFrame loop.
|
|
867
|
+
*/
|
|
868
|
+
get start() {
|
|
869
|
+
return this.drawCore.start;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Expose drawCalculatorSphereOnEachViews for external use.
|
|
873
|
+
*/
|
|
874
|
+
drawCalculatorSphereOnEachViews(axis) {
|
|
875
|
+
this.drawCore.drawCalculatorSphereOnEachViews(axis);
|
|
1718
876
|
}
|
|
1719
877
|
}
|
|
1720
878
|
//# sourceMappingURL=NrrdTools.js.map
|