copper3d 3.0.1 → 3.1.0
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 +197 -875
- package/dist/Utils/segmentation/CanvasState.d.ts +31 -0
- package/dist/Utils/segmentation/CanvasState.js +167 -0
- package/dist/Utils/segmentation/CanvasState.js.map +1 -0
- package/dist/Utils/segmentation/DragOperator.d.ts +10 -2
- package/dist/Utils/segmentation/DragOperator.js +16 -8
- package/dist/Utils/segmentation/DragOperator.js.map +1 -1
- package/dist/Utils/segmentation/DrawToolCore.d.ts +64 -35
- package/dist/Utils/segmentation/DrawToolCore.js +332 -584
- package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
- package/dist/Utils/segmentation/NrrdTools.d.ts +117 -353
- package/dist/Utils/segmentation/NrrdTools.js +622 -1081
- package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
- package/dist/Utils/segmentation/RenderingUtils.d.ts +73 -0
- package/dist/Utils/segmentation/RenderingUtils.js +180 -0
- package/dist/Utils/segmentation/RenderingUtils.js.map +1 -0
- package/dist/Utils/segmentation/core/MaskVolume.js +23 -2
- package/dist/Utils/segmentation/core/MaskVolume.js.map +1 -1
- package/dist/Utils/segmentation/core/UndoManager.d.ts +1 -1
- package/dist/Utils/segmentation/core/UndoManager.js +1 -1
- package/dist/Utils/segmentation/core/index.d.ts +1 -3
- package/dist/Utils/segmentation/core/index.js +0 -1
- package/dist/Utils/segmentation/core/index.js.map +1 -1
- package/dist/Utils/segmentation/core/types.d.ts +384 -0
- package/dist/Utils/segmentation/core/types.js +25 -24
- package/dist/Utils/segmentation/core/types.js.map +1 -1
- package/dist/Utils/segmentation/coreTools/GuiState.d.ts +22 -0
- package/dist/Utils/segmentation/coreTools/GuiState.js +48 -0
- package/dist/Utils/segmentation/coreTools/GuiState.js.map +1 -0
- package/dist/Utils/segmentation/coreTools/NrrdState.d.ts +25 -0
- package/dist/Utils/segmentation/coreTools/NrrdState.js +85 -0
- package/dist/Utils/segmentation/coreTools/NrrdState.js.map +1 -0
- package/dist/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
- package/dist/Utils/segmentation/coreTools/divControlTools.js +1 -1
- package/dist/Utils/segmentation/coreTools/divControlTools.js.map +1 -1
- package/dist/Utils/segmentation/coreTools/gui.d.ts +12 -12
- package/dist/Utils/segmentation/coreTools/gui.js +81 -107
- package/dist/Utils/segmentation/coreTools/gui.js.map +1 -1
- package/dist/Utils/segmentation/eventRouter/EventRouter.d.ts +2 -1
- package/dist/Utils/segmentation/eventRouter/EventRouter.js +15 -10
- package/dist/Utils/segmentation/eventRouter/EventRouter.js.map +1 -1
- package/dist/Utils/segmentation/eventRouter/types.d.ts +3 -3
- package/dist/Utils/segmentation/tools/BaseTool.d.ts +8 -5
- package/dist/Utils/segmentation/tools/BaseTool.js +1 -2
- package/dist/Utils/segmentation/tools/BaseTool.js.map +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 +50 -25
- 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 -10
- package/dist/Utils/segmentation/tools/DragSliceTool.js +63 -53
- package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/DrawingTool.d.ts +85 -0
- package/dist/Utils/segmentation/tools/DrawingTool.js +270 -0
- package/dist/Utils/segmentation/tools/DrawingTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/EraserTool.js +8 -8
- package/dist/Utils/segmentation/tools/EraserTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +13 -33
- package/dist/Utils/segmentation/tools/ImageStoreHelper.js +39 -82
- package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -1
- package/dist/Utils/segmentation/tools/LayerChannelManager.d.ts +52 -0
- package/dist/Utils/segmentation/tools/LayerChannelManager.js +175 -0
- package/dist/Utils/segmentation/tools/LayerChannelManager.js.map +1 -0
- package/dist/Utils/segmentation/tools/PanTool.d.ts +47 -0
- package/dist/Utils/segmentation/tools/PanTool.js +90 -0
- package/dist/Utils/segmentation/tools/PanTool.js.map +1 -0
- 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 +223 -10
- package/dist/Utils/segmentation/tools/SphereTool.js +512 -47
- 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 +3 -3
- package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/index.d.ts +8 -6
- package/dist/Utils/segmentation/tools/index.js +10 -1
- package/dist/Utils/segmentation/tools/index.js.map +1 -1
- package/dist/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
- package/dist/bundle.esm.js +3729 -3017
- package/dist/bundle.umd.js +3729 -3017
- package/dist/index.d.ts +6 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/Utils/segmentation/CanvasState.d.ts +31 -0
- package/dist/types/Utils/segmentation/DragOperator.d.ts +10 -2
- package/dist/types/Utils/segmentation/DrawToolCore.d.ts +64 -35
- package/dist/types/Utils/segmentation/NrrdTools.d.ts +117 -353
- package/dist/types/Utils/segmentation/RenderingUtils.d.ts +73 -0
- package/dist/types/Utils/segmentation/core/UndoManager.d.ts +1 -1
- package/dist/types/Utils/segmentation/core/index.d.ts +1 -3
- package/dist/types/Utils/segmentation/core/types.d.ts +384 -0
- package/dist/types/Utils/segmentation/coreTools/GuiState.d.ts +22 -0
- package/dist/types/Utils/segmentation/coreTools/NrrdState.d.ts +25 -0
- package/dist/types/Utils/segmentation/coreTools/divControlTools.d.ts +1 -1
- package/dist/types/Utils/segmentation/coreTools/gui.d.ts +12 -12
- package/dist/types/Utils/segmentation/eventRouter/EventRouter.d.ts +2 -1
- package/dist/types/Utils/segmentation/eventRouter/types.d.ts +3 -3
- package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +8 -5
- 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 -10
- package/dist/types/Utils/segmentation/tools/DrawingTool.d.ts +85 -0
- package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +13 -33
- package/dist/types/Utils/segmentation/tools/LayerChannelManager.d.ts +52 -0
- package/dist/types/Utils/segmentation/tools/PanTool.d.ts +47 -0
- package/dist/types/Utils/segmentation/tools/SliceRenderPipeline.d.ts +80 -0
- package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +223 -10
- 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 +8 -6
- package/dist/types/Utils/workers/reformatSaveDataWorker.d.ts +1 -1
- package/dist/types/index.d.ts +6 -3
- package/package.json +1 -1
- package/dist/Utils/segmentation/CommToolsData.d.ts +0 -177
- package/dist/Utils/segmentation/CommToolsData.js +0 -548
- package/dist/Utils/segmentation/CommToolsData.js.map +0 -1
- package/dist/Utils/segmentation/core/MigrationUtils.d.ts +0 -97
- package/dist/Utils/segmentation/core/MigrationUtils.js +0 -163
- package/dist/Utils/segmentation/core/MigrationUtils.js.map +0 -1
- package/dist/Utils/segmentation/coreTools/coreType.d.ts +0 -363
- 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 -177
- package/dist/types/Utils/segmentation/core/MigrationUtils.d.ts +0 -97
- package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +0 -363
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { setupGui } from "./coreTools/gui";
|
|
2
|
-
import { autoFocusDiv } from "./coreTools/divControlTools";
|
|
2
|
+
import { autoFocusDiv, enableDownload } from "./coreTools/divControlTools";
|
|
3
3
|
import { DragOperator } from "./DragOperator";
|
|
4
4
|
import { DrawToolCore } from "./DrawToolCore";
|
|
5
|
-
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";
|
|
6
10
|
/**
|
|
7
11
|
* Core NRRD annotation tool for medical image segmentation.
|
|
8
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
|
+
*
|
|
9
22
|
* @example
|
|
10
23
|
* ```ts
|
|
11
24
|
* // Default 3 layers: layer1, layer2, layer3
|
|
@@ -15,54 +28,103 @@ import { MaskVolume, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss } from "./core";
|
|
|
15
28
|
* const tools = new NrrdTools(container, { layers: ["layer1", "layer2"] });
|
|
16
29
|
* ```
|
|
17
30
|
*/
|
|
18
|
-
export class NrrdTools
|
|
31
|
+
export class NrrdTools {
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
33
|
+
// 1. Constructor + Module Initialisation
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
35
|
constructor(container, options) {
|
|
20
|
-
super(container, options);
|
|
21
|
-
this.initState = true;
|
|
22
36
|
this._sliceRAFId = null;
|
|
23
37
|
this._pendingSliceStep = 0;
|
|
38
|
+
/** Whether calculator mode is active (not part of gui_states interface) */
|
|
39
|
+
this._calculatorActive = false;
|
|
40
|
+
/** Stored closure callbacks from gui.ts setupGui() */
|
|
41
|
+
this.guiCallbacks = null;
|
|
24
42
|
this.container = container;
|
|
25
|
-
|
|
26
|
-
|
|
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);
|
|
27
52
|
this.init();
|
|
28
|
-
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));
|
|
29
54
|
// Inject EventRouter into DragOperator for centralized event handling
|
|
30
|
-
if (this.eventRouter) {
|
|
31
|
-
this.dragOperator.setEventRouter(this.eventRouter);
|
|
55
|
+
if (this.drawCore.eventRouter) {
|
|
56
|
+
this.dragOperator.setEventRouter(this.drawCore.eventRouter);
|
|
32
57
|
}
|
|
58
|
+
// Wire sphere overlay refresh callback into DragOperator → DragSliceTool
|
|
59
|
+
this.dragOperator.setRefreshSphereOverlay(() => this.refreshSphereOverlay());
|
|
60
|
+
// Initialize extracted modules
|
|
61
|
+
this.initNrrdToolsModules();
|
|
33
62
|
}
|
|
34
63
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
64
|
+
* Wire DrawToolCore's overridable methods to NrrdTools implementations.
|
|
65
|
+
* This replaces the old inheritance approach.
|
|
37
66
|
*/
|
|
38
|
-
|
|
39
|
-
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();
|
|
40
80
|
}
|
|
41
81
|
/**
|
|
42
|
-
*
|
|
43
|
-
* @param size number
|
|
82
|
+
* A initialise function for nrrd_tools
|
|
44
83
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.baseCanvasesSize = 1;
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
this.baseCanvasesSize = size;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
setDisplaySliceIndexPanel(panel) {
|
|
57
|
-
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();
|
|
58
89
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
});
|
|
65
124
|
}
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
+
// 2. GUI Setup
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
66
128
|
/**
|
|
67
129
|
* Set up GUI for drawing panel
|
|
68
130
|
* @param gui GUI
|
|
@@ -73,798 +135,437 @@ export class NrrdTools extends DrawToolCore {
|
|
|
73
135
|
const guiOptions = {
|
|
74
136
|
modeFolder,
|
|
75
137
|
dragOperator: this.dragOperator,
|
|
76
|
-
gui_states: this.gui_states,
|
|
77
|
-
nrrd_states: this.nrrd_states,
|
|
78
|
-
drawingCanvas: this.protectedData.canvases.drawingCanvas,
|
|
79
|
-
drawingPrameters: this.drawingPrameters,
|
|
80
|
-
eraserUrls: this.eraserUrls,
|
|
81
|
-
pencilUrls: this.pencilUrls,
|
|
82
|
-
mainPreSlices: this.protectedData.mainPreSlices,
|
|
83
|
-
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,
|
|
84
146
|
removeDragMode: this.dragOperator.removeDragMode,
|
|
85
147
|
configDragMode: this.dragOperator.configDragMode,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
updateSlicesContrast: this.updateSlicesContrast,
|
|
89
|
-
setMainAreaSize: this.setMainAreaSize,
|
|
90
|
-
resetPaintAreaUIPosition: this.resetPaintAreaUIPosition,
|
|
91
|
-
resizePaintArea: this.resizePaintArea,
|
|
92
|
-
repraintCurrentContrastSlice: this.repraintCurrentContrastSlice,
|
|
93
|
-
setSyncsliceNum: this.setSyncsliceNum,
|
|
94
|
-
resetLayerCanvas: this.resetLayerCanvas,
|
|
95
|
-
redrawDisplayCanvas: this.redrawDisplayCanvas,
|
|
96
|
-
flipDisplayImageByAxis: this.flipDisplayImageByAxis,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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),
|
|
167
|
+
resetZoom: () => this.executeAction("resetZoom"),
|
|
168
|
+
downloadCurrentMask: () => this.executeAction("downloadCurrentMask"),
|
|
106
169
|
};
|
|
107
170
|
this.guiParameterSettings = setupGui(guiOptions);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
guiSetting: this.guiParameterSettings,
|
|
171
|
+
// Store closure callbacks for programmatic access (Phase 1 Task 1.2)
|
|
172
|
+
this.guiCallbacks = {
|
|
173
|
+
updatePencilState: this.guiParameterSettings.pencil.onChange,
|
|
174
|
+
updateEraserState: this.guiParameterSettings.eraser.onChange,
|
|
175
|
+
updateBrushAndEraserSize: this.guiParameterSettings.brushAndEraserSize.onChange,
|
|
176
|
+
updateSphereState: this.guiParameterSettings.sphere.onChange,
|
|
177
|
+
updateCalDistance: this.guiParameterSettings.activeSphereType.onChange,
|
|
178
|
+
updateWindowHigh: this.guiParameterSettings.windowHigh.onChange,
|
|
179
|
+
updateWindowLow: this.guiParameterSettings.windowLow.onChange,
|
|
180
|
+
finishContrastAdjustment: this.guiParameterSettings.windowHigh.onFinished,
|
|
119
181
|
};
|
|
120
182
|
}
|
|
121
|
-
// ── Layer & Channel Management API ─────────────────────────────────────
|
|
122
183
|
/**
|
|
123
|
-
*
|
|
184
|
+
* Sync guiParameterSettings with current volume metadata.
|
|
124
185
|
*/
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
*/
|
|
132
|
-
setActiveChannel(channel) {
|
|
133
|
-
this.gui_states.activeChannel = channel;
|
|
134
|
-
this.syncBrushColor();
|
|
186
|
+
syncGuiParameterSettings() {
|
|
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;
|
|
191
|
+
}
|
|
135
192
|
}
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
194
|
+
// 3. Public API — Mode / Slider / Color / Action
|
|
195
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
136
196
|
/**
|
|
137
|
-
*
|
|
138
|
-
* Falls back to global CHANNEL_HEX_COLORS if volume not available.
|
|
197
|
+
* Set the current tool mode.
|
|
139
198
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
setMode(mode) {
|
|
200
|
+
if (!this.guiCallbacks)
|
|
201
|
+
return;
|
|
202
|
+
const prevSphere = this.state.gui_states.mode.sphere;
|
|
203
|
+
const prevCalculator = this._calculatorActive;
|
|
204
|
+
this.state.gui_states.mode.pencil = false;
|
|
205
|
+
this.state.gui_states.mode.eraser = false;
|
|
206
|
+
this.state.gui_states.mode.sphere = false;
|
|
207
|
+
this._calculatorActive = false;
|
|
208
|
+
switch (mode) {
|
|
209
|
+
case "pencil":
|
|
210
|
+
this.state.gui_states.mode.pencil = true;
|
|
211
|
+
this.guiCallbacks.updatePencilState();
|
|
212
|
+
break;
|
|
213
|
+
case "brush":
|
|
214
|
+
this.guiCallbacks.updatePencilState();
|
|
215
|
+
break;
|
|
216
|
+
case "eraser":
|
|
217
|
+
this.state.gui_states.mode.eraser = true;
|
|
218
|
+
this.guiCallbacks.updateEraserState();
|
|
219
|
+
break;
|
|
220
|
+
case "sphere":
|
|
221
|
+
this.state.gui_states.mode.sphere = true;
|
|
222
|
+
break;
|
|
223
|
+
case "calculator":
|
|
224
|
+
this.state.gui_states.mode.sphere = true;
|
|
225
|
+
this._calculatorActive = true;
|
|
226
|
+
break;
|
|
148
227
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
228
|
+
if (prevSphere && !this.state.gui_states.mode.sphere) {
|
|
229
|
+
this.guiCallbacks.updateSphereState();
|
|
230
|
+
}
|
|
231
|
+
if (!prevSphere && this.state.gui_states.mode.sphere) {
|
|
232
|
+
this.guiCallbacks.updateSphereState();
|
|
233
|
+
}
|
|
234
|
+
if (prevCalculator && !this._calculatorActive) {
|
|
235
|
+
// calculator was exited
|
|
153
236
|
}
|
|
154
237
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
238
|
+
getMode() {
|
|
239
|
+
if (this._calculatorActive)
|
|
240
|
+
return "calculator";
|
|
241
|
+
if (this.state.gui_states.mode.sphere)
|
|
242
|
+
return "sphere";
|
|
243
|
+
if (this.state.gui_states.mode.eraser)
|
|
244
|
+
return "eraser";
|
|
245
|
+
if (this.state.gui_states.mode.pencil)
|
|
246
|
+
return "pencil";
|
|
247
|
+
return "brush";
|
|
160
248
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
*/
|
|
164
|
-
getActiveChannel() {
|
|
165
|
-
return this.gui_states.activeChannel;
|
|
249
|
+
isCalculatorActive() {
|
|
250
|
+
return this._calculatorActive;
|
|
166
251
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
*/
|
|
170
|
-
setLayerVisible(layerId, visible) {
|
|
171
|
-
this.gui_states.layerVisibility[layerId] = visible;
|
|
172
|
-
this.reloadMasksFromVolume();
|
|
252
|
+
setOpacity(value) {
|
|
253
|
+
this.state.gui_states.drawing.globalAlpha = Math.max(0.1, Math.min(1, value));
|
|
173
254
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*/
|
|
177
|
-
isLayerVisible(layerId) {
|
|
178
|
-
var _a;
|
|
179
|
-
return (_a = this.gui_states.layerVisibility[layerId]) !== null && _a !== void 0 ? _a : true;
|
|
255
|
+
getOpacity() {
|
|
256
|
+
return this.state.gui_states.drawing.globalAlpha;
|
|
180
257
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (this.gui_states.channelVisibility[layerId]) {
|
|
186
|
-
this.gui_states.channelVisibility[layerId][channel] = visible;
|
|
187
|
-
}
|
|
188
|
-
this.reloadMasksFromVolume();
|
|
258
|
+
setBrushSize(size) {
|
|
259
|
+
var _a;
|
|
260
|
+
this.state.gui_states.drawing.brushAndEraserSize = Math.max(5, Math.min(50, size));
|
|
261
|
+
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.updateBrushAndEraserSize();
|
|
189
262
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*/
|
|
193
|
-
isChannelVisible(layerId, channel) {
|
|
194
|
-
var _a, _b;
|
|
195
|
-
return (_b = (_a = this.gui_states.channelVisibility[layerId]) === null || _a === void 0 ? void 0 : _a[channel]) !== null && _b !== void 0 ? _b : true;
|
|
263
|
+
getBrushSize() {
|
|
264
|
+
return this.state.gui_states.drawing.brushAndEraserSize;
|
|
196
265
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return Object.assign({}, this.gui_states.layerVisibility);
|
|
266
|
+
setWindowHigh(value) {
|
|
267
|
+
var _a;
|
|
268
|
+
this.state.gui_states.viewConfig.readyToUpdate = false;
|
|
269
|
+
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.updateWindowHigh(value);
|
|
202
270
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const result = {};
|
|
208
|
-
for (const layerId of this.nrrd_states.layers) {
|
|
209
|
-
result[layerId] = Object.assign({}, this.gui_states.channelVisibility[layerId]);
|
|
210
|
-
}
|
|
211
|
-
return result;
|
|
271
|
+
setWindowLow(value) {
|
|
272
|
+
var _a;
|
|
273
|
+
this.state.gui_states.viewConfig.readyToUpdate = false;
|
|
274
|
+
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.updateWindowLow(value);
|
|
212
275
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* This is useful for determining if a layer has actual annotations
|
|
217
|
-
* before saving or exporting to avoid processing empty layers.
|
|
218
|
-
*
|
|
219
|
-
* @param layerId - The layer to check ('layer1', 'layer2', or 'layer3')
|
|
220
|
-
* @returns True if the layer has any non-zero voxel data, false if empty
|
|
221
|
-
*
|
|
222
|
-
* @example
|
|
223
|
-
* ```ts
|
|
224
|
-
* if (nrrdTools.hasLayerData('layer1')) {
|
|
225
|
-
* await useSaveMasks(caseId, 'layer1');
|
|
226
|
-
* }
|
|
227
|
-
* ```
|
|
228
|
-
*/
|
|
229
|
-
hasLayerData(layerId) {
|
|
230
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
231
|
-
if (!volume) {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
return volume.hasData();
|
|
276
|
+
finishWindowAdjustment() {
|
|
277
|
+
var _a;
|
|
278
|
+
(_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.finishContrastAdjustment();
|
|
235
279
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
* Only affects this layer; other layers remain unchanged.
|
|
240
|
-
*
|
|
241
|
-
* @param layerId Layer to customize (e.g. 'layer1', 'layer2')
|
|
242
|
-
* @param channel Channel label (1-8)
|
|
243
|
-
* @param color RGBAColor object { r, g, b, a } (each 0-255)
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* ```ts
|
|
247
|
-
* // Make channel 2 in layer2 orange
|
|
248
|
-
* nrrdTools.setChannelColor('layer2', 2, { r: 255, g: 128, b: 0, a: 255 });
|
|
249
|
-
* ```
|
|
250
|
-
*/
|
|
251
|
-
setChannelColor(layerId, channel, color) {
|
|
252
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
253
|
-
if (!volume) {
|
|
254
|
-
console.warn(`setChannelColor: unknown layer "${layerId}"`);
|
|
280
|
+
adjustContrast(type, delta) {
|
|
281
|
+
var _a, _b;
|
|
282
|
+
if (!this.guiParameterSettings)
|
|
255
283
|
return;
|
|
284
|
+
const setting = this.guiParameterSettings[type];
|
|
285
|
+
const vol = setting.value;
|
|
286
|
+
const currentValue = type === "windowHigh"
|
|
287
|
+
? ((_a = vol === null || vol === void 0 ? void 0 : vol.windowHigh) !== null && _a !== void 0 ? _a : 0)
|
|
288
|
+
: ((_b = vol === null || vol === void 0 ? void 0 : vol.windowLow) !== null && _b !== void 0 ? _b : 0);
|
|
289
|
+
const newValue = currentValue + delta;
|
|
290
|
+
if (newValue >= setting.max || newValue <= 0)
|
|
291
|
+
return;
|
|
292
|
+
if (type === "windowHigh") {
|
|
293
|
+
this.setWindowHigh(newValue);
|
|
256
294
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.syncBrushColor();
|
|
260
|
-
}
|
|
261
|
-
this.reloadMasksFromVolume();
|
|
262
|
-
this.nrrd_states.onChannelColorChanged(layerId, channel, color);
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Get the current color for a specific channel in a specific layer.
|
|
266
|
-
*
|
|
267
|
-
* @param layerId Layer to query
|
|
268
|
-
* @param channel Channel label (1-8)
|
|
269
|
-
* @returns RGBAColor object (copy, safe to mutate)
|
|
270
|
-
*/
|
|
271
|
-
getChannelColor(layerId, channel) {
|
|
272
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
273
|
-
if (!volume) {
|
|
274
|
-
return { r: 0, g: 255, b: 0, a: 255 };
|
|
295
|
+
else {
|
|
296
|
+
this.setWindowLow(newValue);
|
|
275
297
|
}
|
|
276
|
-
return volume.getChannelColor(channel);
|
|
277
298
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return rgbaToCss(this.getChannelColor(layerId, channel));
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Batch-set multiple channel colors for a single layer (single re-render).
|
|
292
|
-
*
|
|
293
|
-
* @param layerId Layer to customize
|
|
294
|
-
* @param colorMap Partial map of channel -> RGBAColor
|
|
295
|
-
*
|
|
296
|
-
* @example
|
|
297
|
-
* ```ts
|
|
298
|
-
* nrrdTools.setChannelColors('layer2', {
|
|
299
|
-
* 2: { r: 255, g: 128, b: 0, a: 255 },
|
|
300
|
-
* 3: { r: 100, g: 200, b: 50, a: 255 },
|
|
301
|
-
* });
|
|
302
|
-
* ```
|
|
303
|
-
*/
|
|
304
|
-
setChannelColors(layerId, colorMap) {
|
|
305
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
306
|
-
if (!volume) {
|
|
307
|
-
console.warn(`setChannelColors: unknown layer "${layerId}"`);
|
|
308
|
-
return;
|
|
299
|
+
getSliderMeta(key) {
|
|
300
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
301
|
+
if (!this.guiParameterSettings)
|
|
302
|
+
return null;
|
|
303
|
+
const setting = this.guiParameterSettings[key];
|
|
304
|
+
if (!setting)
|
|
305
|
+
return null;
|
|
306
|
+
let value;
|
|
307
|
+
if (key === "windowHigh") {
|
|
308
|
+
value = (_b = (_a = setting.value) === null || _a === void 0 ? void 0 : _a.windowHigh) !== null && _b !== void 0 ? _b : 0;
|
|
309
309
|
}
|
|
310
|
-
|
|
311
|
-
|
|
310
|
+
else if (key === "windowLow") {
|
|
311
|
+
value = (_d = (_c = setting.value) === null || _c === void 0 ? void 0 : _c.windowLow) !== null && _d !== void 0 ? _d : 0;
|
|
312
312
|
}
|
|
313
|
-
|
|
314
|
-
this.
|
|
313
|
+
else {
|
|
314
|
+
value = (_e = this.state.gui_states.drawing[key]) !== null && _e !== void 0 ? _e : 0;
|
|
315
315
|
}
|
|
316
|
-
|
|
316
|
+
return {
|
|
317
|
+
min: (_f = setting.min) !== null && _f !== void 0 ? _f : 0,
|
|
318
|
+
max: (_g = setting.max) !== null && _g !== void 0 ? _g : 100,
|
|
319
|
+
step: (_h = setting.step) !== null && _h !== void 0 ? _h : 1,
|
|
320
|
+
value,
|
|
321
|
+
};
|
|
317
322
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
*
|
|
321
|
-
* @param channel Channel label (1-8)
|
|
322
|
-
* @param color RGBAColor object
|
|
323
|
-
*/
|
|
324
|
-
setAllLayersChannelColor(channel, color) {
|
|
325
|
-
for (const layerId of this.nrrd_states.layers) {
|
|
326
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
327
|
-
if (volume) {
|
|
328
|
-
volume.setChannelColor(channel, color);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (channel === this.gui_states.activeChannel) {
|
|
332
|
-
this.syncBrushColor();
|
|
333
|
-
}
|
|
334
|
-
this.reloadMasksFromVolume();
|
|
323
|
+
setPencilColor(hex) {
|
|
324
|
+
this.state.gui_states.drawing.color = hex;
|
|
335
325
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
326
|
+
getPencilColor() {
|
|
327
|
+
return this.state.gui_states.drawing.color;
|
|
328
|
+
}
|
|
329
|
+
executeAction(action) {
|
|
330
|
+
switch (action) {
|
|
331
|
+
case "undo":
|
|
332
|
+
this.undo();
|
|
333
|
+
break;
|
|
334
|
+
case "redo":
|
|
335
|
+
this.redo();
|
|
336
|
+
break;
|
|
337
|
+
case "clearActiveSliceMask":
|
|
338
|
+
this.drawCore.clearActiveSlice();
|
|
339
|
+
break;
|
|
340
|
+
case "clearActiveLayerMask": {
|
|
341
|
+
const text = "Are you sure remove annotations on All slice?";
|
|
342
|
+
if (confirm(text) === true) {
|
|
343
|
+
this.state.nrrd_states.flags.clearAllFlag = true;
|
|
344
|
+
this.drawCore.clearActiveSlice();
|
|
345
|
+
this.clearActiveLayer();
|
|
346
|
+
}
|
|
347
|
+
this.state.nrrd_states.flags.clearAllFlag = false;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case "resetZoom":
|
|
351
|
+
this.state.nrrd_states.view.sizeFactor = this.state.baseCanvasesSize;
|
|
352
|
+
this.state.gui_states.viewConfig.mainAreaSize = this.state.baseCanvasesSize;
|
|
353
|
+
this.resizePaintArea(this.state.nrrd_states.view.sizeFactor);
|
|
354
|
+
this.resetPaintAreaUIPosition();
|
|
355
|
+
break;
|
|
356
|
+
case "downloadCurrentMask": {
|
|
357
|
+
const config = {
|
|
358
|
+
axis: this.state.protectedData.axis,
|
|
359
|
+
currentSliceIndex: this.state.nrrd_states.view.currentSliceIndex,
|
|
360
|
+
drawingCanvas: this.state.protectedData.canvases.drawingCanvas,
|
|
361
|
+
originWidth: this.state.nrrd_states.image.originWidth,
|
|
362
|
+
originHeight: this.state.nrrd_states.image.originHeight,
|
|
363
|
+
};
|
|
364
|
+
enableDownload(config);
|
|
365
|
+
break;
|
|
355
366
|
}
|
|
356
367
|
}
|
|
357
|
-
this.syncBrushColor();
|
|
358
|
-
this.reloadMasksFromVolume();
|
|
359
368
|
}
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
*
|
|
364
|
-
* Equivalent to pressing Ctrl+Z. Reverts the most recent drawing stroke
|
|
365
|
-
* on the currently active layer and syncs the result to the backend via
|
|
366
|
-
* the `getMask` callback.
|
|
367
|
-
*
|
|
368
|
-
* @example
|
|
369
|
-
* ```ts
|
|
370
|
-
* undoBtn.addEventListener('click', () => nrrdTools.undo());
|
|
371
|
-
* ```
|
|
372
|
-
*/
|
|
369
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
370
|
+
// 4. Public API — Keyboard & History
|
|
371
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
373
372
|
undo() {
|
|
374
|
-
this.undoLastPainting();
|
|
373
|
+
this.drawCore.undoLastPainting();
|
|
375
374
|
}
|
|
376
|
-
/**
|
|
377
|
-
* Programmatically trigger a redo operation.
|
|
378
|
-
*
|
|
379
|
-
* Equivalent to pressing Ctrl+Y. Re-applies the most recently undone
|
|
380
|
-
* drawing stroke on the currently active layer and syncs the result to
|
|
381
|
-
* the backend via the `getMask` callback.
|
|
382
|
-
*
|
|
383
|
-
* @example
|
|
384
|
-
* ```ts
|
|
385
|
-
* redoBtn.addEventListener('click', () => nrrdTools.redo());
|
|
386
|
-
* ```
|
|
387
|
-
*/
|
|
388
375
|
redo() {
|
|
389
|
-
this.redoLastPainting();
|
|
376
|
+
this.drawCore.redoLastPainting();
|
|
390
377
|
}
|
|
391
|
-
/**
|
|
392
|
-
* Enter keyboard-configuration mode.
|
|
393
|
-
*
|
|
394
|
-
* While active, every keydown/keyup handler in DrawToolCore and DragOperator
|
|
395
|
-
* is suppressed so the user can press arbitrary keys in the settings dialog
|
|
396
|
-
* without accidentally triggering drawing, undo, or contrast shortcuts.
|
|
397
|
-
*
|
|
398
|
-
* Always pair with {@link exitKeyboardConfig} when the dialog closes.
|
|
399
|
-
*
|
|
400
|
-
* @example
|
|
401
|
-
* ```ts
|
|
402
|
-
* dialog.addEventListener('open', () => nrrdTools.enterKeyboardConfig());
|
|
403
|
-
* ```
|
|
404
|
-
*/
|
|
405
378
|
enterKeyboardConfig() {
|
|
406
|
-
this.
|
|
379
|
+
this.state.configKeyBoard = true;
|
|
407
380
|
}
|
|
408
|
-
/**
|
|
409
|
-
* Exit keyboard-configuration mode and resume normal shortcut handling.
|
|
410
|
-
*
|
|
411
|
-
* @example
|
|
412
|
-
* ```ts
|
|
413
|
-
* dialog.addEventListener('close', () => nrrdTools.exitKeyboardConfig());
|
|
414
|
-
* ```
|
|
415
|
-
*/
|
|
416
381
|
exitKeyboardConfig() {
|
|
417
|
-
this.
|
|
382
|
+
this.state.configKeyBoard = false;
|
|
418
383
|
}
|
|
419
|
-
/**
|
|
420
|
-
* Enable or disable the contrast window/level shortcut (Ctrl/Meta key).
|
|
421
|
-
*
|
|
422
|
-
* When disabled:
|
|
423
|
-
* - Holding Ctrl no longer enters contrast mode
|
|
424
|
-
* - If contrast mode is currently active it is immediately exited
|
|
425
|
-
* - All other Ctrl-based shortcuts (Ctrl+Z undo, Ctrl+Y redo) are
|
|
426
|
-
* unaffected because they are checked in the keydown handler before
|
|
427
|
-
* the contrast guard runs
|
|
428
|
-
*
|
|
429
|
-
* @param enabled - Pass `false` to disable, `true` to re-enable.
|
|
430
|
-
*
|
|
431
|
-
* @example
|
|
432
|
-
* ```ts
|
|
433
|
-
* // Disable contrast when sphere tool is active
|
|
434
|
-
* nrrdTools.setContrastShortcutEnabled(false);
|
|
435
|
-
*
|
|
436
|
-
* // Re-enable when returning to draw mode
|
|
437
|
-
* nrrdTools.setContrastShortcutEnabled(true);
|
|
438
|
-
* ```
|
|
439
|
-
*/
|
|
440
384
|
setContrastShortcutEnabled(enabled) {
|
|
441
385
|
var _a;
|
|
442
|
-
(_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.setContrastEnabled(enabled);
|
|
386
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setContrastEnabled(enabled);
|
|
443
387
|
}
|
|
444
|
-
/**
|
|
445
|
-
* Returns whether the contrast shortcut is currently enabled.
|
|
446
|
-
*/
|
|
447
388
|
isContrastShortcutEnabled() {
|
|
448
389
|
var _a, _b;
|
|
449
|
-
return (_b = (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()) !== null && _b !== void 0 ? _b : true;
|
|
390
|
+
return (_b = (_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()) !== null && _b !== void 0 ? _b : true;
|
|
450
391
|
}
|
|
451
|
-
/**
|
|
452
|
-
* Update keyboard shortcut bindings.
|
|
453
|
-
*
|
|
454
|
-
* Synchronises both the internal class field (read by every keydown handler)
|
|
455
|
-
* and the EventRouter's internal copy (used for modifier-key mode tracking)
|
|
456
|
-
* so the two never drift apart. If `mouseWheel` is changed the wheel event
|
|
457
|
-
* listener is automatically re-bound via {@link updateMouseWheelEvent}.
|
|
458
|
-
*
|
|
459
|
-
* Only the fields you supply are updated; omitted fields keep their
|
|
460
|
-
* current values.
|
|
461
|
-
*
|
|
462
|
-
* @param settings - Partial keyboard settings to override.
|
|
463
|
-
*
|
|
464
|
-
* @example
|
|
465
|
-
* ```ts
|
|
466
|
-
* nrrdTools.setKeyboardSettings({
|
|
467
|
-
* undo: 'z',
|
|
468
|
-
* redo: 'y',
|
|
469
|
-
* crosshair: 'c',
|
|
470
|
-
* mouseWheel: 'Scroll:Slice',
|
|
471
|
-
* });
|
|
472
|
-
* ```
|
|
473
|
-
*/
|
|
474
392
|
setKeyboardSettings(settings) {
|
|
475
393
|
var _a;
|
|
476
394
|
const mouseWheelChanged = settings.mouseWheel !== undefined
|
|
477
|
-
&& settings.mouseWheel !== this.
|
|
478
|
-
Object.assign(this.
|
|
479
|
-
(_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.setKeyboardSettings(settings);
|
|
395
|
+
&& settings.mouseWheel !== this.state.keyboardSettings.mouseWheel;
|
|
396
|
+
Object.assign(this.state.keyboardSettings, settings);
|
|
397
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setKeyboardSettings(settings);
|
|
480
398
|
if (mouseWheelChanged) {
|
|
481
399
|
this.updateMouseWheelEvent();
|
|
482
400
|
}
|
|
483
401
|
}
|
|
484
|
-
/**
|
|
485
|
-
* Get a snapshot of the current keyboard shortcut bindings.
|
|
486
|
-
*
|
|
487
|
-
* Returns a shallow copy so callers cannot accidentally mutate internal
|
|
488
|
-
* state. To update settings use {@link setKeyboardSettings} instead.
|
|
489
|
-
*
|
|
490
|
-
* @returns Current keyboard settings.
|
|
491
|
-
*
|
|
492
|
-
* @example
|
|
493
|
-
* ```ts
|
|
494
|
-
* const { undo, redo } = nrrdTools.getKeyboardSettings();
|
|
495
|
-
* console.log(`Undo: Ctrl+${undo}, Redo: Ctrl+${redo}`);
|
|
496
|
-
* ```
|
|
497
|
-
*/
|
|
498
402
|
getKeyboardSettings() {
|
|
499
|
-
return Object.assign({}, this.
|
|
403
|
+
return Object.assign({}, this.state.keyboardSettings);
|
|
500
404
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
this.
|
|
506
|
-
this.container.appendChild(this.protectedData.mainAreaContainer);
|
|
507
|
-
autoFocusDiv(this.container);
|
|
508
|
-
this.setShowInMainArea();
|
|
405
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
406
|
+
// 5. Public API — View Control (drag, zoom, pan, slice navigation)
|
|
407
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
408
|
+
drag(opts) {
|
|
409
|
+
this.dragOperator.drag(opts);
|
|
509
410
|
}
|
|
510
411
|
/**
|
|
511
|
-
*
|
|
512
|
-
* entry function
|
|
513
|
-
* * {
|
|
514
|
-
* x:slice,
|
|
515
|
-
* y:slice,
|
|
516
|
-
* z:slice
|
|
517
|
-
* }
|
|
518
|
-
*
|
|
519
|
-
* @param allSlices - all nrrd contrast slices
|
|
520
|
-
|
|
412
|
+
* Core drawing entry point.
|
|
521
413
|
*/
|
|
522
|
-
|
|
523
|
-
this.
|
|
524
|
-
const randomSlice = this.protectedData.allSlicesArray[0];
|
|
525
|
-
this.nrrd_states.nrrd_x_mm = randomSlice.z.canvas.width;
|
|
526
|
-
this.nrrd_states.nrrd_y_mm = randomSlice.z.canvas.height;
|
|
527
|
-
this.nrrd_states.nrrd_z_mm = randomSlice.x.canvas.width;
|
|
528
|
-
this.nrrd_states.nrrd_x_pixel = randomSlice.x.volume.dimensions[0];
|
|
529
|
-
this.nrrd_states.nrrd_y_pixel = randomSlice.x.volume.dimensions[1];
|
|
530
|
-
this.nrrd_states.nrrd_z_pixel = randomSlice.x.volume.dimensions[2];
|
|
531
|
-
this.nrrd_states.voxelSpacing = randomSlice.x.volume.spacing;
|
|
532
|
-
this.nrrd_states.ratios.x = randomSlice.x.volume.spacing[0];
|
|
533
|
-
this.nrrd_states.ratios.y = randomSlice.x.volume.spacing[1];
|
|
534
|
-
this.nrrd_states.ratios.z = randomSlice.x.volume.spacing[2];
|
|
535
|
-
this.nrrd_states.dimensions = randomSlice.x.volume.dimensions;
|
|
536
|
-
// Phase 2 Day 9: Re-initialize MaskVolume with real NRRD dimensions.
|
|
537
|
-
// This replaces the 1×1×1 placeholders from CommToolsData constructor
|
|
538
|
-
// and "turns on" all Day 7/8 volume read/write paths.
|
|
539
|
-
// Invalidate reusable buffer from previous dataset
|
|
540
|
-
this.invalidateSliceBuffer();
|
|
541
|
-
const [vw, vh, vd] = this.nrrd_states.dimensions;
|
|
542
|
-
this.protectedData.maskData.volumes = this.nrrd_states.layers.reduce((acc, id) => {
|
|
543
|
-
acc[id] = new MaskVolume(vw, vh, vd, 1);
|
|
544
|
-
return acc;
|
|
545
|
-
}, {});
|
|
546
|
-
this.nrrd_states.spaceOrigin = randomSlice.x.volume.header.space_origin.map((item) => {
|
|
547
|
-
return item * 1;
|
|
548
|
-
});
|
|
549
|
-
this.protectedData.allSlicesArray.forEach((item, index) => {
|
|
550
|
-
item.x.contrastOrder = index;
|
|
551
|
-
item.y.contrastOrder = index;
|
|
552
|
-
item.z.contrastOrder = index;
|
|
553
|
-
});
|
|
554
|
-
// Phase 3: initPaintImages removed (MaskVolume initialized separately)
|
|
555
|
-
// this.initPaintImages(this.nrrd_states.dimensions);
|
|
556
|
-
// init displayslices array, the axis default is "z"
|
|
557
|
-
this.setDisplaySlicesBaseOnAxis();
|
|
558
|
-
this.afterLoadSlice();
|
|
559
|
-
}
|
|
560
|
-
loadingMaskByLayer(masks, index, imageData) {
|
|
561
|
-
let imageDataLable = this.protectedData.ctxes.emptyCtx.createImageData(this.nrrd_states.nrrd_x_pixel, this.nrrd_states.nrrd_y_pixel);
|
|
562
|
-
this.setEmptyCanvasSize();
|
|
563
|
-
for (let j = 0; j < masks[index].data.length; j++) {
|
|
564
|
-
imageDataLable.data[j] = masks[index].data[j];
|
|
565
|
-
imageData.data[j] += masks[index].data[j];
|
|
566
|
-
}
|
|
567
|
-
return imageDataLable;
|
|
568
|
-
}
|
|
569
|
-
// need to remove
|
|
570
|
-
setMasksData(masksData, loadingBar) {
|
|
571
|
-
if (!!masksData) {
|
|
572
|
-
this.nrrd_states.loadMaskJson = true;
|
|
573
|
-
if (loadingBar) {
|
|
574
|
-
let { loadingContainer, progress } = loadingBar;
|
|
575
|
-
loadingContainer.style.display = "flex";
|
|
576
|
-
progress.innerText = "Loading masks data......";
|
|
577
|
-
}
|
|
578
|
-
this.setEmptyCanvasSize();
|
|
579
|
-
const len = masksData["layer1"].length;
|
|
580
|
-
for (let i = 0; i < len; i++) {
|
|
581
|
-
let imageData = this.protectedData.ctxes.emptyCtx.createImageData(this.nrrd_states.nrrd_x_pixel, this.nrrd_states.nrrd_y_pixel);
|
|
582
|
-
let imageDataLayer1, imageDataLayer2, imageDataLayer3;
|
|
583
|
-
if (masksData["layer1"][i].data.length > 0) {
|
|
584
|
-
this.setEmptyCanvasSize();
|
|
585
|
-
imageDataLayer1 = this.loadingMaskByLayer(masksData["layer1"], i, imageData);
|
|
586
|
-
this.protectedData.ctxes.emptyCtx.putImageData(imageDataLayer1, 0, 0);
|
|
587
|
-
this.storeEachLayerImage(i, "layer1");
|
|
588
|
-
}
|
|
589
|
-
if (masksData["layer2"][i].data.length > 0) {
|
|
590
|
-
this.setEmptyCanvasSize();
|
|
591
|
-
imageDataLayer2 = this.loadingMaskByLayer(masksData["layer2"], i, imageData);
|
|
592
|
-
this.protectedData.ctxes.emptyCtx.putImageData(imageDataLayer2, 0, 0);
|
|
593
|
-
this.storeEachLayerImage(i, "layer2");
|
|
594
|
-
}
|
|
595
|
-
if (masksData["layer3"][i].data.length > 0) {
|
|
596
|
-
this.setEmptyCanvasSize();
|
|
597
|
-
imageDataLayer3 = this.loadingMaskByLayer(masksData["layer3"], i, imageData);
|
|
598
|
-
this.protectedData.ctxes.emptyCtx.putImageData(imageDataLayer3, 0, 0);
|
|
599
|
-
this.storeEachLayerImage(i, "layer3");
|
|
600
|
-
}
|
|
601
|
-
this.setEmptyCanvasSize();
|
|
602
|
-
this.protectedData.ctxes.emptyCtx.putImageData(imageData, 0, 0);
|
|
603
|
-
this.storeAllImages(i, "default");
|
|
604
|
-
}
|
|
605
|
-
this.nrrd_states.loadMaskJson = false;
|
|
606
|
-
this.gui_states.resetZoom();
|
|
607
|
-
if (loadingBar) {
|
|
608
|
-
loadingBar.loadingContainer.style.display = "none";
|
|
609
|
-
}
|
|
610
|
-
}
|
|
414
|
+
draw(opts) {
|
|
415
|
+
this.drawCore.draw(opts);
|
|
611
416
|
}
|
|
612
417
|
/**
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
* Expects pre-extracted voxel bytes (e.g. from useNiftiVoxelData).
|
|
616
|
-
*
|
|
617
|
-
* @param layerVoxels Map of layer ID to raw voxel Uint8Array
|
|
618
|
-
* Keys should be 'layer1', 'layer2', 'layer3'
|
|
619
|
-
* @param loadingBar Optional loading bar UI
|
|
418
|
+
* Set the Draw Display Canvas base size
|
|
620
419
|
*/
|
|
621
|
-
|
|
622
|
-
if (
|
|
623
|
-
|
|
624
|
-
if (loadingBar) {
|
|
625
|
-
loadingBar.loadingContainer.style.display = "flex";
|
|
626
|
-
loadingBar.progress.innerText = "Loading mask layers from NIfTI...";
|
|
627
|
-
}
|
|
628
|
-
try {
|
|
629
|
-
for (const [layerId, rawData] of layerVoxels) {
|
|
630
|
-
const volume = this.protectedData.maskData.volumes[layerId];
|
|
631
|
-
if (!volume) {
|
|
632
|
-
console.warn(`setMasksFromNIfTI: unknown layer "${layerId}", skipping`);
|
|
633
|
-
continue;
|
|
634
|
-
}
|
|
635
|
-
const expectedLen = volume.getRawData().length;
|
|
636
|
-
// Ensure we copy exactly the right number of bytes
|
|
637
|
-
if (rawData.length >= expectedLen) {
|
|
638
|
-
volume.setRawData(rawData.slice(0, expectedLen));
|
|
639
|
-
}
|
|
640
|
-
else {
|
|
641
|
-
const padded = new Uint8Array(expectedLen);
|
|
642
|
-
padded.set(rawData);
|
|
643
|
-
volume.setRawData(padded);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// Reload the current slice from MaskVolume to canvas
|
|
647
|
-
this.reloadMasksFromVolume();
|
|
648
|
-
this.gui_states.resetZoom();
|
|
420
|
+
setBaseDrawDisplayCanvasesSize(size) {
|
|
421
|
+
if (size > 8) {
|
|
422
|
+
this.state.baseCanvasesSize = 8;
|
|
649
423
|
}
|
|
650
|
-
|
|
651
|
-
|
|
424
|
+
else if (size < 1 || typeof size !== "number") {
|
|
425
|
+
this.state.baseCanvasesSize = 1;
|
|
652
426
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
loadingBar.loadingContainer.style.display = "none";
|
|
656
|
-
}
|
|
427
|
+
else {
|
|
428
|
+
this.state.baseCanvasesSize = size;
|
|
657
429
|
}
|
|
658
430
|
}
|
|
659
|
-
|
|
660
|
-
this.
|
|
661
|
-
}
|
|
662
|
-
getCurrentImageDimension() {
|
|
663
|
-
return this.nrrd_states.dimensions;
|
|
664
|
-
}
|
|
665
|
-
getVoxelSpacing() {
|
|
666
|
-
return this.nrrd_states.voxelSpacing;
|
|
431
|
+
setDisplaySliceIndexPanel(panel) {
|
|
432
|
+
this.dragOperator.setShowDragNumberDiv(panel);
|
|
667
433
|
}
|
|
668
|
-
|
|
669
|
-
|
|
434
|
+
enableContrastDragEvents(callback) {
|
|
435
|
+
this.drawCore.setupConrastEvents(callback);
|
|
670
436
|
}
|
|
671
|
-
|
|
672
|
-
|
|
437
|
+
setEraserUrls(urls) {
|
|
438
|
+
this.drawCore.setEraserUrls(urls);
|
|
673
439
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
this.nrrd_states.sphereRadius = 5;
|
|
677
|
-
// move to tumour slice
|
|
678
|
-
const steps = sliceIndex - this.nrrd_states.currentIndex;
|
|
679
|
-
this.setSliceMoving(steps * this.protectedData.displaySlices.length);
|
|
680
|
-
// mock mouse down
|
|
681
|
-
// if user zoom the panel, we need to consider the size factor
|
|
682
|
-
this.drawCalSphereDown(x * this.nrrd_states.sizeFoctor, y * this.nrrd_states.sizeFoctor, sliceIndex, cal_position);
|
|
683
|
-
// mock mouse up
|
|
684
|
-
this.drawCalSphereUp();
|
|
440
|
+
setPencilIconUrls(urls) {
|
|
441
|
+
this.drawCore.setPencilIconUrls(urls);
|
|
685
442
|
}
|
|
686
443
|
/**
|
|
687
444
|
* Switch all contrast slices' orientation
|
|
688
|
-
|
|
689
|
-
* */
|
|
445
|
+
*/
|
|
690
446
|
setSliceOrientation(axisTo) {
|
|
691
447
|
var _a;
|
|
692
448
|
let convetObj;
|
|
693
|
-
if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isCrosshairEnabled()) || this.gui_states.sphere) {
|
|
694
|
-
if (this.protectedData.axis === "z") {
|
|
695
|
-
this.cursorPage.z.index = this.nrrd_states.
|
|
696
|
-
this.cursorPage.z.cursorPageX = this.nrrd_states.cursorPageX;
|
|
697
|
-
this.cursorPage.z.cursorPageY = this.nrrd_states.cursorPageY;
|
|
449
|
+
if (((_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.isCrosshairEnabled()) || this.state.gui_states.mode.sphere) {
|
|
450
|
+
if (this.state.protectedData.axis === "z") {
|
|
451
|
+
this.state.cursorPage.z.index = this.state.nrrd_states.view.currentSliceIndex;
|
|
452
|
+
this.state.cursorPage.z.cursorPageX = this.state.nrrd_states.interaction.cursorPageX;
|
|
453
|
+
this.state.cursorPage.z.cursorPageY = this.state.nrrd_states.interaction.cursorPageY;
|
|
698
454
|
}
|
|
699
|
-
else if (this.protectedData.axis === "x") {
|
|
700
|
-
this.cursorPage.x.index = this.nrrd_states.
|
|
701
|
-
this.cursorPage.x.cursorPageX = this.nrrd_states.cursorPageX;
|
|
702
|
-
this.cursorPage.x.cursorPageY = this.nrrd_states.cursorPageY;
|
|
455
|
+
else if (this.state.protectedData.axis === "x") {
|
|
456
|
+
this.state.cursorPage.x.index = this.state.nrrd_states.view.currentSliceIndex;
|
|
457
|
+
this.state.cursorPage.x.cursorPageX = this.state.nrrd_states.interaction.cursorPageX;
|
|
458
|
+
this.state.cursorPage.x.cursorPageY = this.state.nrrd_states.interaction.cursorPageY;
|
|
703
459
|
}
|
|
704
|
-
else if (this.protectedData.axis === "y") {
|
|
705
|
-
this.cursorPage.y.index = this.nrrd_states.
|
|
706
|
-
this.cursorPage.y.cursorPageX = this.nrrd_states.cursorPageX;
|
|
707
|
-
this.cursorPage.y.cursorPageY = this.nrrd_states.cursorPageY;
|
|
460
|
+
else if (this.state.protectedData.axis === "y") {
|
|
461
|
+
this.state.cursorPage.y.index = this.state.nrrd_states.view.currentSliceIndex;
|
|
462
|
+
this.state.cursorPage.y.cursorPageX = this.state.nrrd_states.interaction.cursorPageX;
|
|
463
|
+
this.state.cursorPage.y.cursorPageY = this.state.nrrd_states.interaction.cursorPageY;
|
|
708
464
|
}
|
|
709
465
|
if (axisTo === "z") {
|
|
710
|
-
if (this.nrrd_states.isCursorSelect && !this.cursorPage.z.updated) {
|
|
711
|
-
if (this.protectedData.axis === "x") {
|
|
712
|
-
|
|
713
|
-
convetObj = this.convertCursorPoint("x", "z", this.cursorPage.x.cursorPageX, this.cursorPage.x.cursorPageY, this.cursorPage.x.index);
|
|
466
|
+
if (this.state.nrrd_states.interaction.isCursorSelect && !this.state.cursorPage.z.updated) {
|
|
467
|
+
if (this.state.protectedData.axis === "x") {
|
|
468
|
+
convetObj = this.drawCore.convertCursorPoint("x", "z", this.state.cursorPage.x.cursorPageX, this.state.cursorPage.x.cursorPageY, this.state.cursorPage.x.index);
|
|
714
469
|
}
|
|
715
|
-
if (this.protectedData.axis === "y") {
|
|
716
|
-
|
|
717
|
-
convetObj = this.convertCursorPoint("y", "z", this.cursorPage.y.cursorPageX, this.cursorPage.y.cursorPageY, this.cursorPage.y.index);
|
|
470
|
+
if (this.state.protectedData.axis === "y") {
|
|
471
|
+
convetObj = this.drawCore.convertCursorPoint("y", "z", this.state.cursorPage.y.cursorPageX, this.state.cursorPage.y.cursorPageY, this.state.cursorPage.y.index);
|
|
718
472
|
}
|
|
719
473
|
}
|
|
720
474
|
else {
|
|
721
|
-
|
|
722
|
-
this.nrrd_states.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
this.nrrd_states.
|
|
726
|
-
this.nrrd_states.cursorPageY = this.cursorPage.z.cursorPageY;
|
|
475
|
+
this.state.nrrd_states.view.currentSliceIndex = this.state.cursorPage.z.index;
|
|
476
|
+
this.state.nrrd_states.view.preSliceIndex =
|
|
477
|
+
this.state.cursorPage.z.index * this.state.nrrd_states.image.ratios.z;
|
|
478
|
+
this.state.nrrd_states.interaction.cursorPageX = this.state.cursorPage.z.cursorPageX;
|
|
479
|
+
this.state.nrrd_states.interaction.cursorPageY = this.state.cursorPage.z.cursorPageY;
|
|
727
480
|
}
|
|
728
481
|
}
|
|
729
482
|
else if (axisTo === "x") {
|
|
730
|
-
if (this.nrrd_states.isCursorSelect && !this.cursorPage.x.updated) {
|
|
731
|
-
if (this.protectedData.axis === "z") {
|
|
732
|
-
|
|
733
|
-
convetObj = this.convertCursorPoint("z", "x", this.cursorPage.z.cursorPageX, this.cursorPage.z.cursorPageY, this.cursorPage.z.index);
|
|
483
|
+
if (this.state.nrrd_states.interaction.isCursorSelect && !this.state.cursorPage.x.updated) {
|
|
484
|
+
if (this.state.protectedData.axis === "z") {
|
|
485
|
+
convetObj = this.drawCore.convertCursorPoint("z", "x", this.state.cursorPage.z.cursorPageX, this.state.cursorPage.z.cursorPageY, this.state.cursorPage.z.index);
|
|
734
486
|
}
|
|
735
|
-
if (this.protectedData.axis === "y") {
|
|
736
|
-
|
|
737
|
-
convetObj = this.convertCursorPoint("y", "x", this.cursorPage.y.cursorPageX, this.cursorPage.y.cursorPageY, this.cursorPage.y.index);
|
|
487
|
+
if (this.state.protectedData.axis === "y") {
|
|
488
|
+
convetObj = this.drawCore.convertCursorPoint("y", "x", this.state.cursorPage.y.cursorPageX, this.state.cursorPage.y.cursorPageY, this.state.cursorPage.y.index);
|
|
738
489
|
}
|
|
739
490
|
}
|
|
740
491
|
else {
|
|
741
|
-
|
|
742
|
-
this.nrrd_states.
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
this.nrrd_states.
|
|
746
|
-
this.nrrd_states.cursorPageY = this.cursorPage.x.cursorPageY;
|
|
492
|
+
this.state.nrrd_states.view.currentSliceIndex = this.state.cursorPage.x.index;
|
|
493
|
+
this.state.nrrd_states.view.preSliceIndex =
|
|
494
|
+
this.state.cursorPage.x.index * this.state.nrrd_states.image.ratios.x;
|
|
495
|
+
this.state.nrrd_states.interaction.cursorPageX = this.state.cursorPage.x.cursorPageX;
|
|
496
|
+
this.state.nrrd_states.interaction.cursorPageY = this.state.cursorPage.x.cursorPageY;
|
|
747
497
|
}
|
|
748
498
|
}
|
|
749
499
|
else if (axisTo === "y") {
|
|
750
|
-
if (this.nrrd_states.isCursorSelect && !this.cursorPage.y.updated) {
|
|
751
|
-
if (this.protectedData.axis === "z") {
|
|
752
|
-
|
|
753
|
-
convetObj = this.convertCursorPoint("z", "y", this.cursorPage.z.cursorPageX, this.cursorPage.z.cursorPageY, this.cursorPage.z.index);
|
|
500
|
+
if (this.state.nrrd_states.interaction.isCursorSelect && !this.state.cursorPage.y.updated) {
|
|
501
|
+
if (this.state.protectedData.axis === "z") {
|
|
502
|
+
convetObj = this.drawCore.convertCursorPoint("z", "y", this.state.cursorPage.z.cursorPageX, this.state.cursorPage.z.cursorPageY, this.state.cursorPage.z.index);
|
|
754
503
|
}
|
|
755
|
-
if (this.protectedData.axis === "x") {
|
|
756
|
-
|
|
757
|
-
convetObj = this.convertCursorPoint("x", "y", this.cursorPage.x.cursorPageX, this.cursorPage.x.cursorPageY, this.cursorPage.x.index);
|
|
504
|
+
if (this.state.protectedData.axis === "x") {
|
|
505
|
+
convetObj = this.drawCore.convertCursorPoint("x", "y", this.state.cursorPage.x.cursorPageX, this.state.cursorPage.x.cursorPageY, this.state.cursorPage.x.index);
|
|
758
506
|
}
|
|
759
507
|
}
|
|
760
508
|
else {
|
|
761
|
-
|
|
762
|
-
this.nrrd_states.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
this.nrrd_states.
|
|
766
|
-
this.nrrd_states.cursorPageY = this.cursorPage.y.cursorPageY;
|
|
509
|
+
this.state.nrrd_states.view.currentSliceIndex = this.state.cursorPage.y.index;
|
|
510
|
+
this.state.nrrd_states.view.preSliceIndex =
|
|
511
|
+
this.state.cursorPage.y.index * this.state.nrrd_states.image.ratios.y;
|
|
512
|
+
this.state.nrrd_states.interaction.cursorPageX = this.state.cursorPage.y.cursorPageX;
|
|
513
|
+
this.state.nrrd_states.interaction.cursorPageY = this.state.cursorPage.y.cursorPageY;
|
|
767
514
|
}
|
|
768
515
|
}
|
|
769
516
|
if (convetObj) {
|
|
770
|
-
|
|
771
|
-
this.nrrd_states.
|
|
772
|
-
this.nrrd_states.
|
|
773
|
-
this.nrrd_states.
|
|
774
|
-
this.nrrd_states.cursorPageY = convetObj.convertCursorNumY;
|
|
517
|
+
this.state.nrrd_states.view.currentSliceIndex = convetObj.currentNewSliceIndex;
|
|
518
|
+
this.state.nrrd_states.view.preSliceIndex = convetObj.preSliceIndex;
|
|
519
|
+
this.state.nrrd_states.interaction.cursorPageX = convetObj.convertCursorNumX;
|
|
520
|
+
this.state.nrrd_states.interaction.cursorPageY = convetObj.convertCursorNumY;
|
|
775
521
|
convetObj = undefined;
|
|
776
522
|
switch (axisTo) {
|
|
777
523
|
case "x":
|
|
778
|
-
this.cursorPage.x.updated = true;
|
|
524
|
+
this.state.cursorPage.x.updated = true;
|
|
779
525
|
break;
|
|
780
526
|
case "y":
|
|
781
|
-
this.cursorPage.y.updated = true;
|
|
527
|
+
this.state.cursorPage.y.updated = true;
|
|
782
528
|
break;
|
|
783
529
|
case "z":
|
|
784
|
-
this.cursorPage.z.updated = true;
|
|
530
|
+
this.state.cursorPage.z.updated = true;
|
|
785
531
|
break;
|
|
786
532
|
}
|
|
787
533
|
}
|
|
788
|
-
if (this.cursorPage.x.updated &&
|
|
789
|
-
this.cursorPage.y.updated &&
|
|
790
|
-
this.cursorPage.z.updated) {
|
|
791
|
-
|
|
792
|
-
this.nrrd_states.isCursorSelect = false;
|
|
534
|
+
if (this.state.cursorPage.x.updated &&
|
|
535
|
+
this.state.cursorPage.y.updated &&
|
|
536
|
+
this.state.cursorPage.z.updated) {
|
|
537
|
+
this.state.nrrd_states.interaction.isCursorSelect = false;
|
|
793
538
|
}
|
|
794
539
|
}
|
|
795
|
-
this.protectedData.axis = axisTo;
|
|
796
|
-
this.resetDisplaySlicesStatus();
|
|
797
|
-
// for sphere plan a
|
|
798
|
-
if (this.gui_states.sphere && !this.nrrd_states.spherePlanB) {
|
|
799
|
-
this.drawSphere(this.nrrd_states.sphereOrigin[axisTo][0] * this.nrrd_states.sizeFoctor, this.nrrd_states.sphereOrigin[axisTo][1] * this.nrrd_states.sizeFoctor, this.nrrd_states.sphereRadius);
|
|
800
|
-
}
|
|
540
|
+
this.state.protectedData.axis = axisTo;
|
|
541
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
801
542
|
}
|
|
802
543
|
addSkip(index) {
|
|
803
|
-
this.protectedData.skipSlicesDic[index] =
|
|
804
|
-
this.protectedData.backUpDisplaySlices[index];
|
|
805
|
-
if (index >= this.protectedData.displaySlices.length) {
|
|
806
|
-
this.nrrd_states.contrastNum = this.protectedData.displaySlices.length;
|
|
544
|
+
this.state.protectedData.skipSlicesDic[index] =
|
|
545
|
+
this.state.protectedData.backUpDisplaySlices[index];
|
|
546
|
+
if (index >= this.state.protectedData.displaySlices.length) {
|
|
547
|
+
this.state.nrrd_states.view.contrastNum = this.state.protectedData.displaySlices.length;
|
|
807
548
|
}
|
|
808
549
|
else {
|
|
809
|
-
this.nrrd_states.contrastNum = index;
|
|
550
|
+
this.state.nrrd_states.view.contrastNum = index;
|
|
810
551
|
}
|
|
811
|
-
this.resetDisplaySlicesStatus();
|
|
552
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
812
553
|
}
|
|
813
554
|
removeSkip(index) {
|
|
814
|
-
this.protectedData.skipSlicesDic[index] = undefined;
|
|
815
|
-
this.nrrd_states.contrastNum = 0;
|
|
816
|
-
this.resetDisplaySlicesStatus();
|
|
817
|
-
}
|
|
818
|
-
clear() {
|
|
819
|
-
// To effectively reduce the js memory garbage
|
|
820
|
-
this.protectedData.allSlicesArray.length = 0;
|
|
821
|
-
this.protectedData.displaySlices.length = 0;
|
|
822
|
-
// Phase 6: Clear all undo/redo stacks
|
|
823
|
-
this.undoManager.clearAll();
|
|
824
|
-
// Phase 3: Reset MaskVolume storage to 1×1×1 placeholders
|
|
825
|
-
this.protectedData.maskData.volumes = this.nrrd_states.layers.reduce((acc, id) => {
|
|
826
|
-
acc[id] = new MaskVolume(1, 1, 1, 1);
|
|
827
|
-
return acc;
|
|
828
|
-
}, {});
|
|
829
|
-
// Invalidate reusable slice buffer
|
|
830
|
-
this.invalidateSliceBuffer();
|
|
831
|
-
this.clearDictionary(this.protectedData.skipSlicesDic);
|
|
832
|
-
// this.nrrd_states.previousPanelL = this.nrrd_states.previousPanelT = -99999;
|
|
833
|
-
this.protectedData.canvases.displayCanvas.style.left =
|
|
834
|
-
this.protectedData.canvases.drawingCanvas.style.left = "";
|
|
835
|
-
this.protectedData.canvases.displayCanvas.style.top =
|
|
836
|
-
this.protectedData.canvases.drawingCanvas.style.top = "";
|
|
837
|
-
this.protectedData.backUpDisplaySlices.length = 0;
|
|
838
|
-
this.protectedData.mainPreSlices = undefined;
|
|
839
|
-
this.protectedData.currentShowingSlice = undefined;
|
|
840
|
-
this.protectedData.previousDrawingImage =
|
|
841
|
-
this.protectedData.ctxes.emptyCtx.createImageData(1, 1);
|
|
842
|
-
this.initState = true;
|
|
843
|
-
this.protectedData.axis = "z";
|
|
844
|
-
this.nrrd_states.sizeFoctor = this.baseCanvasesSize;
|
|
845
|
-
this.gui_states.mainAreaSize = this.baseCanvasesSize;
|
|
846
|
-
this.resetLayerCanvas();
|
|
847
|
-
this.protectedData.canvases.drawingCanvas.width =
|
|
848
|
-
this.protectedData.canvases.drawingCanvas.width;
|
|
849
|
-
this.protectedData.canvases.displayCanvas.width =
|
|
850
|
-
this.protectedData.canvases.displayCanvas.width;
|
|
851
|
-
this.nrrd_states.tumourSphereOrigin = null;
|
|
852
|
-
this.nrrd_states.ribSphereOrigin = null;
|
|
853
|
-
this.nrrd_states.skinSphereOrigin = null;
|
|
854
|
-
this.nrrd_states.nippleSphereOrigin = null;
|
|
555
|
+
this.state.protectedData.skipSlicesDic[index] = undefined;
|
|
556
|
+
this.state.nrrd_states.view.contrastNum = 0;
|
|
557
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
855
558
|
}
|
|
856
559
|
setSliceMoving(step) {
|
|
857
|
-
if (this.protectedData.mainPreSlices) {
|
|
858
|
-
// Accumulate steps so no keydown events are lost
|
|
560
|
+
if (this.state.protectedData.mainPreSlices) {
|
|
859
561
|
this._pendingSliceStep += step;
|
|
860
|
-
// RAF throttle: render at most once per frame, but apply ALL accumulated steps
|
|
861
562
|
if (this._sliceRAFId !== null)
|
|
862
563
|
return;
|
|
863
564
|
this._sliceRAFId = requestAnimationFrame(() => {
|
|
864
565
|
this._sliceRAFId = null;
|
|
865
566
|
const totalStep = this._pendingSliceStep;
|
|
866
567
|
this._pendingSliceStep = 0;
|
|
867
|
-
this.protectedData.
|
|
568
|
+
this.state.protectedData.isDrawing = true;
|
|
868
569
|
this.setSyncsliceNum();
|
|
869
570
|
this.dragOperator.updateIndex(totalStep);
|
|
870
571
|
this.setIsDrawFalse(1000);
|
|
@@ -872,423 +573,233 @@ export class NrrdTools extends DrawToolCore {
|
|
|
872
573
|
}
|
|
873
574
|
}
|
|
874
575
|
setMainAreaSize(factor) {
|
|
875
|
-
this.nrrd_states.
|
|
876
|
-
if (this.nrrd_states.
|
|
877
|
-
this.nrrd_states.
|
|
576
|
+
this.state.nrrd_states.view.sizeFactor = factor;
|
|
577
|
+
if (this.state.nrrd_states.view.sizeFactor >= 8) {
|
|
578
|
+
this.state.nrrd_states.view.sizeFactor = 8;
|
|
878
579
|
}
|
|
879
|
-
else if (this.nrrd_states.
|
|
880
|
-
this.nrrd_states.
|
|
580
|
+
else if (this.state.nrrd_states.view.sizeFactor <= 1) {
|
|
581
|
+
this.state.nrrd_states.view.sizeFactor = 1;
|
|
881
582
|
}
|
|
882
|
-
this.resizePaintArea(this.nrrd_states.
|
|
583
|
+
this.resizePaintArea(this.state.nrrd_states.view.sizeFactor);
|
|
883
584
|
this.resetPaintAreaUIPosition();
|
|
884
|
-
|
|
585
|
+
}
|
|
586
|
+
switchAllSlicesArrayData(allSlices) {
|
|
587
|
+
this.state.protectedData.allSlicesArray.length = 0;
|
|
588
|
+
this.state.protectedData.allSlicesArray = [...allSlices];
|
|
589
|
+
this.sliceRenderPipeline.resetDisplaySlicesStatus();
|
|
590
|
+
}
|
|
591
|
+
appendLoadingbar(loadingbar) {
|
|
592
|
+
this.state.protectedData.mainAreaContainer.appendChild(loadingbar);
|
|
593
|
+
}
|
|
594
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
595
|
+
// 6. Public API — Data Getters
|
|
596
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
597
|
+
getCurrentImageDimension() {
|
|
598
|
+
return this.state.nrrd_states.image.dimensions;
|
|
599
|
+
}
|
|
600
|
+
getVoxelSpacing() {
|
|
601
|
+
return this.state.nrrd_states.image.voxelSpacing;
|
|
602
|
+
}
|
|
603
|
+
getSpaceOrigin() {
|
|
604
|
+
return this.state.nrrd_states.image.spaceOrigin;
|
|
605
|
+
}
|
|
606
|
+
getMaskData() {
|
|
607
|
+
return this.state.protectedData.maskData;
|
|
885
608
|
}
|
|
886
609
|
getContainer() {
|
|
887
|
-
return this.protectedData.mainAreaContainer;
|
|
610
|
+
return this.state.protectedData.mainAreaContainer;
|
|
888
611
|
}
|
|
889
612
|
getDrawingCanvas() {
|
|
890
|
-
return this.protectedData.canvases.drawingCanvas;
|
|
613
|
+
return this.state.protectedData.canvases.drawingCanvas;
|
|
891
614
|
}
|
|
892
615
|
getNrrdToolsSettings() {
|
|
893
|
-
return this.nrrd_states;
|
|
616
|
+
return this.state.nrrd_states;
|
|
894
617
|
}
|
|
895
618
|
getMaxSliceNum() {
|
|
896
|
-
if (this.nrrd_states.showContrast) {
|
|
619
|
+
if (this.state.nrrd_states.view.showContrast) {
|
|
897
620
|
return [
|
|
898
|
-
this.nrrd_states.maxIndex,
|
|
899
|
-
this.nrrd_states.maxIndex * this.protectedData.displaySlices.length,
|
|
621
|
+
this.state.nrrd_states.view.maxIndex,
|
|
622
|
+
this.state.nrrd_states.view.maxIndex * this.state.protectedData.displaySlices.length,
|
|
900
623
|
];
|
|
901
624
|
}
|
|
902
625
|
else {
|
|
903
|
-
return [this.nrrd_states.maxIndex];
|
|
626
|
+
return [this.state.nrrd_states.view.maxIndex];
|
|
904
627
|
}
|
|
905
628
|
}
|
|
906
629
|
getCurrentSlicesNumAndContrastNum() {
|
|
907
630
|
return {
|
|
908
|
-
|
|
909
|
-
contrastIndex: this.nrrd_states.contrastNum,
|
|
631
|
+
currentSliceIndex: this.state.nrrd_states.view.currentSliceIndex,
|
|
632
|
+
contrastIndex: this.state.nrrd_states.view.contrastNum,
|
|
910
633
|
};
|
|
911
634
|
}
|
|
912
635
|
getCurrentSliceIndex() {
|
|
913
|
-
return Math.ceil(this.protectedData.mainPreSlices.index / this.nrrd_states.RSARatio);
|
|
636
|
+
return Math.ceil(this.state.protectedData.mainPreSlices.index / this.state.nrrd_states.image.RSARatio);
|
|
914
637
|
}
|
|
915
638
|
getIsShowContrastState() {
|
|
916
|
-
return this.nrrd_states.showContrast;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
this.
|
|
968
|
-
|
|
969
|
-
this.
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
this.
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
this.compositeAllLayers();
|
|
988
|
-
}
|
|
989
|
-
setMainPreSlice() {
|
|
990
|
-
this.protectedData.mainPreSlices = this.protectedData.displaySlices[0];
|
|
991
|
-
if (this.protectedData.mainPreSlices) {
|
|
992
|
-
this.nrrd_states.RSARatio = this.protectedData.mainPreSlices.RSARatio;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
setOriginCanvasAndPre() {
|
|
996
|
-
if (this.protectedData.mainPreSlices) {
|
|
997
|
-
if (this.nrrd_states.oldIndex > this.nrrd_states.maxIndex)
|
|
998
|
-
this.nrrd_states.oldIndex = this.nrrd_states.maxIndex;
|
|
999
|
-
if (this.initState) {
|
|
1000
|
-
this.nrrd_states.oldIndex =
|
|
1001
|
-
this.protectedData.mainPreSlices.initIndex *
|
|
1002
|
-
this.nrrd_states.RSARatio;
|
|
1003
|
-
this.nrrd_states.currentIndex =
|
|
1004
|
-
this.protectedData.mainPreSlices.initIndex;
|
|
1005
|
-
}
|
|
1006
|
-
else {
|
|
1007
|
-
// !need to change
|
|
1008
|
-
// todo
|
|
1009
|
-
this.protectedData.mainPreSlices.index = this.nrrd_states.oldIndex;
|
|
1010
|
-
}
|
|
1011
|
-
this.protectedData.canvases.originCanvas =
|
|
1012
|
-
this.protectedData.mainPreSlices.canvas;
|
|
1013
|
-
this.updateOriginAndChangedWH();
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
afterLoadSlice() {
|
|
1017
|
-
this.setMainPreSlice();
|
|
1018
|
-
this.setOriginCanvasAndPre();
|
|
1019
|
-
this.protectedData.currentShowingSlice = this.protectedData.mainPreSlices;
|
|
1020
|
-
this.nrrd_states.oldIndex =
|
|
1021
|
-
this.protectedData.mainPreSlices.initIndex * this.nrrd_states.RSARatio;
|
|
1022
|
-
this.nrrd_states.currentIndex = this.protectedData.mainPreSlices.initIndex;
|
|
1023
|
-
// Phase 6: Reset undo/redo stacks on new dataset load
|
|
1024
|
-
this.undoManager.clearAll();
|
|
1025
|
-
// compute max index
|
|
1026
|
-
this.updateMaxIndex();
|
|
1027
|
-
this.dragOperator.updateShowNumDiv(this.nrrd_states.contrastNum);
|
|
1028
|
-
this.initState = false;
|
|
1029
|
-
}
|
|
1030
|
-
updateMaxIndex() {
|
|
1031
|
-
if (this.protectedData.mainPreSlices) {
|
|
1032
|
-
this.nrrd_states.maxIndex = this.protectedData.mainPreSlices.MaxIndex;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
/**
|
|
1036
|
-
* Update the original canvas size, allow set to threejs load one (pixel distance not the mm).
|
|
1037
|
-
* Then update the changedWidth and changedHeight based on the sizeFoctor.
|
|
1038
|
-
*/
|
|
1039
|
-
updateOriginAndChangedWH() {
|
|
1040
|
-
this.nrrd_states.originWidth =
|
|
1041
|
-
this.protectedData.canvases.originCanvas.width;
|
|
1042
|
-
this.nrrd_states.originHeight =
|
|
1043
|
-
this.protectedData.canvases.originCanvas.height;
|
|
1044
|
-
// Let resizePaintArea be the sole setter of changedWidth/changedHeight.
|
|
1045
|
-
// Setting them here would defeat the sizeChanged detection in resizePaintArea,
|
|
1046
|
-
// causing canvas elements to keep stale dimensions after axis switches.
|
|
1047
|
-
this.resizePaintArea(this.nrrd_states.sizeFoctor);
|
|
1048
|
-
this.resetPaintAreaUIPosition();
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Keep all contrast slice index to same.
|
|
1052
|
-
* Synchronize the slice indexes of all the contrasts so that they are consistent with the main slice's index.
|
|
1053
|
-
*/
|
|
1054
|
-
setSyncsliceNum() {
|
|
1055
|
-
this.protectedData.displaySlices.forEach((slice, index) => {
|
|
1056
|
-
if (index !== 0) {
|
|
1057
|
-
slice.index = this.protectedData.mainPreSlices.index;
|
|
1058
|
-
}
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
appendLoadingbar(loadingbar) {
|
|
1062
|
-
this.protectedData.mainAreaContainer.appendChild(loadingbar);
|
|
1063
|
-
}
|
|
1064
|
-
clearStoreImages() {
|
|
1065
|
-
// Phase 3 Task 3.1: Only clear the active layer's MaskVolume
|
|
1066
|
-
if (this.nrrd_states.dimensions.length === 3) {
|
|
1067
|
-
const [w, h, d] = this.nrrd_states.dimensions;
|
|
1068
|
-
const activeLayer = this.gui_states.layer;
|
|
1069
|
-
// Re-init only the active layer's MaskVolume
|
|
1070
|
-
this.protectedData.maskData.volumes[activeLayer] = new MaskVolume(w, h, d, 1);
|
|
1071
|
-
// Phase 6 Task 6.6: Clear undo/redo stack for this layer (volume too large to snapshot)
|
|
1072
|
-
this.undoManager.clearLayer(activeLayer);
|
|
1073
|
-
// Phase 3 Task 3.2: Notify external that this layer's volume was cleared
|
|
1074
|
-
this.nrrd_states.onClearLayerVolume(activeLayer);
|
|
639
|
+
return this.state.nrrd_states.view.showContrast;
|
|
640
|
+
}
|
|
641
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
642
|
+
// 7. Delegated — LayerChannelManager
|
|
643
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
644
|
+
setActiveLayer(layerId) { this.layerChannelManager.setActiveLayer(layerId); }
|
|
645
|
+
getActiveLayer() { return this.layerChannelManager.getActiveLayer(); }
|
|
646
|
+
setActiveChannel(channel) { this.layerChannelManager.setActiveChannel(channel); }
|
|
647
|
+
getActiveChannel() { return this.layerChannelManager.getActiveChannel(); }
|
|
648
|
+
setActiveSphereType(type) { this.layerChannelManager.setActiveSphereType(type); }
|
|
649
|
+
getActiveSphereType() { return this.layerChannelManager.getActiveSphereType(); }
|
|
650
|
+
setLayerVisible(layerId, visible) { this.layerChannelManager.setLayerVisible(layerId, visible); }
|
|
651
|
+
isLayerVisible(layerId) { return this.layerChannelManager.isLayerVisible(layerId); }
|
|
652
|
+
setChannelVisible(layerId, channel, visible) { this.layerChannelManager.setChannelVisible(layerId, channel, visible); }
|
|
653
|
+
isChannelVisible(layerId, channel) { return this.layerChannelManager.isChannelVisible(layerId, channel); }
|
|
654
|
+
getLayerVisibility() { return this.layerChannelManager.getLayerVisibility(); }
|
|
655
|
+
getChannelVisibility() { return this.layerChannelManager.getChannelVisibility(); }
|
|
656
|
+
hasLayerData(layerId) { return this.layerChannelManager.hasLayerData(layerId); }
|
|
657
|
+
setChannelColor(layerId, channel, color) { this.layerChannelManager.setChannelColor(layerId, channel, color); }
|
|
658
|
+
getChannelColor(layerId, channel) { return this.layerChannelManager.getChannelColor(layerId, channel); }
|
|
659
|
+
getChannelHexColor(layerId, channel) { return this.layerChannelManager.getChannelHexColor(layerId, channel); }
|
|
660
|
+
getChannelCssColor(layerId, channel) { return this.layerChannelManager.getChannelCssColor(layerId, channel); }
|
|
661
|
+
setChannelColors(layerId, colorMap) { this.layerChannelManager.setChannelColors(layerId, colorMap); }
|
|
662
|
+
setAllLayersChannelColor(channel, color) { this.layerChannelManager.setAllLayersChannelColor(channel, color); }
|
|
663
|
+
resetChannelColors(layerId, channel) { this.layerChannelManager.resetChannelColors(layerId, channel); }
|
|
664
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
665
|
+
// 8. Delegated — SliceRenderPipeline
|
|
666
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
667
|
+
updateOriginAndChangedWH() { this.sliceRenderPipeline.updateOriginAndChangedWH(); }
|
|
668
|
+
setSyncsliceNum() { this.sliceRenderPipeline.setSyncsliceNum(); }
|
|
669
|
+
resetPaintAreaUIPosition(l, t) { this.sliceRenderPipeline.resetPaintAreaUIPosition(l, t); }
|
|
670
|
+
resetLayerCanvas() { this.sliceRenderPipeline.resetLayerCanvas(); }
|
|
671
|
+
redrawMianPreOnDisplayCanvas() { this.sliceRenderPipeline.redrawMianPreOnDisplayCanvas(); }
|
|
672
|
+
resizePaintArea(factor) { this.sliceRenderPipeline.resizePaintArea(factor); }
|
|
673
|
+
flipDisplayImageByAxis() { this.sliceRenderPipeline.flipDisplayImageByAxis(); }
|
|
674
|
+
setEmptyCanvasSize(axis) { this.sliceRenderPipeline.setEmptyCanvasSize(axis); }
|
|
675
|
+
redrawDisplayCanvas() { this.sliceRenderPipeline.redrawDisplayCanvas(); }
|
|
676
|
+
reloadMasksFromVolume() { this.sliceRenderPipeline.reloadMasksFromVolume(); }
|
|
677
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
678
|
+
// 9. Delegated — DataLoader
|
|
679
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
680
|
+
setAllSlices(allSlices) { this.dataLoader.setAllSlices(allSlices); }
|
|
681
|
+
setMasksData(masksData, loadingBar) { this.dataLoader.setMasksData(masksData, loadingBar); }
|
|
682
|
+
setMasksFromNIfTI(layerVoxels, loadingBar) { this.dataLoader.setMasksFromNIfTI(layerVoxels, loadingBar); }
|
|
683
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
684
|
+
// 10. Sphere Orchestration
|
|
685
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
686
|
+
setCalculateDistanceSphere(x, y, sliceIndex, cal_position) {
|
|
687
|
+
this.state.nrrd_states.sphere.sphereRadius = 5;
|
|
688
|
+
const steps = sliceIndex - this.state.nrrd_states.view.currentSliceIndex;
|
|
689
|
+
this.setSliceMoving(steps * this.state.protectedData.displaySlices.length);
|
|
690
|
+
const mouseX = x * this.state.nrrd_states.view.sizeFactor;
|
|
691
|
+
const mouseY = y * this.state.nrrd_states.view.sizeFactor;
|
|
692
|
+
this.state.nrrd_states.sphere.sphereOrigin[this.state.protectedData.axis] = [
|
|
693
|
+
mouseX, mouseY, sliceIndex,
|
|
694
|
+
];
|
|
695
|
+
this.drawCore.crosshairTool.setUpSphereOrigins(mouseX, mouseY, sliceIndex);
|
|
696
|
+
const originCopy = JSON.parse(JSON.stringify(this.state.nrrd_states.sphere.sphereOrigin));
|
|
697
|
+
switch (cal_position) {
|
|
698
|
+
case "tumour":
|
|
699
|
+
this.state.nrrd_states.sphere.tumourSphereOrigin = originCopy;
|
|
700
|
+
break;
|
|
701
|
+
case "skin":
|
|
702
|
+
this.state.nrrd_states.sphere.skinSphereOrigin = originCopy;
|
|
703
|
+
break;
|
|
704
|
+
case "nipple":
|
|
705
|
+
this.state.nrrd_states.sphere.nippleSphereOrigin = originCopy;
|
|
706
|
+
break;
|
|
707
|
+
case "ribcage":
|
|
708
|
+
this.state.nrrd_states.sphere.ribSphereOrigin = originCopy;
|
|
709
|
+
break;
|
|
1075
710
|
}
|
|
1076
|
-
|
|
1077
|
-
this.
|
|
1078
|
-
|
|
1079
|
-
this.reloadMasksFromVolume();
|
|
711
|
+
this.drawCore.drawCalculatorSphere(this.state.nrrd_states.sphere.sphereRadius);
|
|
712
|
+
this.drawCore.sphereTool.writeAllCalculatorSpheresToVolume();
|
|
713
|
+
this.drawCore.sphereTool.refreshSphereCanvas();
|
|
1080
714
|
}
|
|
1081
715
|
/**
|
|
1082
|
-
*
|
|
1083
|
-
* If no params in, then center the draw and display canvases.
|
|
1084
|
-
* @param l number, Offset to the left
|
|
1085
|
-
* @param t number, Offset to the top
|
|
716
|
+
* Refresh sphere canvas from sphereMaskVolume for the current slice/axis.
|
|
1086
717
|
*/
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
this.protectedData.canvases.displayCanvas.style.left =
|
|
1090
|
-
this.protectedData.canvases.drawingCanvas.style.left = l + "px";
|
|
1091
|
-
this.protectedData.canvases.displayCanvas.style.top =
|
|
1092
|
-
this.protectedData.canvases.drawingCanvas.style.top = t + "px";
|
|
1093
|
-
}
|
|
1094
|
-
else {
|
|
1095
|
-
this.protectedData.canvases.displayCanvas.style.left =
|
|
1096
|
-
this.protectedData.canvases.drawingCanvas.style.left = "";
|
|
1097
|
-
this.protectedData.canvases.displayCanvas.style.top =
|
|
1098
|
-
this.protectedData.canvases.drawingCanvas.style.top = "";
|
|
1099
|
-
this.protectedData.mainAreaContainer.style.justifyContent = "center";
|
|
1100
|
-
this.protectedData.mainAreaContainer.style.alignItems = "center";
|
|
1101
|
-
}
|
|
718
|
+
refreshSphereOverlay() {
|
|
719
|
+
this.drawCore.refreshSphereOverlay();
|
|
1102
720
|
}
|
|
1103
721
|
/**
|
|
1104
|
-
*
|
|
722
|
+
* Enter sphere mode.
|
|
1105
723
|
*/
|
|
1106
|
-
|
|
1107
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.width =
|
|
1108
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.width;
|
|
1109
|
-
for (const [, target] of this.protectedData.layerTargets) {
|
|
1110
|
-
target.canvas.width = target.canvas.width;
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
redrawMianPreOnDisplayCanvas() {
|
|
724
|
+
enterSphereMode() {
|
|
1114
725
|
var _a;
|
|
1115
|
-
this.
|
|
1116
|
-
|
|
1117
|
-
this.
|
|
1118
|
-
|
|
1119
|
-
this.
|
|
1120
|
-
this.protectedData.
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
this.flipDisplayImageByAxis();
|
|
1124
|
-
(_a = this.protectedData.ctxes.displayCtx) === null || _a === void 0 ? void 0 : _a.drawImage(this.protectedData.canvases.originCanvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
1125
|
-
this.resizePaintArea(this.nrrd_states.sizeFoctor);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* Resize the draw and display canvas size based on the input size factor number.
|
|
1130
|
-
* @param factor number
|
|
1131
|
-
*/
|
|
1132
|
-
resizePaintArea(factor) {
|
|
1133
|
-
const newWidth = Math.floor(this.nrrd_states.originWidth * factor);
|
|
1134
|
-
const newHeight = Math.floor(this.nrrd_states.originHeight * factor);
|
|
1135
|
-
const sizeChanged = newWidth !== this.nrrd_states.changedWidth ||
|
|
1136
|
-
newHeight !== this.nrrd_states.changedHeight;
|
|
1137
|
-
// Always clear display/drawing/origin canvases (needed for contrast updates)
|
|
1138
|
-
this.protectedData.canvases.originCanvas.width =
|
|
1139
|
-
this.protectedData.canvases.originCanvas.width;
|
|
1140
|
-
this.protectedData.canvases.displayCanvas.width =
|
|
1141
|
-
this.protectedData.canvases.displayCanvas.width;
|
|
1142
|
-
this.protectedData.canvases.drawingCanvas.width =
|
|
1143
|
-
this.protectedData.canvases.drawingCanvas.width;
|
|
1144
|
-
if (sizeChanged) {
|
|
1145
|
-
// Only clear and resize layer canvases when size actually changes.
|
|
1146
|
-
// Skipping this avoids the expensive reloadMasksFromVolume() call
|
|
1147
|
-
// during contrast toggle (where size stays the same).
|
|
1148
|
-
this.resetLayerCanvas();
|
|
1149
|
-
this.nrrd_states.changedWidth = newWidth;
|
|
1150
|
-
this.nrrd_states.changedHeight = newHeight;
|
|
1151
|
-
this.protectedData.canvases.displayCanvas.width = newWidth;
|
|
1152
|
-
this.protectedData.canvases.displayCanvas.height = newHeight;
|
|
1153
|
-
this.protectedData.canvases.drawingCanvas.width = newWidth;
|
|
1154
|
-
this.protectedData.canvases.drawingCanvas.height = newHeight;
|
|
1155
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.width = newWidth;
|
|
1156
|
-
this.protectedData.canvases.drawingCanvasLayerMaster.height = newHeight;
|
|
1157
|
-
for (const [, target] of this.protectedData.layerTargets) {
|
|
1158
|
-
target.canvas.width = newWidth;
|
|
1159
|
-
target.canvas.height = newHeight;
|
|
726
|
+
this.dragOperator.removeDragMode();
|
|
727
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setGuiTool('sphere');
|
|
728
|
+
const w = this.state.nrrd_states.view.changedWidth;
|
|
729
|
+
const h = this.state.nrrd_states.view.changedHeight;
|
|
730
|
+
for (const layerId of this.state.nrrd_states.image.layers) {
|
|
731
|
+
const target = this.state.protectedData.layerTargets.get(layerId);
|
|
732
|
+
if (target) {
|
|
733
|
+
target.ctx.clearRect(0, 0, target.canvas.width, target.canvas.height);
|
|
1160
734
|
}
|
|
1161
735
|
}
|
|
1162
|
-
this.
|
|
1163
|
-
|
|
1164
|
-
// Phase 3: Reload masks from MaskVolume only when canvas size changed
|
|
1165
|
-
this.reloadMasksFromVolume();
|
|
1166
|
-
}
|
|
1167
|
-
else {
|
|
1168
|
-
// Size unchanged (e.g. contrast toggle): layer canvases still have
|
|
1169
|
-
// valid data, just recomposite to master for the start() render loop.
|
|
1170
|
-
this.compositeAllLayers();
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
/**
|
|
1174
|
-
* Phase 3: Reload all mask layers from MaskVolume using buffer reuse
|
|
1175
|
-
* Replaces the old reloadMaskToLayer approach
|
|
1176
|
-
*/
|
|
1177
|
-
reloadMasksFromVolume() {
|
|
1178
|
-
const axis = this.protectedData.axis;
|
|
1179
|
-
let sliceIndex = this.nrrd_states.currentIndex;
|
|
1180
|
-
// Clamp sliceIndex to valid range for current axis
|
|
1181
|
-
// (currentIndex may not be updated yet when switching axes)
|
|
1182
|
-
try {
|
|
1183
|
-
const vol = this.getVolumeForLayer(this.nrrd_states.layers[0]);
|
|
1184
|
-
const dims = vol.getDimensions();
|
|
1185
|
-
const maxSlice = axis === "x" ? dims.width : axis === "y" ? dims.height : dims.depth;
|
|
1186
|
-
if (sliceIndex >= maxSlice)
|
|
1187
|
-
sliceIndex = maxSlice - 1;
|
|
1188
|
-
if (sliceIndex < 0)
|
|
1189
|
-
sliceIndex = 0;
|
|
1190
|
-
}
|
|
1191
|
-
catch ( /* volume not ready */_a) { /* volume not ready */ }
|
|
1192
|
-
// Get a single reusable buffer shared across all layer renders
|
|
1193
|
-
const buffer = this.getOrCreateSliceBuffer(axis);
|
|
1194
|
-
if (!buffer)
|
|
1195
|
-
return;
|
|
1196
|
-
const w = this.nrrd_states.changedWidth;
|
|
1197
|
-
const h = this.nrrd_states.changedHeight;
|
|
1198
|
-
// Clear and render each layer using the shared buffer
|
|
1199
|
-
for (const layerId of this.nrrd_states.layers) {
|
|
1200
|
-
const target = this.protectedData.layerTargets.get(layerId);
|
|
1201
|
-
if (!target)
|
|
1202
|
-
continue;
|
|
1203
|
-
target.ctx.clearRect(0, 0, w, h);
|
|
1204
|
-
this.renderSliceToCanvas(layerId, axis, sliceIndex, buffer, target.ctx, w, h);
|
|
1205
|
-
}
|
|
1206
|
-
// Composite all layers to master canvas
|
|
1207
|
-
this.compositeAllLayers();
|
|
736
|
+
this.state.protectedData.ctxes.drawingLayerMasterCtx.clearRect(0, 0, w, h);
|
|
737
|
+
this.refreshSphereOverlay();
|
|
1208
738
|
}
|
|
1209
|
-
// compositeAllLayers() is inherited from CommToolsData
|
|
1210
739
|
/**
|
|
1211
|
-
*
|
|
1212
|
-
* This is because the slice canvas from threejs is not in a correct 2D postion.
|
|
1213
|
-
* Thus, everytime when we redraw the display canvas, we need to flip to draw the origin canvas from threejs.
|
|
1214
|
-
* Under different axis(sagittal, Axial, Coronal), the flip orientation is different.
|
|
740
|
+
* Exit sphere mode.
|
|
1215
741
|
*/
|
|
1216
|
-
|
|
1217
|
-
var _a
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
(_c = this.protectedData.ctxes.displayCtx) === null || _c === void 0 ? void 0 : _c.scale(1, -1);
|
|
1224
|
-
(_d = this.protectedData.ctxes.displayCtx) === null || _d === void 0 ? void 0 : _d.translate(0, -this.nrrd_states.changedHeight);
|
|
1225
|
-
}
|
|
1226
|
-
else if (this.protectedData.axis === "y") {
|
|
1227
|
-
(_e = this.protectedData.ctxes.displayCtx) === null || _e === void 0 ? void 0 : _e.scale(1, -1);
|
|
1228
|
-
(_f = this.protectedData.ctxes.displayCtx) === null || _f === void 0 ? void 0 : _f.translate(0, -this.nrrd_states.changedHeight);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
clearDictionary(dic) {
|
|
1232
|
-
for (var key in dic) {
|
|
1233
|
-
delete dic[key];
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
/**
|
|
1237
|
-
* Set the empty canvas width and height based on the axis (pixel distance not the mm), to reduce duplicate codes.
|
|
1238
|
-
*
|
|
1239
|
-
* @param axis
|
|
1240
|
-
*/
|
|
1241
|
-
setEmptyCanvasSize(axis) {
|
|
1242
|
-
switch (!!axis ? axis : this.protectedData.axis) {
|
|
1243
|
-
case "x":
|
|
1244
|
-
this.protectedData.canvases.emptyCanvas.width =
|
|
1245
|
-
this.nrrd_states.nrrd_z_pixel;
|
|
1246
|
-
this.protectedData.canvases.emptyCanvas.height =
|
|
1247
|
-
this.nrrd_states.nrrd_y_pixel;
|
|
1248
|
-
break;
|
|
1249
|
-
case "y":
|
|
1250
|
-
this.protectedData.canvases.emptyCanvas.width =
|
|
1251
|
-
this.nrrd_states.nrrd_x_pixel;
|
|
1252
|
-
this.protectedData.canvases.emptyCanvas.height =
|
|
1253
|
-
this.nrrd_states.nrrd_z_pixel;
|
|
1254
|
-
break;
|
|
1255
|
-
case "z":
|
|
1256
|
-
this.protectedData.canvases.emptyCanvas.width =
|
|
1257
|
-
this.nrrd_states.nrrd_x_pixel;
|
|
1258
|
-
this.protectedData.canvases.emptyCanvas.height =
|
|
1259
|
-
this.nrrd_states.nrrd_y_pixel;
|
|
1260
|
-
break;
|
|
1261
|
-
}
|
|
742
|
+
exitSphereMode() {
|
|
743
|
+
var _a;
|
|
744
|
+
this.dragOperator.configDragMode();
|
|
745
|
+
(_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.setGuiTool('pencil');
|
|
746
|
+
this.state.protectedData.ctxes.drawingSphereCtx.clearRect(0, 0, this.state.protectedData.canvases.drawingSphereCanvas.width, this.state.protectedData.canvases.drawingSphereCanvas.height);
|
|
747
|
+
this.resetLayerCanvas();
|
|
748
|
+
this.reloadMasksFromVolume();
|
|
1262
749
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
this.
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
this.
|
|
1276
|
-
this.protectedData.canvases.originCanvas.width;
|
|
1277
|
-
if (this.protectedData.currentShowingSlice) {
|
|
1278
|
-
this.protectedData.currentShowingSlice.repaint.call(this.protectedData.currentShowingSlice);
|
|
1279
|
-
(_a = this.protectedData.ctxes.displayCtx) === null || _a === void 0 ? void 0 : _a.save();
|
|
1280
|
-
this.flipDisplayImageByAxis();
|
|
1281
|
-
(_b = this.protectedData.ctxes.displayCtx) === null || _b === void 0 ? void 0 : _b.drawImage(this.protectedData.currentShowingSlice.canvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
|
|
1282
|
-
(_c = this.protectedData.ctxes.displayCtx) === null || _c === void 0 ? void 0 : _c.restore();
|
|
1283
|
-
}
|
|
750
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
751
|
+
// 11. Clear / Reset
|
|
752
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
753
|
+
clearActiveLayer() {
|
|
754
|
+
if (this.state.nrrd_states.image.dimensions.length === 3) {
|
|
755
|
+
const [w, h, d] = this.state.nrrd_states.image.dimensions;
|
|
756
|
+
const activeLayer = this.state.gui_states.layerChannel.layer;
|
|
757
|
+
this.state.protectedData.maskData.volumes[activeLayer] = new MaskVolume(w, h, d, 1);
|
|
758
|
+
this.drawCore.undoManager.clearLayer(activeLayer);
|
|
759
|
+
this.state.annotationCallbacks.onLayerVolumeCleared(activeLayer);
|
|
760
|
+
}
|
|
761
|
+
this.drawCore.renderer.invalidateSliceBuffer();
|
|
762
|
+
this.reloadMasksFromVolume();
|
|
1284
763
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
764
|
+
reset() {
|
|
765
|
+
this.state.protectedData.allSlicesArray.length = 0;
|
|
766
|
+
this.state.protectedData.displaySlices.length = 0;
|
|
767
|
+
this.drawCore.undoManager.clearAll();
|
|
768
|
+
this.state.protectedData.maskData.volumes = this.state.nrrd_states.image.layers.reduce((acc, id) => {
|
|
769
|
+
acc[id] = new MaskVolume(1, 1, 1, 1);
|
|
770
|
+
return acc;
|
|
771
|
+
}, {});
|
|
772
|
+
this.state.nrrd_states.sphere.sphereMaskVolume = null;
|
|
773
|
+
this.drawCore.renderer.invalidateSliceBuffer();
|
|
774
|
+
this.clearDictionary(this.state.protectedData.skipSlicesDic);
|
|
775
|
+
this.state.protectedData.canvases.displayCanvas.style.left =
|
|
776
|
+
this.state.protectedData.canvases.drawingCanvas.style.left = "";
|
|
777
|
+
this.state.protectedData.canvases.displayCanvas.style.top =
|
|
778
|
+
this.state.protectedData.canvases.drawingCanvas.style.top = "";
|
|
779
|
+
this.state.protectedData.backUpDisplaySlices.length = 0;
|
|
780
|
+
this.state.protectedData.mainPreSlices = undefined;
|
|
781
|
+
this.state.protectedData.currentShowingSlice = undefined;
|
|
782
|
+
this.sliceRenderPipeline.resetInitState();
|
|
783
|
+
this.state.protectedData.axis = "z";
|
|
784
|
+
this.state.nrrd_states.view.sizeFactor = this.state.baseCanvasesSize;
|
|
785
|
+
this.state.gui_states.viewConfig.mainAreaSize = this.state.baseCanvasesSize;
|
|
786
|
+
this.resetLayerCanvas();
|
|
787
|
+
this.state.protectedData.canvases.drawingCanvas.width =
|
|
788
|
+
this.state.protectedData.canvases.drawingCanvas.width;
|
|
789
|
+
this.state.protectedData.canvases.displayCanvas.width =
|
|
790
|
+
this.state.protectedData.canvases.displayCanvas.width;
|
|
791
|
+
this.state.nrrd_states.sphere.tumourSphereOrigin = null;
|
|
792
|
+
this.state.nrrd_states.sphere.ribSphereOrigin = null;
|
|
793
|
+
this.state.nrrd_states.sphere.skinSphereOrigin = null;
|
|
794
|
+
this.state.nrrd_states.sphere.nippleSphereOrigin = null;
|
|
795
|
+
}
|
|
796
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
797
|
+
// 12. Internal — Input Events
|
|
798
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1288
799
|
configMouseSliceWheel() {
|
|
1289
800
|
const handleMouseZoomSliceWheelMove = (e) => {
|
|
1290
801
|
var _a;
|
|
1291
|
-
if ((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
|
|
802
|
+
if ((_a = this.drawCore.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
|
|
1292
803
|
return;
|
|
1293
804
|
}
|
|
1294
805
|
e.preventDefault();
|
|
@@ -1301,23 +812,53 @@ export class NrrdTools extends DrawToolCore {
|
|
|
1301
812
|
};
|
|
1302
813
|
return handleMouseZoomSliceWheelMove;
|
|
1303
814
|
}
|
|
1304
|
-
/**
|
|
1305
|
-
* Update mouse wheel event.
|
|
1306
|
-
*/
|
|
1307
815
|
updateMouseWheelEvent() {
|
|
1308
|
-
this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
1309
|
-
switch (this.
|
|
816
|
+
this.state.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawCore.drawingPrameters.handleMouseZoomSliceWheel);
|
|
817
|
+
switch (this.state.keyboardSettings.mouseWheel) {
|
|
1310
818
|
case "Scroll:Zoom":
|
|
1311
|
-
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseZoomWheel();
|
|
819
|
+
this.drawCore.drawingPrameters.handleMouseZoomSliceWheel = this.drawCore.configMouseZoomWheel();
|
|
1312
820
|
break;
|
|
1313
821
|
case "Scroll:Slice":
|
|
1314
|
-
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseSliceWheel();
|
|
822
|
+
this.drawCore.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseSliceWheel();
|
|
1315
823
|
break;
|
|
1316
824
|
default:
|
|
1317
|
-
this.drawingPrameters.handleMouseZoomSliceWheel = this.configMouseZoomWheel();
|
|
825
|
+
this.drawCore.drawingPrameters.handleMouseZoomSliceWheel = this.drawCore.configMouseZoomWheel();
|
|
1318
826
|
break;
|
|
1319
827
|
}
|
|
1320
|
-
this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
|
|
828
|
+
this.state.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawCore.drawingPrameters.handleMouseZoomSliceWheel);
|
|
829
|
+
}
|
|
830
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
831
|
+
// 13. Internal — Utility
|
|
832
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
833
|
+
setIsDrawFalse(target) {
|
|
834
|
+
this.preTimer = setTimeout(() => {
|
|
835
|
+
this.state.protectedData.isDrawing = false;
|
|
836
|
+
if (this.preTimer) {
|
|
837
|
+
window.clearTimeout(this.preTimer);
|
|
838
|
+
this.preTimer = undefined;
|
|
839
|
+
}
|
|
840
|
+
}, target);
|
|
841
|
+
}
|
|
842
|
+
setShowInMainArea() {
|
|
843
|
+
this.state.nrrd_states.view.showContrast = true;
|
|
844
|
+
}
|
|
845
|
+
clearDictionary(dic) {
|
|
846
|
+
for (var key in dic) {
|
|
847
|
+
delete dic[key];
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Get the DrawToolCore's start() render loop callback.
|
|
852
|
+
* This is called by DragSliceTool's requestAnimationFrame loop.
|
|
853
|
+
*/
|
|
854
|
+
get start() {
|
|
855
|
+
return this.drawCore.start;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Expose drawCalculatorSphereOnEachViews for external use.
|
|
859
|
+
*/
|
|
860
|
+
drawCalculatorSphereOnEachViews(axis) {
|
|
861
|
+
this.drawCore.drawCalculatorSphereOnEachViews(axis);
|
|
1321
862
|
}
|
|
1322
863
|
}
|
|
1323
864
|
//# sourceMappingURL=NrrdTools.js.map
|