copper3d 2.2.3 → 3.0.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 +1056 -5
- package/dist/Scene/copperScene.d.ts +3 -0
- package/dist/Scene/copperScene.js +13 -0
- package/dist/Scene/copperScene.js.map +1 -1
- package/dist/Utils/segmentation/CommToolsData.d.ts +109 -6
- package/dist/Utils/segmentation/CommToolsData.js +314 -50
- package/dist/Utils/segmentation/CommToolsData.js.map +1 -1
- package/dist/Utils/segmentation/DragOperator.d.ts +13 -7
- package/dist/Utils/segmentation/DragOperator.js +51 -165
- package/dist/Utils/segmentation/DragOperator.js.map +1 -1
- package/dist/Utils/segmentation/DrawToolCore.d.ts +57 -76
- package/dist/Utils/segmentation/DrawToolCore.js +368 -878
- package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
- package/dist/Utils/segmentation/NrrdTools.d.ts +277 -15
- package/dist/Utils/segmentation/NrrdTools.js +594 -232
- package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
- package/dist/Utils/segmentation/core/MaskVolume.d.ts +477 -0
- package/dist/Utils/segmentation/core/MaskVolume.js +1238 -0
- package/dist/Utils/segmentation/core/MaskVolume.js.map +1 -0
- package/dist/Utils/segmentation/core/MigrationUtils.d.ts +97 -0
- package/dist/Utils/segmentation/core/MigrationUtils.js +163 -0
- package/dist/Utils/segmentation/core/MigrationUtils.js.map +1 -0
- package/dist/Utils/segmentation/core/UndoManager.d.ts +47 -0
- package/dist/Utils/segmentation/core/UndoManager.js +86 -0
- package/dist/Utils/segmentation/core/UndoManager.js.map +1 -0
- package/dist/Utils/segmentation/core/index.d.ts +76 -0
- package/dist/Utils/segmentation/core/index.js +75 -0
- package/dist/Utils/segmentation/core/index.js.map +1 -0
- package/dist/Utils/segmentation/core/types.d.ts +106 -0
- package/dist/Utils/segmentation/core/types.js +98 -0
- package/dist/Utils/segmentation/core/types.js.map +1 -0
- package/dist/Utils/segmentation/coreTools/coreType.d.ts +55 -36
- package/dist/Utils/segmentation/coreTools/coreType.js.map +1 -1
- package/dist/Utils/segmentation/coreTools/gui.d.ts +6 -16
- package/dist/Utils/segmentation/coreTools/gui.js +23 -27
- package/dist/Utils/segmentation/coreTools/gui.js.map +1 -1
- package/dist/Utils/segmentation/eventRouter/EventRouter.d.ts +144 -0
- package/dist/Utils/segmentation/eventRouter/EventRouter.js +369 -0
- package/dist/Utils/segmentation/eventRouter/EventRouter.js.map +1 -0
- package/dist/Utils/segmentation/eventRouter/index.d.ts +7 -0
- package/dist/Utils/segmentation/eventRouter/index.js +7 -0
- package/dist/Utils/segmentation/eventRouter/index.js.map +1 -0
- package/dist/Utils/segmentation/eventRouter/types.d.ts +70 -0
- package/dist/Utils/segmentation/eventRouter/types.js +7 -0
- package/dist/Utils/segmentation/eventRouter/types.js.map +1 -0
- package/dist/Utils/segmentation/tools/BaseTool.d.ts +25 -0
- package/dist/Utils/segmentation/tools/BaseTool.js +18 -0
- package/dist/Utils/segmentation/tools/BaseTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/ContrastTool.d.ts +26 -0
- package/dist/Utils/segmentation/tools/ContrastTool.js +118 -0
- package/dist/Utils/segmentation/tools/ContrastTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/CrosshairTool.d.ts +30 -0
- package/dist/Utils/segmentation/tools/CrosshairTool.js +157 -0
- package/dist/Utils/segmentation/tools/CrosshairTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/DragSliceTool.d.ts +44 -0
- package/dist/Utils/segmentation/tools/DragSliceTool.js +170 -0
- package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/EraserTool.d.ts +16 -0
- package/dist/Utils/segmentation/tools/EraserTool.js +92 -0
- package/dist/Utils/segmentation/tools/EraserTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +65 -0
- package/dist/Utils/segmentation/tools/ImageStoreHelper.js +174 -0
- package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -0
- package/dist/Utils/segmentation/tools/SphereTool.d.ts +38 -0
- package/dist/Utils/segmentation/tools/SphereTool.js +193 -0
- package/dist/Utils/segmentation/tools/SphereTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/ZoomTool.d.ts +20 -0
- package/dist/Utils/segmentation/tools/ZoomTool.js +59 -0
- package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -0
- package/dist/Utils/segmentation/tools/index.d.ts +22 -0
- package/dist/Utils/segmentation/tools/index.js +23 -0
- package/dist/Utils/segmentation/tools/index.js.map +1 -0
- package/dist/bundle.esm.js +4740 -1979
- package/dist/bundle.umd.js +4743 -1978
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/types/Renderer/copperRenderer.d.ts +1 -1
- package/dist/types/Renderer/copperRendererOnDemond.d.ts +1 -1
- package/dist/types/Scene/copperScene.d.ts +3 -0
- package/dist/types/Utils/segmentation/CommToolsData.d.ts +109 -6
- package/dist/types/Utils/segmentation/DragOperator.d.ts +13 -7
- package/dist/types/Utils/segmentation/DrawToolCore.d.ts +57 -76
- package/dist/types/Utils/segmentation/NrrdTools.d.ts +277 -15
- package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +477 -0
- package/dist/types/Utils/segmentation/core/MigrationUtils.d.ts +97 -0
- package/dist/types/Utils/segmentation/core/UndoManager.d.ts +47 -0
- package/dist/types/Utils/segmentation/core/index.d.ts +76 -0
- package/dist/types/Utils/segmentation/core/types.d.ts +106 -0
- package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +55 -36
- package/dist/types/Utils/segmentation/coreTools/gui.d.ts +6 -16
- package/dist/types/Utils/segmentation/eventRouter/EventRouter.d.ts +144 -0
- package/dist/types/Utils/segmentation/eventRouter/index.d.ts +7 -0
- package/dist/types/Utils/segmentation/eventRouter/types.d.ts +70 -0
- package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +25 -0
- package/dist/types/Utils/segmentation/tools/ContrastTool.d.ts +26 -0
- package/dist/types/Utils/segmentation/tools/CrosshairTool.d.ts +30 -0
- package/dist/types/Utils/segmentation/tools/DragSliceTool.d.ts +44 -0
- package/dist/types/Utils/segmentation/tools/EraserTool.d.ts +16 -0
- package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +65 -0
- package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +38 -0
- package/dist/types/Utils/segmentation/tools/ZoomTool.d.ts +20 -0
- package/dist/types/Utils/segmentation/tools/index.d.ts +22 -0
- package/dist/types/index.d.ts +5 -3
- package/dist/types/types/types.d.ts +3 -3
- package/dist/types/types.d.ts +3 -3
- package/package.json +1 -1
- package/dist/Utils/segmentation/coreTools/archive.d.ts +0 -1
- package/dist/Utils/segmentation/coreTools/archive.js +0 -23
- package/dist/Utils/segmentation/coreTools/archive.js.map +0 -1
- package/dist/types/Utils/segmentation/coreTools/archive.d.ts +0 -1
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MaskVolume — True 3D Volumetric Mask Storage
|
|
3
|
+
*
|
|
4
|
+
* Stores annotation masks in a single contiguous Uint8Array with the memory
|
|
5
|
+
* layout [z][y][x][channel] (slice-major order).
|
|
6
|
+
*
|
|
7
|
+
* Design goals:
|
|
8
|
+
* - Contiguous memory for optimal cache locality and minimal GC pressure.
|
|
9
|
+
* - Multi-channel support (binary mask, confidence, labels, etc.).
|
|
10
|
+
* - O(1) voxel access with full bounds checking.
|
|
11
|
+
* - Slice extraction to ImageData for Canvas rendering (all 3 axes).
|
|
12
|
+
* - Multi-channel color mapping with 4 render modes.
|
|
13
|
+
*
|
|
14
|
+
* Memory comparison (512 × 512 × 100, 1 channel):
|
|
15
|
+
* Old ImageData approach ≈ 4.4 GB → MaskVolume ≈ 25 MB (~99 % reduction)
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const vol = new MaskVolume(512, 512, 100); // 1 channel (default)
|
|
20
|
+
* vol.setVoxel(256, 256, 50, 255); // paint centre voxel
|
|
21
|
+
*
|
|
22
|
+
* // Grayscale slice (backward compatible)
|
|
23
|
+
* const slice = vol.getSliceImageData(50, 'z');
|
|
24
|
+
*
|
|
25
|
+
* // Colored slice
|
|
26
|
+
* const colored = vol.getSliceImageData(50, 'z', {
|
|
27
|
+
* mode: RenderMode.COLORED_SINGLE,
|
|
28
|
+
* channel: 0,
|
|
29
|
+
* opacity: 0.6,
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
import type { Dimensions, RGBAColor, ChannelColorMap, SliceRenderOptions } from './types';
|
|
34
|
+
export declare class MaskVolume {
|
|
35
|
+
/** Contiguous backing buffer — layout [z][y][x][channel]. */
|
|
36
|
+
private data;
|
|
37
|
+
/** Volume size in voxels. */
|
|
38
|
+
private readonly dims;
|
|
39
|
+
/** Number of value channels per voxel (≥ 1). */
|
|
40
|
+
private readonly numChannels;
|
|
41
|
+
/**
|
|
42
|
+
* Number of bytes in one complete z-slice.
|
|
43
|
+
* Equal to width × height × channels.
|
|
44
|
+
*/
|
|
45
|
+
private readonly bytesPerSlice;
|
|
46
|
+
/** Per-channel color map used for colored rendering modes. */
|
|
47
|
+
private colorMap;
|
|
48
|
+
/**
|
|
49
|
+
* Create a new MaskVolume.
|
|
50
|
+
*
|
|
51
|
+
* All voxels are initialised to **0**.
|
|
52
|
+
*
|
|
53
|
+
* @param width Number of voxels along the X axis (≥ 1).
|
|
54
|
+
* @param height Number of voxels along the Y axis (≥ 1).
|
|
55
|
+
* @param depth Number of voxels along the Z axis (≥ 1).
|
|
56
|
+
* @param channels Number of channels per voxel (default 1).
|
|
57
|
+
* @param customColorMap Optional color map to override defaults.
|
|
58
|
+
*
|
|
59
|
+
* @throws {RangeError} If any dimension or the channel count is < 1.
|
|
60
|
+
*/
|
|
61
|
+
constructor(width: number, height: number, depth: number, channels?: number, customColorMap?: ChannelColorMap);
|
|
62
|
+
/**
|
|
63
|
+
* Map 3D coordinates + channel to a flat 1D index.
|
|
64
|
+
*
|
|
65
|
+
* **Memory layout (slice-major):**
|
|
66
|
+
* ```
|
|
67
|
+
* index = (z × height × width × channels)
|
|
68
|
+
* + (y × width × channels)
|
|
69
|
+
* + (x × channels)
|
|
70
|
+
* + channel
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @param x Voxel column (0 … width − 1).
|
|
74
|
+
* @param y Voxel row (0 … height − 1).
|
|
75
|
+
* @param z Slice index (0 … depth − 1).
|
|
76
|
+
* @param channel Channel index (0 … channels − 1).
|
|
77
|
+
* @returns Flat 1D index into `this.data`.
|
|
78
|
+
*
|
|
79
|
+
* @throws {RangeError} If any coordinate or channel is out of bounds.
|
|
80
|
+
*/
|
|
81
|
+
private getIndex;
|
|
82
|
+
/**
|
|
83
|
+
* Read a single voxel value.
|
|
84
|
+
*
|
|
85
|
+
* @param x Voxel column (0 … width − 1).
|
|
86
|
+
* @param y Voxel row (0 … height − 1).
|
|
87
|
+
* @param z Slice index (0 … depth − 1).
|
|
88
|
+
* @param channel Channel index (default 0).
|
|
89
|
+
* @returns The stored value (0 – 255).
|
|
90
|
+
*
|
|
91
|
+
* @throws {RangeError} If any coordinate is out of bounds.
|
|
92
|
+
*/
|
|
93
|
+
getVoxel(x: number, y: number, z: number, channel?: number): number;
|
|
94
|
+
/**
|
|
95
|
+
* Write a single voxel value.
|
|
96
|
+
*
|
|
97
|
+
* Values are clamped to the Uint8 range [0, 255] by the typed array.
|
|
98
|
+
*
|
|
99
|
+
* @param x Voxel column (0 … width − 1).
|
|
100
|
+
* @param y Voxel row (0 … height − 1).
|
|
101
|
+
* @param z Slice index (0 … depth − 1).
|
|
102
|
+
* @param value New voxel value (0 – 255).
|
|
103
|
+
* @param channel Channel index (default 0).
|
|
104
|
+
*
|
|
105
|
+
* @throws {RangeError} If any coordinate is out of bounds.
|
|
106
|
+
*/
|
|
107
|
+
setVoxel(x: number, y: number, z: number, value: number, channel?: number): void;
|
|
108
|
+
/**
|
|
109
|
+
* Update the color for a specific label/channel in the color map.
|
|
110
|
+
*
|
|
111
|
+
* For label-based volumes (1-channel), the channel parameter refers to
|
|
112
|
+
* the label value (0-8), not the storage channel index.
|
|
113
|
+
*
|
|
114
|
+
* @param channel Label/channel index to update (0-8).
|
|
115
|
+
* @param color New RGBA color.
|
|
116
|
+
*
|
|
117
|
+
* @throws {RangeError} If channel is outside valid label range [0, 8].
|
|
118
|
+
*/
|
|
119
|
+
setChannelColor(channel: number, color: RGBAColor): void;
|
|
120
|
+
/**
|
|
121
|
+
* Get the current color for a channel.
|
|
122
|
+
*
|
|
123
|
+
* Falls back to the background color (transparent) if no color is defined.
|
|
124
|
+
*
|
|
125
|
+
* @param channel Channel index.
|
|
126
|
+
* @returns A copy of the RGBA color.
|
|
127
|
+
*/
|
|
128
|
+
getChannelColor(channel: number): RGBAColor;
|
|
129
|
+
/**
|
|
130
|
+
* Reset the color map for one or all labels to defaults.
|
|
131
|
+
*
|
|
132
|
+
* @param channel Optional specific label to reset. If omitted, resets all.
|
|
133
|
+
*/
|
|
134
|
+
resetChannelColors(channel?: number): void;
|
|
135
|
+
/**
|
|
136
|
+
* Get a shallow copy of the entire color map.
|
|
137
|
+
*
|
|
138
|
+
* @returns A copy of the color map (safe to mutate).
|
|
139
|
+
*/
|
|
140
|
+
getColorMap(): ChannelColorMap;
|
|
141
|
+
/**
|
|
142
|
+
* Extract a 2D slice as an ImageData with color mapping.
|
|
143
|
+
*
|
|
144
|
+
* Slice dimensions depend on the axis:
|
|
145
|
+
*
|
|
146
|
+
* | Axis | Plane | Width | Height |
|
|
147
|
+
* |------|----------|--------|--------|
|
|
148
|
+
* | `z` | Axial | width | height |
|
|
149
|
+
* | `y` | Coronal | width | depth |
|
|
150
|
+
* | `x` | Sagittal | depth | height |
|
|
151
|
+
*
|
|
152
|
+
* @param sliceIndex Index along the specified axis.
|
|
153
|
+
* @param axis `'x'` (sagittal), `'y'` (coronal), or `'z'` (axial).
|
|
154
|
+
* @param options Rendering options (mode, channel, colors, etc.).
|
|
155
|
+
* @returns ImageData ready for `ctx.putImageData()`.
|
|
156
|
+
*
|
|
157
|
+
* @throws {RangeError} If sliceIndex is out of bounds for the given axis.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* // Grayscale (default, backward compatible)
|
|
162
|
+
* const gs = vol.getSliceImageData(50, 'z');
|
|
163
|
+
*
|
|
164
|
+
* // Colored single-channel
|
|
165
|
+
* const cs = vol.getSliceImageData(50, 'z', {
|
|
166
|
+
* mode: RenderMode.COLORED_SINGLE,
|
|
167
|
+
* channel: 0,
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* // Multi-channel priority
|
|
171
|
+
* const mc = vol.getSliceImageData(50, 'z', {
|
|
172
|
+
* mode: RenderMode.COLORED_MULTI,
|
|
173
|
+
* visibleChannels: [false, true, true, true],
|
|
174
|
+
* });
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
getSliceImageData(sliceIndex: number, axis?: 'x' | 'y' | 'z', options?: SliceRenderOptions): ImageData;
|
|
178
|
+
/**
|
|
179
|
+
* Write a 2D ImageData back into the volume for the given axis/slice.
|
|
180
|
+
*
|
|
181
|
+
* When the volume has **4 channels**, all RGBA components are stored
|
|
182
|
+
* (ch0=R, ch1=G, ch2=B, ch3=A) for lossless round-trip.
|
|
183
|
+
* Otherwise, the **R channel** of each pixel is used as grayscale.
|
|
184
|
+
*
|
|
185
|
+
* @param sliceIndex Index along the specified axis.
|
|
186
|
+
* @param imageData Source ImageData (expected dimensions must match the axis).
|
|
187
|
+
* @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
|
|
188
|
+
* @param channel Target channel (default 0, ignored when numChannels >= 4).
|
|
189
|
+
*
|
|
190
|
+
* @throws {RangeError} If sliceIndex is out of bounds.
|
|
191
|
+
* @throws {Error} If imageData dimensions don't match.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const imgData = ctx.getImageData(0, 0, 512, 512);
|
|
196
|
+
* vol.setSliceFromImageData(50, imgData, 'z');
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
setSliceFromImageData(sliceIndex: number, imageData: ImageData, axis?: 'x' | 'y' | 'z', channel?: number): void;
|
|
200
|
+
/**
|
|
201
|
+
* Extract a 2D slice as raw RGBA ImageData (lossless round-trip).
|
|
202
|
+
*
|
|
203
|
+
* Requires the volume to have **≥ 4 channels** (ch0=R, ch1=G, ch2=B, ch3=A).
|
|
204
|
+
* Returns the exact pixel data that was stored via `setSliceFromImageData`,
|
|
205
|
+
* bypassing all render modes.
|
|
206
|
+
*
|
|
207
|
+
* @param sliceIndex Index along the specified axis.
|
|
208
|
+
* @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
|
|
209
|
+
* @returns ImageData with the stored RGBA values.
|
|
210
|
+
*
|
|
211
|
+
* @throws {RangeError} If sliceIndex is out of bounds.
|
|
212
|
+
* @throws {Error} If the volume has fewer than 4 channels.
|
|
213
|
+
*/
|
|
214
|
+
getSliceRawImageData(sliceIndex: number, axis?: 'x' | 'y' | 'z'): ImageData;
|
|
215
|
+
/**
|
|
216
|
+
* Write slice data into an existing ImageData buffer (zero-allocation).
|
|
217
|
+
*
|
|
218
|
+
* Same semantics as {@link getSliceRawImageData} but avoids creating a new
|
|
219
|
+
* ImageData object on every call. The caller is responsible for providing
|
|
220
|
+
* a buffer whose dimensions match the expected slice size.
|
|
221
|
+
*
|
|
222
|
+
* All pixels are fully overwritten — no clearing is needed beforehand.
|
|
223
|
+
*
|
|
224
|
+
* @param sliceIndex Index along the specified axis.
|
|
225
|
+
* @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
|
|
226
|
+
* @param target Pre-allocated ImageData to write into.
|
|
227
|
+
*
|
|
228
|
+
* @throws {Error} If the volume has fewer than 4 channels.
|
|
229
|
+
* @throws {RangeError} If sliceIndex is out of bounds.
|
|
230
|
+
* @throws {Error} If target dimensions don't match the slice.
|
|
231
|
+
*/
|
|
232
|
+
getSliceRawImageDataInto(sliceIndex: number, axis: "z" | "y" | "x" | undefined, target: ImageData): void;
|
|
233
|
+
/**
|
|
234
|
+
/**
|
|
235
|
+
* Store label data from canvas RGBA ImageData into a 1-channel volume.
|
|
236
|
+
*
|
|
237
|
+
* For each pixel: matches the RGB color against MASK_CHANNEL_COLORS to
|
|
238
|
+
* determine which channel label to store. If no match is found, falls
|
|
239
|
+
* back to `activeChannel`. Transparent pixels (alpha === 0) are stored as 0.
|
|
240
|
+
*
|
|
241
|
+
* This is the write counterpart to `renderLabelSliceInto`.
|
|
242
|
+
*
|
|
243
|
+
* @param sliceIndex Index along the specified axis.
|
|
244
|
+
* @param imageData Canvas ImageData (RGBA) to convert.
|
|
245
|
+
* @param axis `'x'`, `'y'`, or `'z'`.
|
|
246
|
+
* @param activeChannel Fallback label for pixels whose RGB doesn't match any channel (1-8).
|
|
247
|
+
* @param channelVisible Optional map of visible channels (true=visible, false=hidden).
|
|
248
|
+
* If provided, data for hidden channels will be preserved
|
|
249
|
+
* when the canvas pixel is transparent.
|
|
250
|
+
*/
|
|
251
|
+
setSliceLabelsFromImageData(sliceIndex: number, imageData: ImageData, axis?: 'x' | 'y' | 'z', activeChannel?: number, channelVisible?: Record<number, boolean>): void;
|
|
252
|
+
/**
|
|
253
|
+
* Build a Map from RGB packed integer to channel label for reverse lookup.
|
|
254
|
+
* Uses this instance's colorMap (channels 1-8, skips 0 = transparent).
|
|
255
|
+
*
|
|
256
|
+
* Instance method ensures custom per-layer colors are correctly reverse-mapped.
|
|
257
|
+
*/
|
|
258
|
+
private buildRgbToChannelMap;
|
|
259
|
+
/**
|
|
260
|
+
* Render a 1-channel label slice into a pre-allocated RGBA ImageData buffer.
|
|
261
|
+
*
|
|
262
|
+
* Each voxel stores a label value (0-8). Label 0 = transparent.
|
|
263
|
+
* Labels 1-8 are mapped to colors via MASK_CHANNEL_COLORS, filtered
|
|
264
|
+
* by the channelVisible array.
|
|
265
|
+
*
|
|
266
|
+
* @param sliceIndex Index along the specified axis.
|
|
267
|
+
* @param axis `'x'`, `'y'`, or `'z'`.
|
|
268
|
+
* @param target Pre-allocated ImageData buffer (must match slice dimensions).
|
|
269
|
+
* @param channelVisible Array where index N indicates if channel N is visible.
|
|
270
|
+
* If undefined, all channels are visible.
|
|
271
|
+
* @param opacity Opacity multiplier 0.0–1.0 (default 1.0).
|
|
272
|
+
*/
|
|
273
|
+
renderLabelSliceInto(sliceIndex: number, axis: "z" | "y" | "x" | undefined, target: ImageData, channelVisible?: Record<number, boolean>, opacity?: number): void;
|
|
274
|
+
/**
|
|
275
|
+
* Return a copy of the volume dimensions.
|
|
276
|
+
*
|
|
277
|
+
* @returns `{ width, height, depth }` — a fresh object (safe to mutate).
|
|
278
|
+
*/
|
|
279
|
+
getDimensions(): Dimensions;
|
|
280
|
+
/**
|
|
281
|
+
* Return the number of channels per voxel.
|
|
282
|
+
*
|
|
283
|
+
* @returns Channel count (≥ 1).
|
|
284
|
+
*/
|
|
285
|
+
getChannels(): number;
|
|
286
|
+
/**
|
|
287
|
+
* Return total memory used by the backing buffer, in bytes.
|
|
288
|
+
*
|
|
289
|
+
* @returns `width × height × depth × channels` bytes.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* const vol = new MaskVolume(512, 512, 100);
|
|
294
|
+
* vol.getMemoryUsage(); // 26_214_400 (~25 MB)
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
getMemoryUsage(): number;
|
|
298
|
+
/**
|
|
299
|
+
* Return a **reference** to the raw backing buffer.
|
|
300
|
+
*
|
|
301
|
+
* Use this for direct read access (e.g. serialisation, GPU upload).
|
|
302
|
+
* Mutating the returned array mutates the volume.
|
|
303
|
+
*
|
|
304
|
+
* @returns The underlying Uint8Array.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* // Serialise for network transfer
|
|
309
|
+
* const bytes = vol.getRawData();
|
|
310
|
+
* socket.send(bytes.buffer);
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
getRawData(): Uint8Array;
|
|
314
|
+
/**
|
|
315
|
+
* Replace the entire backing buffer.
|
|
316
|
+
*
|
|
317
|
+
* The provided array **must** have exactly the same length as the
|
|
318
|
+
* current buffer (`width × height × depth × channels`).
|
|
319
|
+
*
|
|
320
|
+
* @param newData Replacement data.
|
|
321
|
+
* @throws {Error} If the length does not match.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* // Restore from a saved snapshot
|
|
326
|
+
* const saved = new Uint8Array(savedBuffer);
|
|
327
|
+
* vol.setRawData(saved);
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
setRawData(newData: Uint8Array): void;
|
|
331
|
+
/**
|
|
332
|
+
* Create an independent deep copy of this volume.
|
|
333
|
+
*
|
|
334
|
+
* The clone has the same dimensions, channels, data, and color map
|
|
335
|
+
* but shares no references with the original.
|
|
336
|
+
*
|
|
337
|
+
* @returns A new MaskVolume with identical content.
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* // Undo support: snapshot before an operation
|
|
342
|
+
* const snapshot = vol.clone();
|
|
343
|
+
* performEdit(vol);
|
|
344
|
+
* // Rollback: vol.setRawData(snapshot.getRawData());
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
clone(): MaskVolume;
|
|
348
|
+
/**
|
|
349
|
+
* Zero every voxel in the entire volume (all channels).
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```ts
|
|
353
|
+
* vol.clear();
|
|
354
|
+
* vol.getVoxel(256, 256, 50); // 0
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
clear(): void;
|
|
358
|
+
/**
|
|
359
|
+
* Check if the volume contains any non-zero voxel data.
|
|
360
|
+
*
|
|
361
|
+
* Returns true if at least one voxel has a non-zero value,
|
|
362
|
+
* false if all voxels are zero (empty mask).
|
|
363
|
+
*
|
|
364
|
+
* This is useful for determining if a layer has actual mask
|
|
365
|
+
* annotations before saving or exporting.
|
|
366
|
+
*
|
|
367
|
+
* @returns True if any voxel is non-zero, false if all zeros.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```ts
|
|
371
|
+
* const vol = new MaskVolume(512, 512, 100);
|
|
372
|
+
* vol.hasData(); // false (all zeros)
|
|
373
|
+
*
|
|
374
|
+
* vol.setVoxel(256, 256, 50, 255);
|
|
375
|
+
* vol.hasData(); // true
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
hasData(): boolean;
|
|
379
|
+
/**
|
|
380
|
+
* Zero every voxel in a single slice along the given axis.
|
|
381
|
+
*
|
|
382
|
+
* If `channel` is provided, only that channel is cleared;
|
|
383
|
+
* otherwise **all** channels in the slice are cleared.
|
|
384
|
+
*
|
|
385
|
+
* @param sliceIndex Index along the specified axis.
|
|
386
|
+
* @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
|
|
387
|
+
* @param channel Optional channel to clear (default: all).
|
|
388
|
+
*
|
|
389
|
+
* @throws {RangeError} If sliceIndex is out of bounds.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* // Erase a single axial slice
|
|
394
|
+
* vol.clearSlice(50, 'z');
|
|
395
|
+
*
|
|
396
|
+
* // Erase only channel 1 of slice 50
|
|
397
|
+
* vol.clearSlice(50, 'z', 1);
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
clearSlice(sliceIndex: number, axis?: 'x' | 'y' | 'z', channel?: number): void;
|
|
401
|
+
/**
|
|
402
|
+
* Extract a 2D slice as a flat Uint8Array (one byte per voxel per channel).
|
|
403
|
+
*
|
|
404
|
+
* For a 1-channel volume the result is `sliceWidth × sliceHeight` bytes,
|
|
405
|
+
* each byte being the label value at that pixel. This is suitable for
|
|
406
|
+
* sending to a backend that expects raw label data (e.g. NIfTI slice
|
|
407
|
+
* replacement).
|
|
408
|
+
*
|
|
409
|
+
* @param sliceIndex Index along the specified axis.
|
|
410
|
+
* @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
|
|
411
|
+
* @returns A **copy** of the slice data plus its dimensions.
|
|
412
|
+
*
|
|
413
|
+
* @throws {RangeError} If sliceIndex is out of bounds.
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```ts
|
|
417
|
+
* const { data, width, height } = vol.getSliceUint8(50, 'z');
|
|
418
|
+
* // data.length === width * height * channels
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
getSliceUint8(sliceIndex: number, axis?: 'x' | 'y' | 'z'): {
|
|
422
|
+
data: Uint8Array;
|
|
423
|
+
width: number;
|
|
424
|
+
height: number;
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* Write a 2D slice from a flat Uint8Array back into the volume.
|
|
428
|
+
*
|
|
429
|
+
* This is the inverse of {@link getSliceUint8}. The `data` array must
|
|
430
|
+
* have exactly `sliceWidth × sliceHeight × channels` bytes.
|
|
431
|
+
*
|
|
432
|
+
* @param sliceIndex Index along the specified axis.
|
|
433
|
+
* @param data Flat source array (same layout as returned by getSliceUint8).
|
|
434
|
+
* @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
|
|
435
|
+
*
|
|
436
|
+
* @throws {RangeError} If sliceIndex is out of bounds.
|
|
437
|
+
*/
|
|
438
|
+
setSliceUint8(sliceIndex: number, data: Uint8Array, axis?: 'x' | 'y' | 'z'): void;
|
|
439
|
+
/**
|
|
440
|
+
* Get the 2D slice dimensions for a given axis.
|
|
441
|
+
*
|
|
442
|
+
* @returns `[width, height]` of the slice.
|
|
443
|
+
*/
|
|
444
|
+
private getSliceDimensions;
|
|
445
|
+
/**
|
|
446
|
+
* Validate that a slice index is within bounds for the given axis.
|
|
447
|
+
*
|
|
448
|
+
* @throws {RangeError} If out of bounds.
|
|
449
|
+
*/
|
|
450
|
+
private validateSliceIndex;
|
|
451
|
+
/**
|
|
452
|
+
* Render a single channel as grayscale (original behaviour).
|
|
453
|
+
*
|
|
454
|
+
* Non-zero voxels are fully opaque (A = 255), zero voxels are
|
|
455
|
+
* transparent (A = 0).
|
|
456
|
+
*/
|
|
457
|
+
private renderGrayscale;
|
|
458
|
+
/**
|
|
459
|
+
* Render a single channel with its predefined color.
|
|
460
|
+
*
|
|
461
|
+
* Alpha is modulated by the voxel intensity and the opacity multiplier.
|
|
462
|
+
*/
|
|
463
|
+
private renderColoredSingle;
|
|
464
|
+
/**
|
|
465
|
+
* Render all visible channels with distinct colors.
|
|
466
|
+
*
|
|
467
|
+
* The **highest-index** non-zero visible channel wins (priority-based).
|
|
468
|
+
*/
|
|
469
|
+
private renderColoredMulti;
|
|
470
|
+
/**
|
|
471
|
+
* Render all visible channels with additive blending.
|
|
472
|
+
*
|
|
473
|
+
* Each channel contributes its color weighted by intensity × opacity.
|
|
474
|
+
* RGB values are clamped to 255.
|
|
475
|
+
*/
|
|
476
|
+
private renderBlended;
|
|
477
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration Utilities for MaskVolume
|
|
3
|
+
*
|
|
4
|
+
* Provides bidirectional conversion between the legacy per-slice
|
|
5
|
+
* ImageData storage (`IPaintImages`) and the new contiguous 3D
|
|
6
|
+
* volumetric storage (`MaskVolume`).
|
|
7
|
+
*
|
|
8
|
+
* These utilities enable:
|
|
9
|
+
* - **Forward migration**: Import existing annotation data into MaskVolume.
|
|
10
|
+
* - **Rollback compatibility**: Export MaskVolume back to IPaintImages.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Forward: legacy → volume
|
|
15
|
+
* const volume = convertIPaintImagesToVolume(paintImages, { width: 512, height: 512, depth: 100 });
|
|
16
|
+
*
|
|
17
|
+
* // Backward: volume → legacy
|
|
18
|
+
* const legacy = convertVolumeToIPaintImages(volume);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import type { Dimensions } from './types';
|
|
22
|
+
import { MaskVolume } from './MaskVolume';
|
|
23
|
+
/**
|
|
24
|
+
* A single painted slice (legacy format).
|
|
25
|
+
*/
|
|
26
|
+
export interface IPaintImage {
|
|
27
|
+
index: number;
|
|
28
|
+
image: ImageData;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Per-axis collection of painted slices (legacy format).
|
|
32
|
+
*/
|
|
33
|
+
export interface IPaintImages {
|
|
34
|
+
x: IPaintImage[];
|
|
35
|
+
y: IPaintImage[];
|
|
36
|
+
z: IPaintImage[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Convert legacy per-slice ImageData storage into a MaskVolume.
|
|
40
|
+
*
|
|
41
|
+
* The function iterates over all three axes (`x`, `y`, `z`) and inserts
|
|
42
|
+
* each slice into the volume using `setSliceFromImageData`.
|
|
43
|
+
*
|
|
44
|
+
* **Sparse data handling**: Slices that are absent from the arrays are
|
|
45
|
+
* left as zero (the MaskVolume default). Duplicate indices are
|
|
46
|
+
* overwritten in order (last wins).
|
|
47
|
+
*
|
|
48
|
+
* @param paintImages Legacy per-axis slice collection.
|
|
49
|
+
* @param dimensions Volume dimensions (must match the ImageData sizes).
|
|
50
|
+
* @param channel Target channel in the volume (default 0).
|
|
51
|
+
*
|
|
52
|
+
* @returns A new MaskVolume populated with the legacy data.
|
|
53
|
+
*
|
|
54
|
+
* @throws {Error} If a slice's ImageData dimensions don't match the
|
|
55
|
+
* expected size for its axis.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const volume = convertIPaintImagesToVolume(
|
|
60
|
+
* maskData.paintImagesLayer1,
|
|
61
|
+
* { width: 512, height: 512, depth: 100 },
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function convertIPaintImagesToVolume(paintImages: IPaintImages, dimensions: Dimensions, channel?: number): MaskVolume;
|
|
66
|
+
/**
|
|
67
|
+
* Convert a MaskVolume back to legacy per-slice ImageData storage.
|
|
68
|
+
*
|
|
69
|
+
* Extracts **all** slices along the Z axis (axial) into an
|
|
70
|
+
* `IPaintImages` structure. X and Y axes are left as empty arrays
|
|
71
|
+
* since the legacy system can reconstruct them on demand.
|
|
72
|
+
*
|
|
73
|
+
* Only non-empty slices (those containing at least one non-zero pixel)
|
|
74
|
+
* are included, matching the sparse behaviour of the original storage.
|
|
75
|
+
*
|
|
76
|
+
* @param volume The MaskVolume to export.
|
|
77
|
+
* @param channel Channel to extract (default 0).
|
|
78
|
+
* @param options Export options.
|
|
79
|
+
* @param options.includeAllAxes If true, also export X and Y axis slices
|
|
80
|
+
* (expensive — use only if needed for full
|
|
81
|
+
* backward compatibility). Default: false.
|
|
82
|
+
* @param options.includeEmpty If true, include slices that are entirely
|
|
83
|
+
* zero. Default: false.
|
|
84
|
+
*
|
|
85
|
+
* @returns A legacy IPaintImages structure.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const legacy = convertVolumeToIPaintImages(volume);
|
|
90
|
+
* // legacy.z contains non-empty axial slices
|
|
91
|
+
* // legacy.x and legacy.y are empty by default
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function convertVolumeToIPaintImages(volume: MaskVolume, channel?: number, options?: {
|
|
95
|
+
includeAllAxes?: boolean;
|
|
96
|
+
includeEmpty?: boolean;
|
|
97
|
+
}): IPaintImages;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UndoManager — Per-Layer Slice-Snapshot Undo/Redo
|
|
3
|
+
*
|
|
4
|
+
* Phase 6: Replaces the old HTMLImageElement-based undo system.
|
|
5
|
+
*
|
|
6
|
+
* Design:
|
|
7
|
+
* - Each layer has an independent undo and redo stack.
|
|
8
|
+
* - Each entry (MaskDelta) stores a full 2D slice snapshot before and after
|
|
9
|
+
* the operation (cheaper than full volume, sufficient for single-stroke ops).
|
|
10
|
+
* - maxStackSize: 50 entries per layer.
|
|
11
|
+
* - Undo/redo operations also trigger backend sync via getMask callback.
|
|
12
|
+
*/
|
|
13
|
+
export interface MaskDelta {
|
|
14
|
+
layerId: string;
|
|
15
|
+
axis: "x" | "y" | "z";
|
|
16
|
+
sliceIndex: number;
|
|
17
|
+
/** Full slice data captured before the drawing operation. */
|
|
18
|
+
oldSlice: Uint8Array;
|
|
19
|
+
/** Full slice data captured after the drawing operation. */
|
|
20
|
+
newSlice: Uint8Array;
|
|
21
|
+
}
|
|
22
|
+
export declare class UndoManager {
|
|
23
|
+
private undoStacks;
|
|
24
|
+
private redoStacks;
|
|
25
|
+
private activeLayer;
|
|
26
|
+
constructor();
|
|
27
|
+
/** Set the currently active layer (determines which stack undo/redo operates on). */
|
|
28
|
+
setActiveLayer(layer: string): void;
|
|
29
|
+
/** Push a delta onto the active layer's undo stack and clear the redo stack. */
|
|
30
|
+
push(delta: MaskDelta): void;
|
|
31
|
+
/**
|
|
32
|
+
* Undo the last operation on the active layer.
|
|
33
|
+
* @returns The delta that was undone, or undefined if nothing to undo.
|
|
34
|
+
*/
|
|
35
|
+
undo(): MaskDelta | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Redo the last undone operation on the active layer.
|
|
38
|
+
* @returns The delta that was redone, or undefined if nothing to redo.
|
|
39
|
+
*/
|
|
40
|
+
redo(): MaskDelta | undefined;
|
|
41
|
+
canUndo(): boolean;
|
|
42
|
+
canRedo(): boolean;
|
|
43
|
+
/** Clear undo and redo stacks for a specific layer (called on clearStoreImages). */
|
|
44
|
+
clearLayer(layer: string): void;
|
|
45
|
+
/** Clear all stacks for all layers (called on full dataset reload). */
|
|
46
|
+
clearAll(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module segmentation/core
|
|
3
|
+
*
|
|
4
|
+
* Core 3D Volumetric Mask Storage System
|
|
5
|
+
*
|
|
6
|
+
* This module provides a memory-efficient 3D mask storage solution for medical
|
|
7
|
+
* image annotation, replacing the legacy per-slice ImageData approach with a
|
|
8
|
+
* single contiguous {@link MaskVolume} backed by a `Uint8Array`.
|
|
9
|
+
*
|
|
10
|
+
* ## Architecture
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* core/
|
|
14
|
+
* ├── types.ts — Shared type definitions (Dimensions, RenderMode, colors)
|
|
15
|
+
* ├── MaskVolume.ts — Core volume class (storage, access, rendering)
|
|
16
|
+
* ├── MigrationUtils.ts — Legacy ↔ Volume bidirectional conversion
|
|
17
|
+
* └── index.ts — Public API barrel export (this file)
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* ## Memory Layout
|
|
21
|
+
*
|
|
22
|
+
* The backing buffer uses **slice-major** order: `[z][y][x][channel]`.
|
|
23
|
+
*
|
|
24
|
+
* ```
|
|
25
|
+
* index = z * (width × height × channels)
|
|
26
|
+
* + y * (width × channels)
|
|
27
|
+
* + x * channels
|
|
28
|
+
* + channel
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* For a 512×512×100 single-channel volume this allocates ~25 MB,
|
|
32
|
+
* compared to ~100 MB for ImageData-per-slice (Z only) or ~524 MB
|
|
33
|
+
* for all three axes.
|
|
34
|
+
*
|
|
35
|
+
* ## Quick Start
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { MaskVolume, RenderMode } from './core';
|
|
39
|
+
*
|
|
40
|
+
* // Create a volume matching the NRRD dimensions
|
|
41
|
+
* const vol = new MaskVolume(512, 512, 100);
|
|
42
|
+
*
|
|
43
|
+
* // Paint a voxel
|
|
44
|
+
* vol.setVoxel(256, 256, 50, 255);
|
|
45
|
+
*
|
|
46
|
+
* // Extract a slice for Canvas rendering
|
|
47
|
+
* const slice = vol.getSliceImageData(50, 'z');
|
|
48
|
+
* ctx.putImageData(slice, 0, 0);
|
|
49
|
+
*
|
|
50
|
+
* // Colored rendering
|
|
51
|
+
* const colored = vol.getSliceImageData(50, 'z', {
|
|
52
|
+
* mode: RenderMode.COLORED_SINGLE,
|
|
53
|
+
* channel: 0,
|
|
54
|
+
* opacity: 0.6,
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* ## Migration from Legacy Storage
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { convertIPaintImagesToVolume, convertVolumeToIPaintImages } from './core';
|
|
62
|
+
*
|
|
63
|
+
* // Forward: legacy IPaintImages → MaskVolume
|
|
64
|
+
* const volume = convertIPaintImagesToVolume(paintImages, { width: 512, height: 512, depth: 100 });
|
|
65
|
+
*
|
|
66
|
+
* // Backward: MaskVolume → legacy IPaintImages (for rollback)
|
|
67
|
+
* const legacy = convertVolumeToIPaintImages(volume);
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export type { Dimensions, RGBAColor, ChannelColorMap, SliceRenderOptions, LayerId, ChannelValue, } from './types';
|
|
71
|
+
export { RenderMode, MASK_CHANNEL_COLORS, MASK_CHANNEL_CSS_COLORS, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, } from './types';
|
|
72
|
+
export { MaskVolume } from './MaskVolume';
|
|
73
|
+
export type { IPaintImage, IPaintImages } from './MigrationUtils';
|
|
74
|
+
export { convertIPaintImagesToVolume, convertVolumeToIPaintImages, } from './MigrationUtils';
|
|
75
|
+
export type { MaskDelta } from './UndoManager';
|
|
76
|
+
export { UndoManager } from './UndoManager';
|