copper3d 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Utils/segmentation/DragOperator.d.ts +2 -1
- package/dist/Utils/segmentation/DragOperator.js +3 -1
- package/dist/Utils/segmentation/DragOperator.js.map +1 -1
- package/dist/Utils/segmentation/NrrdTools.d.ts +3 -1
- package/dist/Utils/segmentation/NrrdTools.js +46 -2
- package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
- package/dist/Utils/segmentation/core/GaussianSmoother.d.ts +55 -0
- package/dist/Utils/segmentation/core/GaussianSmoother.js +210 -0
- package/dist/Utils/segmentation/core/GaussianSmoother.js.map +1 -0
- package/dist/Utils/segmentation/core/MaskVolume.d.ts +9 -0
- package/dist/Utils/segmentation/core/MaskVolume.js +11 -0
- package/dist/Utils/segmentation/core/MaskVolume.js.map +1 -1
- package/dist/Utils/segmentation/tools/DragSliceTool.d.ts +0 -4
- package/dist/Utils/segmentation/tools/DragSliceTool.js +2 -19
- package/dist/Utils/segmentation/tools/DragSliceTool.js.map +1 -1
- package/dist/Utils/segmentation/tools/SliceRenderPipeline.js +8 -1
- package/dist/Utils/segmentation/tools/SliceRenderPipeline.js.map +1 -1
- package/dist/Utils/segmentation/tools/ToolHost.d.ts +1 -1
- package/dist/bundle.esm.js +281 -25
- package/dist/bundle.umd.js +281 -24
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/types/Utils/segmentation/DragOperator.d.ts +2 -1
- package/dist/types/Utils/segmentation/NrrdTools.d.ts +3 -1
- package/dist/types/Utils/segmentation/core/GaussianSmoother.d.ts +55 -0
- package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +9 -0
- package/dist/types/Utils/segmentation/tools/DragSliceTool.d.ts +0 -4
- package/dist/types/Utils/segmentation/tools/ToolHost.d.ts +1 -1
- package/dist/types/index.d.ts +3 -2
- package/package.json +1 -1
package/dist/bundle.esm.js
CHANGED
|
@@ -71387,6 +71387,17 @@ class MaskVolume {
|
|
|
71387
71387
|
getChannels() {
|
|
71388
71388
|
return this.numChannels;
|
|
71389
71389
|
}
|
|
71390
|
+
/**
|
|
71391
|
+
* Return the number of bytes in one complete z-slice.
|
|
71392
|
+
*
|
|
71393
|
+
* Equal to `width × height × channels`. Useful for direct buffer
|
|
71394
|
+
* index arithmetic in performance-critical code paths.
|
|
71395
|
+
*
|
|
71396
|
+
* @returns Bytes per z-slice.
|
|
71397
|
+
*/
|
|
71398
|
+
getBytesPerSlice() {
|
|
71399
|
+
return this.bytesPerSlice;
|
|
71400
|
+
}
|
|
71390
71401
|
/**
|
|
71391
71402
|
* Return total memory used by the backing buffer, in bytes.
|
|
71392
71403
|
*
|
|
@@ -73204,8 +73215,8 @@ class DragSliceTool extends BaseTool {
|
|
|
73204
73215
|
}
|
|
73205
73216
|
}
|
|
73206
73217
|
}
|
|
73207
|
-
// Composite all layers to master canvas
|
|
73208
|
-
this.compositeAllLayers();
|
|
73218
|
+
// Composite all layers to master canvas (uses per-layer alpha)
|
|
73219
|
+
this.callbacks.compositeAllLayers();
|
|
73209
73220
|
}
|
|
73210
73221
|
// Refresh sphere overlay from volume for the new slice
|
|
73211
73222
|
if (this.ctx.gui_states.mode.sphere) {
|
|
@@ -73214,23 +73225,6 @@ class DragSliceTool extends BaseTool {
|
|
|
73214
73225
|
view.switchSliceFlag = false;
|
|
73215
73226
|
}
|
|
73216
73227
|
}
|
|
73217
|
-
/**
|
|
73218
|
-
* Composite all visible layer canvases to the master display canvas.
|
|
73219
|
-
*/
|
|
73220
|
-
compositeAllLayers() {
|
|
73221
|
-
const masterCtx = this.ctx.protectedData.ctxes.drawingLayerMasterCtx;
|
|
73222
|
-
const width = this.ctx.nrrd_states.view.changedWidth;
|
|
73223
|
-
const height = this.ctx.nrrd_states.view.changedHeight;
|
|
73224
|
-
masterCtx.clearRect(0, 0, width, height);
|
|
73225
|
-
// Master stores full-alpha composite; globalAlpha applied in start() render loop.
|
|
73226
|
-
for (const layerId of this.ctx.nrrd_states.image.layers) {
|
|
73227
|
-
if (!this.ctx.gui_states.layerChannel.layerVisibility[layerId])
|
|
73228
|
-
continue;
|
|
73229
|
-
const target = this.ctx.protectedData.layerTargets.get(layerId);
|
|
73230
|
-
if (target)
|
|
73231
|
-
masterCtx.drawImage(target.canvas, 0, 0, width, height);
|
|
73232
|
-
}
|
|
73233
|
-
}
|
|
73234
73228
|
// ===== Canvas Cleanup =====
|
|
73235
73229
|
cleanCanvases(flag) {
|
|
73236
73230
|
if (flag) {
|
|
@@ -73272,7 +73266,7 @@ class DragSliceTool extends BaseTool {
|
|
|
73272
73266
|
}
|
|
73273
73267
|
|
|
73274
73268
|
class DragOperator {
|
|
73275
|
-
constructor(container, nrrd_sates, gui_states, protectedData, drawingPrameters, setSyncsliceNum, setIsDrawFalse, flipDisplayImageByAxis, setEmptyCanvasSize, getOrCreateSliceBuffer, renderSliceToCanvas) {
|
|
73269
|
+
constructor(container, nrrd_sates, gui_states, protectedData, drawingPrameters, setSyncsliceNum, setIsDrawFalse, flipDisplayImageByAxis, setEmptyCanvasSize, getOrCreateSliceBuffer, renderSliceToCanvas, compositeAllLayers) {
|
|
73276
73270
|
this.dragPrameters = {
|
|
73277
73271
|
move: 0,
|
|
73278
73272
|
y: 0,
|
|
@@ -73308,6 +73302,7 @@ class DragOperator {
|
|
|
73308
73302
|
this.setEmptyCanvasSize = setEmptyCanvasSize;
|
|
73309
73303
|
this.getOrCreateSliceBuffer = getOrCreateSliceBuffer;
|
|
73310
73304
|
this.renderSliceToCanvas = renderSliceToCanvas;
|
|
73305
|
+
this.compositeAllLayers = compositeAllLayers;
|
|
73311
73306
|
this.showDragNumberDiv = createShowSliceNumberDiv();
|
|
73312
73307
|
this.init();
|
|
73313
73308
|
}
|
|
@@ -73333,6 +73328,7 @@ class DragOperator {
|
|
|
73333
73328
|
setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
|
|
73334
73329
|
getOrCreateSliceBuffer: (axis) => this.getOrCreateSliceBuffer(axis),
|
|
73335
73330
|
renderSliceToCanvas: (layer, axis, sliceIndex, buffer, targetCtx, w, h) => this.renderSliceToCanvas(layer, axis, sliceIndex, buffer, targetCtx, w, h),
|
|
73331
|
+
compositeAllLayers: () => this.compositeAllLayers(),
|
|
73336
73332
|
refreshSphereOverlay: () => { var _a; return (_a = this.refreshSphereOverlayCb) === null || _a === void 0 ? void 0 : _a.call(this); },
|
|
73337
73333
|
}, this.showDragNumberDiv, dragEffectCanvases);
|
|
73338
73334
|
}
|
|
@@ -76460,6 +76456,216 @@ class CanvasState {
|
|
|
76460
76456
|
}
|
|
76461
76457
|
}
|
|
76462
76458
|
|
|
76459
|
+
/**
|
|
76460
|
+
* GaussianSmoother — 3D Gaussian smoothing for segmentation masks.
|
|
76461
|
+
*
|
|
76462
|
+
* Applies a separable 3D Gaussian blur to a single label channel within a
|
|
76463
|
+
* MaskVolume, then thresholds the result back to a binary mask. This smooths
|
|
76464
|
+
* jagged edges and fills small holes in segmentation annotations.
|
|
76465
|
+
*
|
|
76466
|
+
* Pure, stateless utility — no DOM/Canvas/GUI dependencies.
|
|
76467
|
+
*
|
|
76468
|
+
* Performance: uses direct typed-array access (bypassing getVoxel/setVoxel
|
|
76469
|
+
* boundary checks) and branch-free convolution for the interior region.
|
|
76470
|
+
*/
|
|
76471
|
+
class GaussianSmoother {
|
|
76472
|
+
/**
|
|
76473
|
+
* Smooth a single label channel in-place using separable 3D Gaussian blur.
|
|
76474
|
+
*
|
|
76475
|
+
* Steps:
|
|
76476
|
+
* 1. Extract — create a Float32Array with 1.0 where voxel === channel, 0.0 elsewhere.
|
|
76477
|
+
* 2. Blur — apply separable Gaussian (X → Y → Z) on the float buffer.
|
|
76478
|
+
* 3. Threshold — binarize at 0.5.
|
|
76479
|
+
* 4. Write back — overwrite/erase voxels according to the thresholded result.
|
|
76480
|
+
*
|
|
76481
|
+
* @param volume The MaskVolume to operate on (modified in-place).
|
|
76482
|
+
* @param channel The label value to smooth (must be > 0).
|
|
76483
|
+
* @param sigma Base Gaussian sigma in voxel units (default 1.0).
|
|
76484
|
+
* @param spacing Optional voxel spacing [sx, sy, sz]. When provided, per-axis
|
|
76485
|
+
* sigma is computed as `sigma / spacing[axis]` so that the
|
|
76486
|
+
* physical smoothing radius is isotropic.
|
|
76487
|
+
*/
|
|
76488
|
+
static gaussianSmooth3D(volume, channel, sigma = 1.0, spacing) {
|
|
76489
|
+
const dims = volume.getDimensions();
|
|
76490
|
+
const { width, height, depth } = dims;
|
|
76491
|
+
const totalVoxels = width * height * depth;
|
|
76492
|
+
// Direct access to underlying buffer — bypasses getVoxel/setVoxel
|
|
76493
|
+
// boundary checks for ~10x speedup in hot loops.
|
|
76494
|
+
const rawData = volume.getRawData();
|
|
76495
|
+
const channels = volume.getChannels();
|
|
76496
|
+
const bytesPerSlice = volume.getBytesPerSlice();
|
|
76497
|
+
const rowStride = width * channels;
|
|
76498
|
+
// Compute per-axis sigma (normalized to voxel units)
|
|
76499
|
+
const sigmaX = spacing ? sigma / spacing[0] : sigma;
|
|
76500
|
+
const sigmaY = spacing ? sigma / spacing[1] : sigma;
|
|
76501
|
+
const sigmaZ = spacing ? sigma / spacing[2] : sigma;
|
|
76502
|
+
// 1. Extract binary float buffer — direct array access
|
|
76503
|
+
const buffer = new Float32Array(totalVoxels);
|
|
76504
|
+
for (let z = 0; z < depth; z++) {
|
|
76505
|
+
const zOffset = z * bytesPerSlice;
|
|
76506
|
+
for (let y = 0; y < height; y++) {
|
|
76507
|
+
const zyOffset = zOffset + y * rowStride;
|
|
76508
|
+
for (let x = 0; x < width; x++) {
|
|
76509
|
+
const bufIdx = z * height * width + y * width + x;
|
|
76510
|
+
buffer[bufIdx] = rawData[zyOffset + x * channels] === channel ? 1.0 : 0.0;
|
|
76511
|
+
}
|
|
76512
|
+
}
|
|
76513
|
+
}
|
|
76514
|
+
// 2. Separable Gaussian blur: X → Y → Z
|
|
76515
|
+
const kernelX = GaussianSmoother.generateKernel1D(sigmaX);
|
|
76516
|
+
const kernelY = GaussianSmoother.generateKernel1D(sigmaY);
|
|
76517
|
+
const kernelZ = GaussianSmoother.generateKernel1D(sigmaZ);
|
|
76518
|
+
GaussianSmoother.convolve1D(buffer, width, height, depth, 0, kernelX); // X axis
|
|
76519
|
+
GaussianSmoother.convolve1D(buffer, width, height, depth, 1, kernelY); // Y axis
|
|
76520
|
+
GaussianSmoother.convolve1D(buffer, width, height, depth, 2, kernelZ); // Z axis
|
|
76521
|
+
// 3 & 4. Threshold and write back — direct array access
|
|
76522
|
+
for (let z = 0; z < depth; z++) {
|
|
76523
|
+
const zOffset = z * bytesPerSlice;
|
|
76524
|
+
for (let y = 0; y < height; y++) {
|
|
76525
|
+
const zyOffset = zOffset + y * rowStride;
|
|
76526
|
+
for (let x = 0; x < width; x++) {
|
|
76527
|
+
const bufIdx = z * height * width + y * width + x;
|
|
76528
|
+
const smoothed = buffer[bufIdx] >= 0.5 ? 1 : 0;
|
|
76529
|
+
const rawIdx = zyOffset + x * channels;
|
|
76530
|
+
const original = rawData[rawIdx];
|
|
76531
|
+
if (smoothed === 1 && original !== channel) {
|
|
76532
|
+
// Smoothed region expands into this voxel — overwrite
|
|
76533
|
+
rawData[rawIdx] = channel;
|
|
76534
|
+
}
|
|
76535
|
+
else if (smoothed === 0 && original === channel) {
|
|
76536
|
+
// Smoothed region retreats — erase
|
|
76537
|
+
rawData[rawIdx] = 0;
|
|
76538
|
+
}
|
|
76539
|
+
// Otherwise leave unchanged
|
|
76540
|
+
}
|
|
76541
|
+
}
|
|
76542
|
+
}
|
|
76543
|
+
}
|
|
76544
|
+
/**
|
|
76545
|
+
* Generate a normalized 1D Gaussian kernel truncated at ±3σ.
|
|
76546
|
+
*
|
|
76547
|
+
* @param sigma Standard deviation (in voxels). Must be > 0.
|
|
76548
|
+
* @returns Normalized Float32Array of odd length.
|
|
76549
|
+
*/
|
|
76550
|
+
static generateKernel1D(sigma) {
|
|
76551
|
+
if (sigma <= 0) {
|
|
76552
|
+
return new Float32Array([1.0]);
|
|
76553
|
+
}
|
|
76554
|
+
const radius = Math.ceil(sigma * 3);
|
|
76555
|
+
const size = 2 * radius + 1;
|
|
76556
|
+
const kernel = new Float32Array(size);
|
|
76557
|
+
const twoSigmaSq = 2 * sigma * sigma;
|
|
76558
|
+
let sum = 0;
|
|
76559
|
+
for (let i = 0; i < size; i++) {
|
|
76560
|
+
const x = i - radius;
|
|
76561
|
+
kernel[i] = Math.exp(-(x * x) / twoSigmaSq);
|
|
76562
|
+
sum += kernel[i];
|
|
76563
|
+
}
|
|
76564
|
+
// Normalize
|
|
76565
|
+
for (let i = 0; i < size; i++) {
|
|
76566
|
+
kernel[i] /= sum;
|
|
76567
|
+
}
|
|
76568
|
+
return kernel;
|
|
76569
|
+
}
|
|
76570
|
+
/**
|
|
76571
|
+
* In-place single-axis convolution with zero-padding at boundaries.
|
|
76572
|
+
*
|
|
76573
|
+
* Uses a 3-segment approach: left boundary (with bounds check),
|
|
76574
|
+
* middle interior (branch-free, ~95% of work), right boundary
|
|
76575
|
+
* (with bounds check). This eliminates per-element branching
|
|
76576
|
+
* in the hot inner loop.
|
|
76577
|
+
*
|
|
76578
|
+
* @param data Flat float buffer (width × height × depth).
|
|
76579
|
+
* @param width X dimension.
|
|
76580
|
+
* @param height Y dimension.
|
|
76581
|
+
* @param depth Z dimension.
|
|
76582
|
+
* @param axis 0 = X, 1 = Y, 2 = Z.
|
|
76583
|
+
* @param kernel 1D convolution kernel (odd length).
|
|
76584
|
+
*/
|
|
76585
|
+
static convolve1D(data, width, height, depth, axis, kernel) {
|
|
76586
|
+
const kLen = kernel.length;
|
|
76587
|
+
const radius = (kLen - 1) / 2;
|
|
76588
|
+
// Determine the dimension length along the convolution axis
|
|
76589
|
+
// and the stride between consecutive elements along that axis
|
|
76590
|
+
let axisLen;
|
|
76591
|
+
let stride;
|
|
76592
|
+
let outerCount;
|
|
76593
|
+
if (axis === 0) {
|
|
76594
|
+
// X axis: stride = 1, iterate over all (y, z) lines
|
|
76595
|
+
axisLen = width;
|
|
76596
|
+
stride = 1;
|
|
76597
|
+
outerCount = height * depth;
|
|
76598
|
+
}
|
|
76599
|
+
else if (axis === 1) {
|
|
76600
|
+
// Y axis: stride = width, iterate over all (x, z) lines
|
|
76601
|
+
axisLen = height;
|
|
76602
|
+
stride = width;
|
|
76603
|
+
outerCount = width * depth;
|
|
76604
|
+
}
|
|
76605
|
+
else {
|
|
76606
|
+
// Z axis: stride = width * height, iterate over all (x, y) lines
|
|
76607
|
+
axisLen = depth;
|
|
76608
|
+
stride = width * height;
|
|
76609
|
+
outerCount = width * height;
|
|
76610
|
+
}
|
|
76611
|
+
// Precompute middle segment bounds
|
|
76612
|
+
const midStart = radius;
|
|
76613
|
+
const midEnd = axisLen - radius;
|
|
76614
|
+
// Temporary line buffer to avoid read-after-write issues
|
|
76615
|
+
const line = new Float32Array(axisLen);
|
|
76616
|
+
for (let outer = 0; outer < outerCount; outer++) {
|
|
76617
|
+
// Compute the starting index for this line
|
|
76618
|
+
let lineStart;
|
|
76619
|
+
if (axis === 0) {
|
|
76620
|
+
lineStart = outer * width;
|
|
76621
|
+
}
|
|
76622
|
+
else if (axis === 1) {
|
|
76623
|
+
const z = Math.floor(outer / width);
|
|
76624
|
+
const x = outer % width;
|
|
76625
|
+
lineStart = z * width * height + x;
|
|
76626
|
+
}
|
|
76627
|
+
else {
|
|
76628
|
+
lineStart = outer;
|
|
76629
|
+
}
|
|
76630
|
+
// Read the line
|
|
76631
|
+
for (let i = 0; i < axisLen; i++) {
|
|
76632
|
+
line[i] = data[lineStart + i * stride];
|
|
76633
|
+
}
|
|
76634
|
+
// Left boundary segment (i = 0 .. radius-1): lower bound check
|
|
76635
|
+
for (let i = 0; i < midStart; i++) {
|
|
76636
|
+
let sum = 0;
|
|
76637
|
+
for (let k = 0; k < kLen; k++) {
|
|
76638
|
+
const j = i + k - radius;
|
|
76639
|
+
if (j >= 0) {
|
|
76640
|
+
sum += line[j] * kernel[k];
|
|
76641
|
+
}
|
|
76642
|
+
}
|
|
76643
|
+
data[lineStart + i * stride] = sum;
|
|
76644
|
+
}
|
|
76645
|
+
// Middle segment (i = radius .. axisLen-radius-1): NO bounds check
|
|
76646
|
+
for (let i = midStart; i < midEnd; i++) {
|
|
76647
|
+
let sum = 0;
|
|
76648
|
+
const lineOffset = i - radius;
|
|
76649
|
+
for (let k = 0; k < kLen; k++) {
|
|
76650
|
+
sum += line[lineOffset + k] * kernel[k];
|
|
76651
|
+
}
|
|
76652
|
+
data[lineStart + i * stride] = sum;
|
|
76653
|
+
}
|
|
76654
|
+
// Right boundary segment (i = axisLen-radius .. axisLen-1): upper bound check
|
|
76655
|
+
for (let i = midEnd; i < axisLen; i++) {
|
|
76656
|
+
let sum = 0;
|
|
76657
|
+
for (let k = 0; k < kLen; k++) {
|
|
76658
|
+
const j = i + k - radius;
|
|
76659
|
+
if (j < axisLen) {
|
|
76660
|
+
sum += line[j] * kernel[k];
|
|
76661
|
+
}
|
|
76662
|
+
}
|
|
76663
|
+
data[lineStart + i * stride] = sum;
|
|
76664
|
+
}
|
|
76665
|
+
}
|
|
76666
|
+
}
|
|
76667
|
+
}
|
|
76668
|
+
|
|
76463
76669
|
/**
|
|
76464
76670
|
* Manages layer/channel state: active selection, visibility, and channel colors.
|
|
76465
76671
|
*
|
|
@@ -76976,6 +77182,8 @@ class SliceRenderPipeline extends BaseTool {
|
|
|
76976
77182
|
* Then update the changedWidth and changedHeight based on the sizeFactor.
|
|
76977
77183
|
*/
|
|
76978
77184
|
updateOriginAndChangedWH() {
|
|
77185
|
+
const prevW = this.ctx.nrrd_states.image.originWidth;
|
|
77186
|
+
const prevH = this.ctx.nrrd_states.image.originHeight;
|
|
76979
77187
|
this.ctx.nrrd_states.image.originWidth =
|
|
76980
77188
|
this.ctx.protectedData.canvases.originCanvas.width;
|
|
76981
77189
|
this.ctx.nrrd_states.image.originHeight =
|
|
@@ -76984,7 +77192,12 @@ class SliceRenderPipeline extends BaseTool {
|
|
|
76984
77192
|
// Setting them here would defeat the sizeChanged detection in resizePaintArea,
|
|
76985
77193
|
// causing canvas elements to keep stale dimensions after axis switches.
|
|
76986
77194
|
this.resizePaintArea(this.ctx.nrrd_states.view.sizeFactor);
|
|
76987
|
-
|
|
77195
|
+
// Only re-center when origin dimensions changed (e.g. axis switch).
|
|
77196
|
+
// Contrast toggle doesn't change origin size, so skip to preserve user's pan/zoom.
|
|
77197
|
+
if (prevW !== this.ctx.nrrd_states.image.originWidth ||
|
|
77198
|
+
prevH !== this.ctx.nrrd_states.image.originHeight) {
|
|
77199
|
+
this.resetPaintAreaUIPosition();
|
|
77200
|
+
}
|
|
76988
77201
|
}
|
|
76989
77202
|
/**
|
|
76990
77203
|
* Keep all contrast slice index to same.
|
|
@@ -77198,7 +77411,7 @@ class NrrdTools {
|
|
|
77198
77411
|
// Wire RenderingUtils' setEmptyCanvasSize callback
|
|
77199
77412
|
this.drawCore.renderer.setEmptyCanvasSize = (axis) => this.setEmptyCanvasSize(axis);
|
|
77200
77413
|
this.init();
|
|
77201
|
-
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));
|
|
77414
|
+
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), this.drawCore.renderer.compositeAllLayers.bind(this.drawCore.renderer));
|
|
77202
77415
|
// Inject EventRouter into DragOperator for centralized event handling
|
|
77203
77416
|
if (this.drawCore.eventRouter) {
|
|
77204
77417
|
this.dragOperator.setEventRouter(this.drawCore.eventRouter);
|
|
@@ -77517,7 +77730,8 @@ class NrrdTools {
|
|
|
77517
77730
|
getPencilColor() {
|
|
77518
77731
|
return this.state.gui_states.drawing.color;
|
|
77519
77732
|
}
|
|
77520
|
-
executeAction(action) {
|
|
77733
|
+
executeAction(action, opts) {
|
|
77734
|
+
var _a;
|
|
77521
77735
|
switch (action) {
|
|
77522
77736
|
case "undo":
|
|
77523
77737
|
this.undo();
|
|
@@ -77555,6 +77769,48 @@ class NrrdTools {
|
|
|
77555
77769
|
enableDownload(config);
|
|
77556
77770
|
break;
|
|
77557
77771
|
}
|
|
77772
|
+
case "gaussianSmooth": {
|
|
77773
|
+
const layerId = this.state.gui_states.layerChannel.layer;
|
|
77774
|
+
const channel = this.state.gui_states.layerChannel.activeChannel;
|
|
77775
|
+
const volume = this.drawCore.renderer.getVolumeForLayer(layerId);
|
|
77776
|
+
if (volume && channel > 0) {
|
|
77777
|
+
const dims = volume.getDimensions();
|
|
77778
|
+
// 1. Snapshot all Z-slices before mutation (undo support)
|
|
77779
|
+
const deltas = [];
|
|
77780
|
+
for (let z = 0; z < dims.depth; z++) {
|
|
77781
|
+
const sliceData = volume.getSliceUint8(z, "z").data;
|
|
77782
|
+
const hasChannel = sliceData.some((v) => v === channel);
|
|
77783
|
+
if (hasChannel) {
|
|
77784
|
+
deltas.push({
|
|
77785
|
+
layerId,
|
|
77786
|
+
axis: "z",
|
|
77787
|
+
sliceIndex: z,
|
|
77788
|
+
oldSlice: sliceData.slice(),
|
|
77789
|
+
newSlice: new Uint8Array(0), // placeholder, filled after smoothing
|
|
77790
|
+
});
|
|
77791
|
+
}
|
|
77792
|
+
}
|
|
77793
|
+
// 2. Apply smoothing with anisotropic spacing
|
|
77794
|
+
const spacing = this.state.nrrd_states.image.voxelSpacing;
|
|
77795
|
+
const spacingTuple = spacing.length >= 3 ? [spacing[0], spacing[1], spacing[2]] : undefined;
|
|
77796
|
+
const sigma = (_a = opts === null || opts === void 0 ? void 0 : opts.sigma) !== null && _a !== void 0 ? _a : 1.0;
|
|
77797
|
+
GaussianSmoother.gaussianSmooth3D(volume, channel, sigma, spacingTuple);
|
|
77798
|
+
// 3. Capture newSlice data after smoothing and push undo group
|
|
77799
|
+
for (const delta of deltas) {
|
|
77800
|
+
delta.newSlice = volume.getSliceUint8(delta.sliceIndex, "z").data.slice();
|
|
77801
|
+
}
|
|
77802
|
+
if (deltas.length > 0) {
|
|
77803
|
+
this.drawCore.undoManager.pushGroup(deltas);
|
|
77804
|
+
}
|
|
77805
|
+
// 4. Notify backend for each changed slice
|
|
77806
|
+
for (const delta of deltas) {
|
|
77807
|
+
const { data: sliceData, width, height } = volume.getSliceUint8(delta.sliceIndex, "z");
|
|
77808
|
+
this.state.annotationCallbacks.onMaskChanged(sliceData, layerId, channel, delta.sliceIndex, "z", width, height, false);
|
|
77809
|
+
}
|
|
77810
|
+
this.reloadMasksFromVolume();
|
|
77811
|
+
}
|
|
77812
|
+
break;
|
|
77813
|
+
}
|
|
77558
77814
|
}
|
|
77559
77815
|
}
|
|
77560
77816
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -78269,7 +78525,7 @@ function evaluateElement(element, weights) {
|
|
|
78269
78525
|
}
|
|
78270
78526
|
|
|
78271
78527
|
// import * as kiwrious from "copper3d_plugin_heart_k";
|
|
78272
|
-
const REVISION = "v3.2.
|
|
78528
|
+
const REVISION = "v3.2.1-beta";
|
|
78273
78529
|
console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
|
|
78274
78530
|
|
|
78275
|
-
export { CHANNEL_COLORS, CHANNEL_HEX_COLORS, CameraViewPoint, Copper3dTrackballControls, MeshNodeTool, NrrdTools, REVISION, addBoxHelper, addLabelToScene, configKiwriousHeart, convert3DPostoScreenPos, convertScreenPosto3DPos, copperMScene, copperMSceneRenderer, copperRenderer, copperRendererOnDemond, copperScene, copperSceneOnDemond, createTexture2D_NRRD, fullScreenListenner, kiwrious, loading, removeGuiFolderChilden, rgbaToCss, rgbaToHex, setHDRFilePath, throttle };
|
|
78531
|
+
export { CHANNEL_COLORS, CHANNEL_HEX_COLORS, CameraViewPoint, Copper3dTrackballControls, GaussianSmoother, MeshNodeTool, NrrdTools, REVISION, addBoxHelper, addLabelToScene, configKiwriousHeart, convert3DPostoScreenPos, convertScreenPosto3DPos, copperMScene, copperMSceneRenderer, copperRenderer, copperRendererOnDemond, copperScene, copperSceneOnDemond, createTexture2D_NRRD, fullScreenListenner, kiwrious, loading, removeGuiFolderChilden, rgbaToCss, rgbaToHex, setHDRFilePath, throttle };
|