copper3d 3.2.0 → 3.3.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/dist/Renderer/baseRenderer.js +5 -3
- package/dist/Renderer/baseRenderer.js.map +1 -1
- package/dist/Scene/baseScene.js +18 -4
- package/dist/Scene/baseScene.js.map +1 -1
- package/dist/Scene/copperScene.d.ts +1 -0
- package/dist/Scene/copperScene.js +17 -4
- package/dist/Scene/copperScene.js.map +1 -1
- 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 +321 -36
- package/dist/bundle.umd.js +321 -35
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/types/Scene/copperScene.d.ts +1 -0
- 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.umd.js
CHANGED
|
@@ -49812,8 +49812,8 @@ void main() {
|
|
|
49812
49812
|
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
|
|
49813
49813
|
};
|
|
49814
49814
|
this.renderer = renderer;
|
|
49815
|
-
this.ambientLight = new AmbientLight(
|
|
49816
|
-
this.directionalLight = new DirectionalLight(0xffffff, 0
|
|
49815
|
+
this.ambientLight = new AmbientLight(0x606060, 0.8);
|
|
49816
|
+
this.directionalLight = new DirectionalLight(0xffffff, 1.0);
|
|
49817
49817
|
if (!(opt === null || opt === void 0 ? void 0 : opt.alpha)) {
|
|
49818
49818
|
this.vignette = createBackground({
|
|
49819
49819
|
aspect: this.container.clientWidth / this.container.clientHeight,
|
|
@@ -49897,17 +49897,31 @@ void main() {
|
|
|
49897
49897
|
this.scene.add(obj);
|
|
49898
49898
|
}
|
|
49899
49899
|
addLights() {
|
|
49900
|
-
|
|
49900
|
+
// Hemisphere light: sky/ground for broad ambient fill
|
|
49901
|
+
const hemiLight = new HemisphereLight(0xddeeff, 0x0f0e0d, 0.6);
|
|
49901
49902
|
hemiLight.name = "hemi_light";
|
|
49902
49903
|
this.scene.add(hemiLight);
|
|
49904
|
+
// Main key light — front-right, slightly above
|
|
49903
49905
|
this.ambientLight.name = "ambient_light";
|
|
49904
49906
|
this.directionalLight.name = "main_light";
|
|
49905
|
-
this.directionalLight.position.set(0.5, 0, 0.866);
|
|
49907
|
+
this.directionalLight.position.set(0.5, 0.5, 0.866);
|
|
49906
49908
|
this.camera.add(this.ambientLight);
|
|
49907
49909
|
this.camera.add(this.directionalLight);
|
|
49910
|
+
// Fill light — opposite side to soften shadows
|
|
49911
|
+
const fillLight = new DirectionalLight(0xffffff, 0.4);
|
|
49912
|
+
fillLight.name = "fill_light";
|
|
49913
|
+
fillLight.position.set(-0.5, -0.2, -0.866);
|
|
49914
|
+
this.camera.add(fillLight);
|
|
49915
|
+
// Top light — illuminates the top surface that key/fill lights miss
|
|
49916
|
+
const topLight = new DirectionalLight(0xffffff, 0.5);
|
|
49917
|
+
topLight.name = "top_light";
|
|
49918
|
+
topLight.position.set(0, 1, 0.2);
|
|
49919
|
+
this.camera.add(topLight);
|
|
49908
49920
|
this.lights.push(this.ambientLight);
|
|
49909
49921
|
this.lights.push(this.directionalLight);
|
|
49910
49922
|
this.lights.push(hemiLight);
|
|
49923
|
+
this.lights.push(fillLight);
|
|
49924
|
+
this.lights.push(topLight);
|
|
49911
49925
|
}
|
|
49912
49926
|
removeLights() {
|
|
49913
49927
|
if (this.lights) {
|
|
@@ -50611,6 +50625,8 @@ void main() {
|
|
|
50611
50625
|
}
|
|
50612
50626
|
// this.renderer.useLegacyLights = true;
|
|
50613
50627
|
this.renderer.outputColorSpace = SRGBColorSpace;
|
|
50628
|
+
this.renderer.toneMapping = ACESFilmicToneMapping;
|
|
50629
|
+
this.renderer.toneMappingExposure = 1.8;
|
|
50614
50630
|
this.gui = null;
|
|
50615
50631
|
this.stats = new Stats$1();
|
|
50616
50632
|
this.pmremGenerator = new PMREMGenerator(this.renderer);
|
|
@@ -50636,9 +50652,9 @@ void main() {
|
|
|
50636
50652
|
grid: false,
|
|
50637
50653
|
// Lights
|
|
50638
50654
|
addLights: true,
|
|
50639
|
-
exposure: 1.
|
|
50640
|
-
ambientIntensity: 0.
|
|
50641
|
-
ambientColor:
|
|
50655
|
+
exposure: 1.8,
|
|
50656
|
+
ambientIntensity: 0.8,
|
|
50657
|
+
ambientColor: 0x606060,
|
|
50642
50658
|
directIntensity: 0.8 * Math.PI,
|
|
50643
50659
|
directColor: 0xffffff,
|
|
50644
50660
|
bgColor1: "#5454ad",
|
|
@@ -59435,12 +59451,25 @@ void main() {
|
|
|
59435
59451
|
const loader = copperGltfLoader(this.renderer);
|
|
59436
59452
|
loader.load(url, (glb) => {
|
|
59437
59453
|
const content = glb.scene;
|
|
59454
|
+
// Enhance PBR materials for better visual quality
|
|
59455
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.enhanceMaterial) !== false) {
|
|
59456
|
+
content.traverse((child) => {
|
|
59457
|
+
if (child.isMesh) {
|
|
59458
|
+
const mesh = child;
|
|
59459
|
+
const mat = mesh.material;
|
|
59460
|
+
if (mat.isMeshStandardMaterial) {
|
|
59461
|
+
mat.roughness = Math.min(mat.roughness, 0.35);
|
|
59462
|
+
mat.metalness = Math.max(mat.metalness, 0.05);
|
|
59463
|
+
mat.envMapIntensity = 1.2;
|
|
59464
|
+
mat.side = DoubleSide;
|
|
59465
|
+
mat.needsUpdate = true;
|
|
59466
|
+
}
|
|
59467
|
+
}
|
|
59468
|
+
});
|
|
59469
|
+
}
|
|
59438
59470
|
this.scene.add(content);
|
|
59439
59471
|
!!callback && callback(content);
|
|
59440
|
-
},
|
|
59441
|
-
(xhr) => { },
|
|
59442
|
-
// called when loading has errors
|
|
59443
|
-
(error) => {
|
|
59472
|
+
}, (xhr) => { }, (error) => {
|
|
59444
59473
|
console.log("An error happened: ", error);
|
|
59445
59474
|
});
|
|
59446
59475
|
}
|
|
@@ -71395,6 +71424,17 @@ void main() {
|
|
|
71395
71424
|
getChannels() {
|
|
71396
71425
|
return this.numChannels;
|
|
71397
71426
|
}
|
|
71427
|
+
/**
|
|
71428
|
+
* Return the number of bytes in one complete z-slice.
|
|
71429
|
+
*
|
|
71430
|
+
* Equal to `width × height × channels`. Useful for direct buffer
|
|
71431
|
+
* index arithmetic in performance-critical code paths.
|
|
71432
|
+
*
|
|
71433
|
+
* @returns Bytes per z-slice.
|
|
71434
|
+
*/
|
|
71435
|
+
getBytesPerSlice() {
|
|
71436
|
+
return this.bytesPerSlice;
|
|
71437
|
+
}
|
|
71398
71438
|
/**
|
|
71399
71439
|
* Return total memory used by the backing buffer, in bytes.
|
|
71400
71440
|
*
|
|
@@ -73212,8 +73252,8 @@ void main() {
|
|
|
73212
73252
|
}
|
|
73213
73253
|
}
|
|
73214
73254
|
}
|
|
73215
|
-
// Composite all layers to master canvas
|
|
73216
|
-
this.compositeAllLayers();
|
|
73255
|
+
// Composite all layers to master canvas (uses per-layer alpha)
|
|
73256
|
+
this.callbacks.compositeAllLayers();
|
|
73217
73257
|
}
|
|
73218
73258
|
// Refresh sphere overlay from volume for the new slice
|
|
73219
73259
|
if (this.ctx.gui_states.mode.sphere) {
|
|
@@ -73222,23 +73262,6 @@ void main() {
|
|
|
73222
73262
|
view.switchSliceFlag = false;
|
|
73223
73263
|
}
|
|
73224
73264
|
}
|
|
73225
|
-
/**
|
|
73226
|
-
* Composite all visible layer canvases to the master display canvas.
|
|
73227
|
-
*/
|
|
73228
|
-
compositeAllLayers() {
|
|
73229
|
-
const masterCtx = this.ctx.protectedData.ctxes.drawingLayerMasterCtx;
|
|
73230
|
-
const width = this.ctx.nrrd_states.view.changedWidth;
|
|
73231
|
-
const height = this.ctx.nrrd_states.view.changedHeight;
|
|
73232
|
-
masterCtx.clearRect(0, 0, width, height);
|
|
73233
|
-
// Master stores full-alpha composite; globalAlpha applied in start() render loop.
|
|
73234
|
-
for (const layerId of this.ctx.nrrd_states.image.layers) {
|
|
73235
|
-
if (!this.ctx.gui_states.layerChannel.layerVisibility[layerId])
|
|
73236
|
-
continue;
|
|
73237
|
-
const target = this.ctx.protectedData.layerTargets.get(layerId);
|
|
73238
|
-
if (target)
|
|
73239
|
-
masterCtx.drawImage(target.canvas, 0, 0, width, height);
|
|
73240
|
-
}
|
|
73241
|
-
}
|
|
73242
73265
|
// ===== Canvas Cleanup =====
|
|
73243
73266
|
cleanCanvases(flag) {
|
|
73244
73267
|
if (flag) {
|
|
@@ -73280,7 +73303,7 @@ void main() {
|
|
|
73280
73303
|
}
|
|
73281
73304
|
|
|
73282
73305
|
class DragOperator {
|
|
73283
|
-
constructor(container, nrrd_sates, gui_states, protectedData, drawingPrameters, setSyncsliceNum, setIsDrawFalse, flipDisplayImageByAxis, setEmptyCanvasSize, getOrCreateSliceBuffer, renderSliceToCanvas) {
|
|
73306
|
+
constructor(container, nrrd_sates, gui_states, protectedData, drawingPrameters, setSyncsliceNum, setIsDrawFalse, flipDisplayImageByAxis, setEmptyCanvasSize, getOrCreateSliceBuffer, renderSliceToCanvas, compositeAllLayers) {
|
|
73284
73307
|
this.dragPrameters = {
|
|
73285
73308
|
move: 0,
|
|
73286
73309
|
y: 0,
|
|
@@ -73316,6 +73339,7 @@ void main() {
|
|
|
73316
73339
|
this.setEmptyCanvasSize = setEmptyCanvasSize;
|
|
73317
73340
|
this.getOrCreateSliceBuffer = getOrCreateSliceBuffer;
|
|
73318
73341
|
this.renderSliceToCanvas = renderSliceToCanvas;
|
|
73342
|
+
this.compositeAllLayers = compositeAllLayers;
|
|
73319
73343
|
this.showDragNumberDiv = createShowSliceNumberDiv();
|
|
73320
73344
|
this.init();
|
|
73321
73345
|
}
|
|
@@ -73341,6 +73365,7 @@ void main() {
|
|
|
73341
73365
|
setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
|
|
73342
73366
|
getOrCreateSliceBuffer: (axis) => this.getOrCreateSliceBuffer(axis),
|
|
73343
73367
|
renderSliceToCanvas: (layer, axis, sliceIndex, buffer, targetCtx, w, h) => this.renderSliceToCanvas(layer, axis, sliceIndex, buffer, targetCtx, w, h),
|
|
73368
|
+
compositeAllLayers: () => this.compositeAllLayers(),
|
|
73344
73369
|
refreshSphereOverlay: () => { var _a; return (_a = this.refreshSphereOverlayCb) === null || _a === void 0 ? void 0 : _a.call(this); },
|
|
73345
73370
|
}, this.showDragNumberDiv, dragEffectCanvases);
|
|
73346
73371
|
}
|
|
@@ -76468,6 +76493,216 @@ void main() {
|
|
|
76468
76493
|
}
|
|
76469
76494
|
}
|
|
76470
76495
|
|
|
76496
|
+
/**
|
|
76497
|
+
* GaussianSmoother — 3D Gaussian smoothing for segmentation masks.
|
|
76498
|
+
*
|
|
76499
|
+
* Applies a separable 3D Gaussian blur to a single label channel within a
|
|
76500
|
+
* MaskVolume, then thresholds the result back to a binary mask. This smooths
|
|
76501
|
+
* jagged edges and fills small holes in segmentation annotations.
|
|
76502
|
+
*
|
|
76503
|
+
* Pure, stateless utility — no DOM/Canvas/GUI dependencies.
|
|
76504
|
+
*
|
|
76505
|
+
* Performance: uses direct typed-array access (bypassing getVoxel/setVoxel
|
|
76506
|
+
* boundary checks) and branch-free convolution for the interior region.
|
|
76507
|
+
*/
|
|
76508
|
+
class GaussianSmoother {
|
|
76509
|
+
/**
|
|
76510
|
+
* Smooth a single label channel in-place using separable 3D Gaussian blur.
|
|
76511
|
+
*
|
|
76512
|
+
* Steps:
|
|
76513
|
+
* 1. Extract — create a Float32Array with 1.0 where voxel === channel, 0.0 elsewhere.
|
|
76514
|
+
* 2. Blur — apply separable Gaussian (X → Y → Z) on the float buffer.
|
|
76515
|
+
* 3. Threshold — binarize at 0.5.
|
|
76516
|
+
* 4. Write back — overwrite/erase voxels according to the thresholded result.
|
|
76517
|
+
*
|
|
76518
|
+
* @param volume The MaskVolume to operate on (modified in-place).
|
|
76519
|
+
* @param channel The label value to smooth (must be > 0).
|
|
76520
|
+
* @param sigma Base Gaussian sigma in voxel units (default 1.0).
|
|
76521
|
+
* @param spacing Optional voxel spacing [sx, sy, sz]. When provided, per-axis
|
|
76522
|
+
* sigma is computed as `sigma / spacing[axis]` so that the
|
|
76523
|
+
* physical smoothing radius is isotropic.
|
|
76524
|
+
*/
|
|
76525
|
+
static gaussianSmooth3D(volume, channel, sigma = 1.0, spacing) {
|
|
76526
|
+
const dims = volume.getDimensions();
|
|
76527
|
+
const { width, height, depth } = dims;
|
|
76528
|
+
const totalVoxels = width * height * depth;
|
|
76529
|
+
// Direct access to underlying buffer — bypasses getVoxel/setVoxel
|
|
76530
|
+
// boundary checks for ~10x speedup in hot loops.
|
|
76531
|
+
const rawData = volume.getRawData();
|
|
76532
|
+
const channels = volume.getChannels();
|
|
76533
|
+
const bytesPerSlice = volume.getBytesPerSlice();
|
|
76534
|
+
const rowStride = width * channels;
|
|
76535
|
+
// Compute per-axis sigma (normalized to voxel units)
|
|
76536
|
+
const sigmaX = spacing ? sigma / spacing[0] : sigma;
|
|
76537
|
+
const sigmaY = spacing ? sigma / spacing[1] : sigma;
|
|
76538
|
+
const sigmaZ = spacing ? sigma / spacing[2] : sigma;
|
|
76539
|
+
// 1. Extract binary float buffer — direct array access
|
|
76540
|
+
const buffer = new Float32Array(totalVoxels);
|
|
76541
|
+
for (let z = 0; z < depth; z++) {
|
|
76542
|
+
const zOffset = z * bytesPerSlice;
|
|
76543
|
+
for (let y = 0; y < height; y++) {
|
|
76544
|
+
const zyOffset = zOffset + y * rowStride;
|
|
76545
|
+
for (let x = 0; x < width; x++) {
|
|
76546
|
+
const bufIdx = z * height * width + y * width + x;
|
|
76547
|
+
buffer[bufIdx] = rawData[zyOffset + x * channels] === channel ? 1.0 : 0.0;
|
|
76548
|
+
}
|
|
76549
|
+
}
|
|
76550
|
+
}
|
|
76551
|
+
// 2. Separable Gaussian blur: X → Y → Z
|
|
76552
|
+
const kernelX = GaussianSmoother.generateKernel1D(sigmaX);
|
|
76553
|
+
const kernelY = GaussianSmoother.generateKernel1D(sigmaY);
|
|
76554
|
+
const kernelZ = GaussianSmoother.generateKernel1D(sigmaZ);
|
|
76555
|
+
GaussianSmoother.convolve1D(buffer, width, height, depth, 0, kernelX); // X axis
|
|
76556
|
+
GaussianSmoother.convolve1D(buffer, width, height, depth, 1, kernelY); // Y axis
|
|
76557
|
+
GaussianSmoother.convolve1D(buffer, width, height, depth, 2, kernelZ); // Z axis
|
|
76558
|
+
// 3 & 4. Threshold and write back — direct array access
|
|
76559
|
+
for (let z = 0; z < depth; z++) {
|
|
76560
|
+
const zOffset = z * bytesPerSlice;
|
|
76561
|
+
for (let y = 0; y < height; y++) {
|
|
76562
|
+
const zyOffset = zOffset + y * rowStride;
|
|
76563
|
+
for (let x = 0; x < width; x++) {
|
|
76564
|
+
const bufIdx = z * height * width + y * width + x;
|
|
76565
|
+
const smoothed = buffer[bufIdx] >= 0.5 ? 1 : 0;
|
|
76566
|
+
const rawIdx = zyOffset + x * channels;
|
|
76567
|
+
const original = rawData[rawIdx];
|
|
76568
|
+
if (smoothed === 1 && original !== channel) {
|
|
76569
|
+
// Smoothed region expands into this voxel — overwrite
|
|
76570
|
+
rawData[rawIdx] = channel;
|
|
76571
|
+
}
|
|
76572
|
+
else if (smoothed === 0 && original === channel) {
|
|
76573
|
+
// Smoothed region retreats — erase
|
|
76574
|
+
rawData[rawIdx] = 0;
|
|
76575
|
+
}
|
|
76576
|
+
// Otherwise leave unchanged
|
|
76577
|
+
}
|
|
76578
|
+
}
|
|
76579
|
+
}
|
|
76580
|
+
}
|
|
76581
|
+
/**
|
|
76582
|
+
* Generate a normalized 1D Gaussian kernel truncated at ±3σ.
|
|
76583
|
+
*
|
|
76584
|
+
* @param sigma Standard deviation (in voxels). Must be > 0.
|
|
76585
|
+
* @returns Normalized Float32Array of odd length.
|
|
76586
|
+
*/
|
|
76587
|
+
static generateKernel1D(sigma) {
|
|
76588
|
+
if (sigma <= 0) {
|
|
76589
|
+
return new Float32Array([1.0]);
|
|
76590
|
+
}
|
|
76591
|
+
const radius = Math.ceil(sigma * 3);
|
|
76592
|
+
const size = 2 * radius + 1;
|
|
76593
|
+
const kernel = new Float32Array(size);
|
|
76594
|
+
const twoSigmaSq = 2 * sigma * sigma;
|
|
76595
|
+
let sum = 0;
|
|
76596
|
+
for (let i = 0; i < size; i++) {
|
|
76597
|
+
const x = i - radius;
|
|
76598
|
+
kernel[i] = Math.exp(-(x * x) / twoSigmaSq);
|
|
76599
|
+
sum += kernel[i];
|
|
76600
|
+
}
|
|
76601
|
+
// Normalize
|
|
76602
|
+
for (let i = 0; i < size; i++) {
|
|
76603
|
+
kernel[i] /= sum;
|
|
76604
|
+
}
|
|
76605
|
+
return kernel;
|
|
76606
|
+
}
|
|
76607
|
+
/**
|
|
76608
|
+
* In-place single-axis convolution with zero-padding at boundaries.
|
|
76609
|
+
*
|
|
76610
|
+
* Uses a 3-segment approach: left boundary (with bounds check),
|
|
76611
|
+
* middle interior (branch-free, ~95% of work), right boundary
|
|
76612
|
+
* (with bounds check). This eliminates per-element branching
|
|
76613
|
+
* in the hot inner loop.
|
|
76614
|
+
*
|
|
76615
|
+
* @param data Flat float buffer (width × height × depth).
|
|
76616
|
+
* @param width X dimension.
|
|
76617
|
+
* @param height Y dimension.
|
|
76618
|
+
* @param depth Z dimension.
|
|
76619
|
+
* @param axis 0 = X, 1 = Y, 2 = Z.
|
|
76620
|
+
* @param kernel 1D convolution kernel (odd length).
|
|
76621
|
+
*/
|
|
76622
|
+
static convolve1D(data, width, height, depth, axis, kernel) {
|
|
76623
|
+
const kLen = kernel.length;
|
|
76624
|
+
const radius = (kLen - 1) / 2;
|
|
76625
|
+
// Determine the dimension length along the convolution axis
|
|
76626
|
+
// and the stride between consecutive elements along that axis
|
|
76627
|
+
let axisLen;
|
|
76628
|
+
let stride;
|
|
76629
|
+
let outerCount;
|
|
76630
|
+
if (axis === 0) {
|
|
76631
|
+
// X axis: stride = 1, iterate over all (y, z) lines
|
|
76632
|
+
axisLen = width;
|
|
76633
|
+
stride = 1;
|
|
76634
|
+
outerCount = height * depth;
|
|
76635
|
+
}
|
|
76636
|
+
else if (axis === 1) {
|
|
76637
|
+
// Y axis: stride = width, iterate over all (x, z) lines
|
|
76638
|
+
axisLen = height;
|
|
76639
|
+
stride = width;
|
|
76640
|
+
outerCount = width * depth;
|
|
76641
|
+
}
|
|
76642
|
+
else {
|
|
76643
|
+
// Z axis: stride = width * height, iterate over all (x, y) lines
|
|
76644
|
+
axisLen = depth;
|
|
76645
|
+
stride = width * height;
|
|
76646
|
+
outerCount = width * height;
|
|
76647
|
+
}
|
|
76648
|
+
// Precompute middle segment bounds
|
|
76649
|
+
const midStart = radius;
|
|
76650
|
+
const midEnd = axisLen - radius;
|
|
76651
|
+
// Temporary line buffer to avoid read-after-write issues
|
|
76652
|
+
const line = new Float32Array(axisLen);
|
|
76653
|
+
for (let outer = 0; outer < outerCount; outer++) {
|
|
76654
|
+
// Compute the starting index for this line
|
|
76655
|
+
let lineStart;
|
|
76656
|
+
if (axis === 0) {
|
|
76657
|
+
lineStart = outer * width;
|
|
76658
|
+
}
|
|
76659
|
+
else if (axis === 1) {
|
|
76660
|
+
const z = Math.floor(outer / width);
|
|
76661
|
+
const x = outer % width;
|
|
76662
|
+
lineStart = z * width * height + x;
|
|
76663
|
+
}
|
|
76664
|
+
else {
|
|
76665
|
+
lineStart = outer;
|
|
76666
|
+
}
|
|
76667
|
+
// Read the line
|
|
76668
|
+
for (let i = 0; i < axisLen; i++) {
|
|
76669
|
+
line[i] = data[lineStart + i * stride];
|
|
76670
|
+
}
|
|
76671
|
+
// Left boundary segment (i = 0 .. radius-1): lower bound check
|
|
76672
|
+
for (let i = 0; i < midStart; i++) {
|
|
76673
|
+
let sum = 0;
|
|
76674
|
+
for (let k = 0; k < kLen; k++) {
|
|
76675
|
+
const j = i + k - radius;
|
|
76676
|
+
if (j >= 0) {
|
|
76677
|
+
sum += line[j] * kernel[k];
|
|
76678
|
+
}
|
|
76679
|
+
}
|
|
76680
|
+
data[lineStart + i * stride] = sum;
|
|
76681
|
+
}
|
|
76682
|
+
// Middle segment (i = radius .. axisLen-radius-1): NO bounds check
|
|
76683
|
+
for (let i = midStart; i < midEnd; i++) {
|
|
76684
|
+
let sum = 0;
|
|
76685
|
+
const lineOffset = i - radius;
|
|
76686
|
+
for (let k = 0; k < kLen; k++) {
|
|
76687
|
+
sum += line[lineOffset + k] * kernel[k];
|
|
76688
|
+
}
|
|
76689
|
+
data[lineStart + i * stride] = sum;
|
|
76690
|
+
}
|
|
76691
|
+
// Right boundary segment (i = axisLen-radius .. axisLen-1): upper bound check
|
|
76692
|
+
for (let i = midEnd; i < axisLen; i++) {
|
|
76693
|
+
let sum = 0;
|
|
76694
|
+
for (let k = 0; k < kLen; k++) {
|
|
76695
|
+
const j = i + k - radius;
|
|
76696
|
+
if (j < axisLen) {
|
|
76697
|
+
sum += line[j] * kernel[k];
|
|
76698
|
+
}
|
|
76699
|
+
}
|
|
76700
|
+
data[lineStart + i * stride] = sum;
|
|
76701
|
+
}
|
|
76702
|
+
}
|
|
76703
|
+
}
|
|
76704
|
+
}
|
|
76705
|
+
|
|
76471
76706
|
/**
|
|
76472
76707
|
* Manages layer/channel state: active selection, visibility, and channel colors.
|
|
76473
76708
|
*
|
|
@@ -76984,6 +77219,8 @@ void main() {
|
|
|
76984
77219
|
* Then update the changedWidth and changedHeight based on the sizeFactor.
|
|
76985
77220
|
*/
|
|
76986
77221
|
updateOriginAndChangedWH() {
|
|
77222
|
+
const prevW = this.ctx.nrrd_states.image.originWidth;
|
|
77223
|
+
const prevH = this.ctx.nrrd_states.image.originHeight;
|
|
76987
77224
|
this.ctx.nrrd_states.image.originWidth =
|
|
76988
77225
|
this.ctx.protectedData.canvases.originCanvas.width;
|
|
76989
77226
|
this.ctx.nrrd_states.image.originHeight =
|
|
@@ -76992,7 +77229,12 @@ void main() {
|
|
|
76992
77229
|
// Setting them here would defeat the sizeChanged detection in resizePaintArea,
|
|
76993
77230
|
// causing canvas elements to keep stale dimensions after axis switches.
|
|
76994
77231
|
this.resizePaintArea(this.ctx.nrrd_states.view.sizeFactor);
|
|
76995
|
-
|
|
77232
|
+
// Only re-center when origin dimensions changed (e.g. axis switch).
|
|
77233
|
+
// Contrast toggle doesn't change origin size, so skip to preserve user's pan/zoom.
|
|
77234
|
+
if (prevW !== this.ctx.nrrd_states.image.originWidth ||
|
|
77235
|
+
prevH !== this.ctx.nrrd_states.image.originHeight) {
|
|
77236
|
+
this.resetPaintAreaUIPosition();
|
|
77237
|
+
}
|
|
76996
77238
|
}
|
|
76997
77239
|
/**
|
|
76998
77240
|
* Keep all contrast slice index to same.
|
|
@@ -77206,7 +77448,7 @@ void main() {
|
|
|
77206
77448
|
// Wire RenderingUtils' setEmptyCanvasSize callback
|
|
77207
77449
|
this.drawCore.renderer.setEmptyCanvasSize = (axis) => this.setEmptyCanvasSize(axis);
|
|
77208
77450
|
this.init();
|
|
77209
|
-
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));
|
|
77451
|
+
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));
|
|
77210
77452
|
// Inject EventRouter into DragOperator for centralized event handling
|
|
77211
77453
|
if (this.drawCore.eventRouter) {
|
|
77212
77454
|
this.dragOperator.setEventRouter(this.drawCore.eventRouter);
|
|
@@ -77525,7 +77767,8 @@ void main() {
|
|
|
77525
77767
|
getPencilColor() {
|
|
77526
77768
|
return this.state.gui_states.drawing.color;
|
|
77527
77769
|
}
|
|
77528
|
-
executeAction(action) {
|
|
77770
|
+
executeAction(action, opts) {
|
|
77771
|
+
var _a;
|
|
77529
77772
|
switch (action) {
|
|
77530
77773
|
case "undo":
|
|
77531
77774
|
this.undo();
|
|
@@ -77563,6 +77806,48 @@ void main() {
|
|
|
77563
77806
|
enableDownload(config);
|
|
77564
77807
|
break;
|
|
77565
77808
|
}
|
|
77809
|
+
case "gaussianSmooth": {
|
|
77810
|
+
const layerId = this.state.gui_states.layerChannel.layer;
|
|
77811
|
+
const channel = this.state.gui_states.layerChannel.activeChannel;
|
|
77812
|
+
const volume = this.drawCore.renderer.getVolumeForLayer(layerId);
|
|
77813
|
+
if (volume && channel > 0) {
|
|
77814
|
+
const dims = volume.getDimensions();
|
|
77815
|
+
// 1. Snapshot all Z-slices before mutation (undo support)
|
|
77816
|
+
const deltas = [];
|
|
77817
|
+
for (let z = 0; z < dims.depth; z++) {
|
|
77818
|
+
const sliceData = volume.getSliceUint8(z, "z").data;
|
|
77819
|
+
const hasChannel = sliceData.some((v) => v === channel);
|
|
77820
|
+
if (hasChannel) {
|
|
77821
|
+
deltas.push({
|
|
77822
|
+
layerId,
|
|
77823
|
+
axis: "z",
|
|
77824
|
+
sliceIndex: z,
|
|
77825
|
+
oldSlice: sliceData.slice(),
|
|
77826
|
+
newSlice: new Uint8Array(0), // placeholder, filled after smoothing
|
|
77827
|
+
});
|
|
77828
|
+
}
|
|
77829
|
+
}
|
|
77830
|
+
// 2. Apply smoothing with anisotropic spacing
|
|
77831
|
+
const spacing = this.state.nrrd_states.image.voxelSpacing;
|
|
77832
|
+
const spacingTuple = spacing.length >= 3 ? [spacing[0], spacing[1], spacing[2]] : undefined;
|
|
77833
|
+
const sigma = (_a = opts === null || opts === void 0 ? void 0 : opts.sigma) !== null && _a !== void 0 ? _a : 1.0;
|
|
77834
|
+
GaussianSmoother.gaussianSmooth3D(volume, channel, sigma, spacingTuple);
|
|
77835
|
+
// 3. Capture newSlice data after smoothing and push undo group
|
|
77836
|
+
for (const delta of deltas) {
|
|
77837
|
+
delta.newSlice = volume.getSliceUint8(delta.sliceIndex, "z").data.slice();
|
|
77838
|
+
}
|
|
77839
|
+
if (deltas.length > 0) {
|
|
77840
|
+
this.drawCore.undoManager.pushGroup(deltas);
|
|
77841
|
+
}
|
|
77842
|
+
// 4. Notify backend for each changed slice
|
|
77843
|
+
for (const delta of deltas) {
|
|
77844
|
+
const { data: sliceData, width, height } = volume.getSliceUint8(delta.sliceIndex, "z");
|
|
77845
|
+
this.state.annotationCallbacks.onMaskChanged(sliceData, layerId, channel, delta.sliceIndex, "z", width, height, false);
|
|
77846
|
+
}
|
|
77847
|
+
this.reloadMasksFromVolume();
|
|
77848
|
+
}
|
|
77849
|
+
break;
|
|
77850
|
+
}
|
|
77566
77851
|
}
|
|
77567
77852
|
}
|
|
77568
77853
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -78277,13 +78562,14 @@ void main() {
|
|
|
78277
78562
|
}
|
|
78278
78563
|
|
|
78279
78564
|
// import * as kiwrious from "copper3d_plugin_heart_k";
|
|
78280
|
-
const REVISION = "v3.
|
|
78565
|
+
const REVISION = "v3.3.0-beta";
|
|
78281
78566
|
console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
|
|
78282
78567
|
|
|
78283
78568
|
exports.CHANNEL_COLORS = CHANNEL_COLORS;
|
|
78284
78569
|
exports.CHANNEL_HEX_COLORS = CHANNEL_HEX_COLORS;
|
|
78285
78570
|
exports.CameraViewPoint = CameraViewPoint;
|
|
78286
78571
|
exports.Copper3dTrackballControls = Copper3dTrackballControls;
|
|
78572
|
+
exports.GaussianSmoother = GaussianSmoother;
|
|
78287
78573
|
exports.MeshNodeTool = MeshNodeTool;
|
|
78288
78574
|
exports.NrrdTools = NrrdTools;
|
|
78289
78575
|
exports.REVISION = REVISION;
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { createTexture2D_NRRD } from "./Utils/texture2d";
|
|
|
13
13
|
import { configKiwriousHeart } from "./Utils/kiwrious/configKiwrious";
|
|
14
14
|
import kiwrious from "./Utils/kiwrious/configKiwrious";
|
|
15
15
|
import { NrrdTools } from "./Utils/segmentation/NrrdTools";
|
|
16
|
+
import { GaussianSmoother } from "./Utils/segmentation/core/GaussianSmoother";
|
|
16
17
|
import { Copper3dTrackballControls } from "./Controls/Copper3dTrackballControls";
|
|
17
18
|
import { MeshNodeTool } from "./Utils/MeshNodeTool";
|
|
18
19
|
import { removeGuiFolderChilden } from "./Utils/segmentation/coreTools/gui";
|
|
@@ -24,6 +25,6 @@ import type { ToolMode, IAnnotationCallbacks } from "./Utils/segmentation/core/t
|
|
|
24
25
|
import { CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss } from "./Utils/segmentation/core/index";
|
|
25
26
|
import type { LayerId, ChannelValue } from "./Utils/segmentation/core/index";
|
|
26
27
|
import "./css/style.css";
|
|
27
|
-
export declare const REVISION = "v3.
|
|
28
|
-
export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, };
|
|
28
|
+
export declare const REVISION = "v3.3.0-beta";
|
|
29
|
+
export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, GaussianSmoother, };
|
|
29
30
|
export type { positionType, screenPosType, optsType, nrrdMeshesType, nrrdSliceType, SensorDecodedValue_kiwrious, SensorReadResult_kiwrious, HeartRateResult_kiwrious, loadingBarType, IPaintImage, exportPaintImageType, IOptVTKLoader, ICommXYZ, IGUIStates, IGuiParameterSettings, INrrdStates, NrrdState, GuiState, IGuiMeta, ToolMode, IAnnotationCallbacks, LayerId, ChannelValue, };
|
package/dist/index.js
CHANGED
|
@@ -14,13 +14,14 @@ import { createTexture2D_NRRD } from "./Utils/texture2d";
|
|
|
14
14
|
import { configKiwriousHeart } from "./Utils/kiwrious/configKiwrious";
|
|
15
15
|
import kiwrious from "./Utils/kiwrious/configKiwrious";
|
|
16
16
|
import { NrrdTools } from "./Utils/segmentation/NrrdTools";
|
|
17
|
+
import { GaussianSmoother } from "./Utils/segmentation/core/GaussianSmoother";
|
|
17
18
|
// Phase 7: Segmentation Module - Unified exports
|
|
18
19
|
import { Copper3dTrackballControls } from "./Controls/Copper3dTrackballControls";
|
|
19
20
|
import { MeshNodeTool } from "./Utils/MeshNodeTool";
|
|
20
21
|
import { removeGuiFolderChilden } from "./Utils/segmentation/coreTools/gui";
|
|
21
22
|
import { CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss } from "./Utils/segmentation/core/index";
|
|
22
23
|
import "./css/style.css";
|
|
23
|
-
export const REVISION = "v3.
|
|
24
|
+
export const REVISION = "v3.3.0-beta";
|
|
24
25
|
console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
|
|
25
|
-
export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, };
|
|
26
|
+
export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, GaussianSmoother, };
|
|
26
27
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EACL,eAAe,EAGf,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAY,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,QAAQ,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,iDAAiD;AAEjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAiB5E,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAG3G,OAAO,iBAAiB,CAAC;AAEzB,MAAM,CAAC,MAAM,QAAQ,GAAG,aAAa,CAAC;AAEtC,OAAO,CAAC,GAAG,CACT,mCAAmC,QAAQ,EAAE,EAC7C,8CAA8C,EAC9C,8CAA8C,CAC/C,CAAC;AAEF,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,SAAS,EACT,OAAO,EACP,yBAAyB,EACzB,oBAAoB,EACpB,YAAY,EACZ,QAAQ,EACR,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,SAAS,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EACL,eAAe,EAGf,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAY,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,QAAQ,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAC9E,iDAAiD;AAEjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAiB5E,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAG3G,OAAO,iBAAiB,CAAC;AAEzB,MAAM,CAAC,MAAM,QAAQ,GAAG,aAAa,CAAC;AAEtC,OAAO,CAAC,GAAG,CACT,mCAAmC,QAAQ,EAAE,EAC7C,8CAA8C,EAC9C,8CAA8C,CAC/C,CAAC;AAEF,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,SAAS,EACT,OAAO,EACP,yBAAyB,EACzB,oBAAoB,EACpB,YAAY,EACZ,QAAQ,EACR,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,gBAAgB,GACjB,CAAC"}
|
|
@@ -18,6 +18,7 @@ export declare class copperScene extends baseScene {
|
|
|
18
18
|
loadGltf(url: string, callback?: (content: THREE.Group) => void): void;
|
|
19
19
|
loadPureGLB(url: string, callback?: (mesh: THREE.Group) => void, opts?: {
|
|
20
20
|
color: string;
|
|
21
|
+
enhanceMaterial?: boolean;
|
|
21
22
|
}): void;
|
|
22
23
|
loadVtk(url: string): void;
|
|
23
24
|
loadVtks(models: Array<vtkModels>): void;
|
|
@@ -18,9 +18,10 @@ export declare class DragOperator {
|
|
|
18
18
|
private setEmptyCanvasSize;
|
|
19
19
|
private getOrCreateSliceBuffer;
|
|
20
20
|
private renderSliceToCanvas;
|
|
21
|
+
private compositeAllLayers;
|
|
21
22
|
private refreshSphereOverlayCb;
|
|
22
23
|
private eventRouter;
|
|
23
|
-
constructor(container: HTMLElement, nrrd_sates: NrrdState, gui_states: GuiState, protectedData: IProtected, drawingPrameters: IDrawingEvents, setSyncsliceNum: () => void, setIsDrawFalse: (target: number) => void, flipDisplayImageByAxis: () => void, setEmptyCanvasSize: (axis?: "x" | "y" | "z") => void, getOrCreateSliceBuffer: (axis: "x" | "y" | "z") => ImageData | null, renderSliceToCanvas: (layer: string, axis: "x" | "y" | "z", sliceIndex: number, buffer: ImageData, targetCtx: CanvasRenderingContext2D, scaledWidth: number, scaledHeight: number) => void);
|
|
24
|
+
constructor(container: HTMLElement, nrrd_sates: NrrdState, gui_states: GuiState, protectedData: IProtected, drawingPrameters: IDrawingEvents, setSyncsliceNum: () => void, setIsDrawFalse: (target: number) => void, flipDisplayImageByAxis: () => void, setEmptyCanvasSize: (axis?: "x" | "y" | "z") => void, getOrCreateSliceBuffer: (axis: "x" | "y" | "z") => ImageData | null, renderSliceToCanvas: (layer: string, axis: "x" | "y" | "z", sliceIndex: number, buffer: ImageData, targetCtx: CanvasRenderingContext2D, scaledWidth: number, scaledHeight: number) => void, compositeAllLayers: () => void);
|
|
24
25
|
private init;
|
|
25
26
|
setShowDragNumberDiv(sliceIndexContainer: HTMLDivElement): void;
|
|
26
27
|
/**
|
|
@@ -88,7 +88,9 @@ export declare class NrrdTools {
|
|
|
88
88
|
getSliderMeta(key: string): IGuiMeta | null;
|
|
89
89
|
setPencilColor(hex: string): void;
|
|
90
90
|
getPencilColor(): string;
|
|
91
|
-
executeAction(action: "undo" | "redo" | "clearActiveSliceMask" | "clearActiveLayerMask" | "resetZoom" | "downloadCurrentMask"
|
|
91
|
+
executeAction(action: "undo" | "redo" | "clearActiveSliceMask" | "clearActiveLayerMask" | "resetZoom" | "downloadCurrentMask" | "gaussianSmooth", opts?: {
|
|
92
|
+
sigma?: number;
|
|
93
|
+
}): void;
|
|
92
94
|
undo(): void;
|
|
93
95
|
redo(): void;
|
|
94
96
|
enterKeyboardConfig(): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GaussianSmoother — 3D Gaussian smoothing for segmentation masks.
|
|
3
|
+
*
|
|
4
|
+
* Applies a separable 3D Gaussian blur to a single label channel within a
|
|
5
|
+
* MaskVolume, then thresholds the result back to a binary mask. This smooths
|
|
6
|
+
* jagged edges and fills small holes in segmentation annotations.
|
|
7
|
+
*
|
|
8
|
+
* Pure, stateless utility — no DOM/Canvas/GUI dependencies.
|
|
9
|
+
*
|
|
10
|
+
* Performance: uses direct typed-array access (bypassing getVoxel/setVoxel
|
|
11
|
+
* boundary checks) and branch-free convolution for the interior region.
|
|
12
|
+
*/
|
|
13
|
+
import type { MaskVolume } from './MaskVolume';
|
|
14
|
+
export declare class GaussianSmoother {
|
|
15
|
+
/**
|
|
16
|
+
* Smooth a single label channel in-place using separable 3D Gaussian blur.
|
|
17
|
+
*
|
|
18
|
+
* Steps:
|
|
19
|
+
* 1. Extract — create a Float32Array with 1.0 where voxel === channel, 0.0 elsewhere.
|
|
20
|
+
* 2. Blur — apply separable Gaussian (X → Y → Z) on the float buffer.
|
|
21
|
+
* 3. Threshold — binarize at 0.5.
|
|
22
|
+
* 4. Write back — overwrite/erase voxels according to the thresholded result.
|
|
23
|
+
*
|
|
24
|
+
* @param volume The MaskVolume to operate on (modified in-place).
|
|
25
|
+
* @param channel The label value to smooth (must be > 0).
|
|
26
|
+
* @param sigma Base Gaussian sigma in voxel units (default 1.0).
|
|
27
|
+
* @param spacing Optional voxel spacing [sx, sy, sz]. When provided, per-axis
|
|
28
|
+
* sigma is computed as `sigma / spacing[axis]` so that the
|
|
29
|
+
* physical smoothing radius is isotropic.
|
|
30
|
+
*/
|
|
31
|
+
static gaussianSmooth3D(volume: MaskVolume, channel: number, sigma?: number, spacing?: [number, number, number]): void;
|
|
32
|
+
/**
|
|
33
|
+
* Generate a normalized 1D Gaussian kernel truncated at ±3σ.
|
|
34
|
+
*
|
|
35
|
+
* @param sigma Standard deviation (in voxels). Must be > 0.
|
|
36
|
+
* @returns Normalized Float32Array of odd length.
|
|
37
|
+
*/
|
|
38
|
+
static generateKernel1D(sigma: number): Float32Array;
|
|
39
|
+
/**
|
|
40
|
+
* In-place single-axis convolution with zero-padding at boundaries.
|
|
41
|
+
*
|
|
42
|
+
* Uses a 3-segment approach: left boundary (with bounds check),
|
|
43
|
+
* middle interior (branch-free, ~95% of work), right boundary
|
|
44
|
+
* (with bounds check). This eliminates per-element branching
|
|
45
|
+
* in the hot inner loop.
|
|
46
|
+
*
|
|
47
|
+
* @param data Flat float buffer (width × height × depth).
|
|
48
|
+
* @param width X dimension.
|
|
49
|
+
* @param height Y dimension.
|
|
50
|
+
* @param depth Z dimension.
|
|
51
|
+
* @param axis 0 = X, 1 = Y, 2 = Z.
|
|
52
|
+
* @param kernel 1D convolution kernel (odd length).
|
|
53
|
+
*/
|
|
54
|
+
private static convolve1D;
|
|
55
|
+
}
|
|
@@ -250,6 +250,15 @@ export declare class MaskVolume {
|
|
|
250
250
|
* @returns Channel count (≥ 1).
|
|
251
251
|
*/
|
|
252
252
|
getChannels(): number;
|
|
253
|
+
/**
|
|
254
|
+
* Return the number of bytes in one complete z-slice.
|
|
255
|
+
*
|
|
256
|
+
* Equal to `width × height × channels`. Useful for direct buffer
|
|
257
|
+
* index arithmetic in performance-critical code paths.
|
|
258
|
+
*
|
|
259
|
+
* @returns Bytes per z-slice.
|
|
260
|
+
*/
|
|
261
|
+
getBytesPerSlice(): number;
|
|
253
262
|
/**
|
|
254
263
|
* Return total memory used by the backing buffer, in bytes.
|
|
255
264
|
*
|
|
@@ -26,10 +26,6 @@ export declare class DragSliceTool extends BaseTool {
|
|
|
26
26
|
setShowDragNumberDiv(div: HTMLDivElement): void;
|
|
27
27
|
updateIndex(move: number): void;
|
|
28
28
|
private drawDragSlice;
|
|
29
|
-
/**
|
|
30
|
-
* Composite all visible layer canvases to the master display canvas.
|
|
31
|
-
*/
|
|
32
|
-
private compositeAllLayers;
|
|
33
29
|
private cleanCanvases;
|
|
34
30
|
updateShowNumDiv(contrastNum: number): void;
|
|
35
31
|
updateCurrentContrastSlice(): any;
|