@niivue/nv-ext-drawing 1.0.0-rc.3 → 1.0.0-rc.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niivue/nv-ext-drawing",
3
- "version": "1.0.0-rc.3",
3
+ "version": "1.0.0-rc.4",
4
4
  "type": "module",
5
5
  "description": "Drawing interpolation and segmentation tools for NiiVue",
6
6
  "main": "./dist/nv-ext-drawing.js",
@@ -16,10 +16,10 @@
16
16
  "dist"
17
17
  ],
18
18
  "peerDependencies": {
19
- "@niivue/niivue": "workspace:*"
19
+ "@niivue/niivue": "1.0.0-rc.4"
20
20
  },
21
21
  "devDependencies": {
22
- "@niivue/niivue": "workspace:*",
22
+ "@niivue/niivue": "1.0.0-rc.4",
23
23
  "typescript": "~5.8.3",
24
24
  "vite": "^6.3.2",
25
25
  "vite-plugin-dts": "^4.5.4"
@@ -27,5 +27,8 @@
27
27
  "scripts": {
28
28
  "build": "npx vite build",
29
29
  "typecheck": "bunx tsc --noEmit"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
30
33
  }
31
34
  }
package/dist/index.d.ts DELETED
@@ -1,100 +0,0 @@
1
- import { DrawingDims, InterpolationOptions, SliceType } from './processing/drawing';
2
- import { Connectivity, MagicWandOptions, MagicWandResult, ThresholdMode } from './processing/magicWand';
3
- export type { Connectivity, DrawingDims, InterpolationOptions, MagicWandOptions, MagicWandResult, SliceType, ThresholdMode, };
4
- /**
5
- * Find the first and last slices containing drawing data along a given axis.
6
- * Runs in a Web Worker.
7
- */
8
- export declare function findDrawingBoundarySlices(sliceType: SliceType, drawBitmap: Uint8Array, dims: DrawingDims): Promise<{
9
- first: number;
10
- last: number;
11
- } | null>;
12
- /**
13
- * Interpolate between drawn slices to fill gaps in the drawing bitmap.
14
- * Runs in a Web Worker. Returns a new Uint8Array with the interpolated bitmap.
15
- *
16
- * Image data (`imageData`) must be in RAS voxel order. Use
17
- * `ctx.backgroundVolume.imgRAS` from the extension context.
18
- */
19
- export declare function interpolateMaskSlices(drawBitmap: Uint8Array, dims: DrawingDims, imageData: ArrayLike<number> | null, maxVal: number, sliceIndexLow: number | undefined, sliceIndexHigh: number | undefined, options?: InterpolationOptions): Promise<Uint8Array>;
20
- /**
21
- * Intensity-based flood fill (magic wand / click-to-segment).
22
- * Runs in a Web Worker. Returns the updated drawing bitmap **and** a
23
- * result summary (filled voxel count, thresholds used, seed intensity).
24
- *
25
- * Image data (`imageData`) must be in RAS voxel order. Use
26
- * `ctx.backgroundVolume.imgRAS` from the extension context.
27
- *
28
- * @param seed - `[x, y, z]` RAS-voxel coordinate of the seed.
29
- * @param drawBitmap - Drawing bitmap in RAS voxel order.
30
- * @param dims - Volume dimensions in RAS space.
31
- * @param imageData - Background intensity volume in RAS voxel order.
32
- * @param options - See {@link MagicWandOptions}.
33
- * @param voxelSizeMM - Voxel size in mm `[sx, sy, sz]` for distance constraint.
34
- */
35
- export declare function magicWand(seed: [number, number, number], drawBitmap: Uint8Array, dims: DrawingDims, imageData: ArrayLike<number>, options?: MagicWandOptions, voxelSizeMM?: [number, number, number]): Promise<{
36
- bitmap: Uint8Array;
37
- result: MagicWandResult;
38
- }>;
39
- /**
40
- * Flood-fill a connected cluster of identically-coloured voxels in the
41
- * drawing bitmap (erase / recolour). Runs in a Web Worker.
42
- *
43
- * @param seed - `[x, y, z]` RAS-voxel coordinate of the seed.
44
- * @param drawBitmap - Drawing bitmap in RAS voxel order.
45
- * @param dims - Volume dimensions in RAS space.
46
- * @param newColor - Colour to apply (0 = erase).
47
- * @param connectivity - Neighbour connectivity (default 6).
48
- */
49
- export declare function magicWandFromBitmap(seed: [number, number, number], drawBitmap: Uint8Array, dims: DrawingDims, newColor: number, connectivity?: Connectivity): Promise<{
50
- bitmap: Uint8Array;
51
- count: number;
52
- }>;
53
- /**
54
- * High-performance magic wand preview using SharedArrayBuffer.
55
- *
56
- * The working bitmap lives in shared memory. The worker restores the
57
- * committed state, runs the flood fill in-place, and posts back only
58
- * the result summary. The main thread reads the updated bitmap
59
- * directly — zero copies on the hot path.
60
- *
61
- * **Requires** cross-origin isolation headers (COOP + COEP) for
62
- * `SharedArrayBuffer` to be available.
63
- *
64
- * Image data must be in RAS voxel order. Use `ctx.backgroundVolume.imgRAS`
65
- * from the extension context.
66
- *
67
- * Usage with the extension context:
68
- * ```ts
69
- * const ctx = nv.createExtensionContext();
70
- * const dr = ctx.drawing;
71
- * const bg = ctx.backgroundVolume;
72
- * const wand = new MagicWandShared(dr.dims, bg.imgRAS, committed, dr.voxelSizeMM);
73
- * await wand.ready;
74
- * ```
75
- */
76
- export declare class MagicWandShared {
77
- /** Uint8Array view over the SharedArrayBuffer — assign to `drawingVolume.img`. */
78
- readonly bitmap: Uint8Array;
79
- /** Resolves when the worker is initialized and ready for preview calls. */
80
- readonly ready: Promise<void>;
81
- private _worker;
82
- private _gen;
83
- constructor(dims: DrawingDims, imageData: ArrayLike<number>, committedBitmap: Uint8Array, voxelSizeMM?: [number, number, number]);
84
- /**
85
- * Send a new committed bitmap to the worker.
86
- * Call after commit, undo, or clear.
87
- */
88
- updateCommitted(committed: Uint8Array): void;
89
- /**
90
- * Run a magic wand preview. The worker restores the committed bitmap
91
- * into the shared working buffer, runs the flood fill, and returns
92
- * the result summary. By the time the promise resolves, `this.bitmap`
93
- * already contains the updated drawing.
94
- *
95
- * Returns `null` if a newer preview superseded this one.
96
- */
97
- preview(seed: [number, number, number], options?: MagicWandOptions): Promise<MagicWandResult | null>;
98
- /** Terminate the worker and release resources. */
99
- dispose(): void;
100
- }
@@ -1,193 +0,0 @@
1
- import { NVWorker as p } from "@niivue/niivue";
2
- const g = '(function(){"use strict";const X={AXIAL:0,CORONAL:1};function ct(t,e){const{dimX:o,dimY:r,dimZ:s}=e;return t===X.AXIAL?{w:o,h:r,depth:s}:t===X.CORONAL?{w:o,h:s,depth:r}:{w:r,h:s,depth:o}}function Y(t,e,o,r){return t+e*r.dimX+o*r.dimX*r.dimY}function j(t,e,o,r,s){if(!s)return Y(t,e,o,r);const{img2RASstep:i,img2RASstart:n}=s;return n[0]+t*i[0]+n[1]+e*i[1]+n[2]+o*i[2]}function U(t,e,o,r){const{dimX:s,dimY:i,dimZ:n}=r;if(e===X.AXIAL){const c=new Float32Array(s*i),f=t*s*i;for(let a=0;a<c.length;a++)c[a]=o[f+a];return c}if(e===X.CORONAL){const c=new Float32Array(s*n);for(let f=0;f<n;f++)for(let a=0;a<s;a++)c[a+f*s]=o[Y(a,t,f,r)];return c}const l=new Float32Array(i*n);for(let c=0;c<n;c++)for(let f=0;f<i;f++)l[f+c*i]=o[Y(t,f,c,r)];return l}function J(t,e,o,r,s,i){const{dimX:n,dimY:l,dimZ:c}=r,f=1/s;if(e===X.AXIAL){const u=new Float32Array(n*l);for(let h=0;h<l;h++)for(let p=0;p<n;p++)u[p+h*n]=o[j(p,h,t,r,i)]*f;return u}if(e===X.CORONAL){const u=new Float32Array(n*c);for(let h=0;h<c;h++)for(let p=0;p<n;p++)u[p+h*n]=o[j(p,t,h,r,i)]*f;return u}const a=new Float32Array(l*c);for(let u=0;u<c;u++)for(let h=0;h<l;h++)a[h+u*l]=o[j(t,h,u,r,i)]*f;return a}function wt(t,e,o,r,s,i,n){const{dimX:l,dimY:c,dimZ:f}=s;if(o===X.AXIAL){const a=e*l*c;for(let u=0;u<t.length;u++)t[u]>=i&&(r[a+u]=n)}else if(o===X.CORONAL)for(let a=0;a<f;a++)for(let u=0;u<l;u++)t[u+a*l]>=i&&(r[Y(u,e,a,s)]=n);else for(let a=0;a<f;a++)for(let u=0;u<c;u++)t[u+a*c]>=i&&(r[Y(e,u,a,s)]=n)}function ft(t,e,o){if(e<3||o<3)return;const r=new Float32Array(t.length);for(let s=0;s<o;s++)for(let i=0;i<e;i++){const n=i+s*e;r[n]=i===0||i===e-1?t[n]:(t[n-1]+2*t[n]+t[n+1])*.25}for(let s=0;s<o;s++)for(let i=0;i<e;i++){const n=i+s*e;t[n]=s===0||s===o-1?r[n]:(r[n-e]+2*r[n]+r[n+e])*.25}}function pt(t,e,o,r){const s=Math.abs(o-t),i=Math.abs(o-e),n=Math.exp(-s*s/(2*r*r)),l=Math.exp(-i*i/(2*r*r)),c=n+l;return c<1e-6?.5:n/c}function bt(t,e,o){const{depth:r}=ct(t,o);let s=-1,i=-1;for(let n=0;n<r;n++){const l=U(n,t,e,o);let c=!1;for(let f=0;f<l.length;f++)if(l[f]>0){c=!0;break}c&&(s===-1&&(s=n),i=n)}return s===-1?null:{first:s,last:i}}function xt(t,e,o,r,s,i,n,l){const c=n.sliceType??X.AXIAL,{w:f,h:a,depth:u}=ct(c,e),h=u-1,p={intensityWeight:n.intensityWeight??.7,binaryThreshold:n.binaryThreshold??.375,intensitySigma:n.intensitySigma??.1,applySmoothingToSlices:n.applySmoothingToSlices??!0,useIntensityGuided:n.useIntensityGuided??!1};if(s!==void 0&&i!==void 0){if(s>=i)throw new Error("Low slice index must be less than high slice index");if(s<0||i>h)throw new Error(`Slice indices out of bounds [0, ${h}]`)}const C=new Map;for(let b=0;b<=h;b++){const N=U(b,c,t,e);for(let y=0;y<N.length;y++){const g=N[y];if(g>0){const x=C.get(g);x?(x.min=Math.min(x.min,b),x.max=Math.max(x.max,b)):C.set(g,{min:b,max:b})}}}for(const[b,N]of C){const y=s!==void 0?Math.max(s,N.min):N.min,g=i!==void 0?Math.min(i,N.max):N.max;if(y>=g||g-y<2)continue;const x=U(y,c,t,e),O=U(g,c,t,e),M=new Float32Array(x.length),I=new Float32Array(O.length);for(let A=0;A<x.length;A++)M[A]=x[A]===b?1:0,I[A]=O[A]===b?1:0;p.applySmoothingToSlices&&(ft(M,f,a),ft(I,f,a));for(let A=y+1;A<g;A++){const E=new Float32Array(f*a),d=(A-y)/(g-y),w=1-d;if(p.useIntensityGuided&&o){const m=J(y,c,o,e,r,l),v=J(g,c,o,e,r,l),nt=J(A,c,o,e,r,l),Z=p.intensityWeight;for(let F=0;F<M.length;F++)if(M[F]>0||I[F]>0){const et=pt(m[F],v[F],nt[F],p.intensitySigma),D=Z*et+(1-Z)*w,ot=1-D;E[F]=M[F]*D+I[F]*ot}else E[F]=M[F]*w+I[F]*d}else for(let m=0;m<M.length;m++)E[m]=M[m]*w+I[m]*d;wt(E,A,c,t,e,p.binaryThreshold,b)}}return t}function k(t,e,o,r){return t+e*r.dimX+o*r.dimX*r.dimY}function lt(t,e,o,r,s){if(!s)return k(t,e,o,r);const{img2RASstep:i,img2RASstart:n}=s;return n[0]+t*i[0]+n[1]+e*i[1]+n[2]+o*i[2]}const K=[[-1,0,0],[1,0,0],[0,-1,0],[0,1,0],[0,0,-1],[0,0,1]],at=[[-1,-1,0],[1,-1,0],[-1,1,0],[1,1,0],[0,-1,-1],[0,1,-1],[-1,0,-1],[1,0,-1],[0,-1,1],[0,1,1],[-1,0,1],[1,0,1]],Mt=[[-1,-1,-1],[1,-1,-1],[-1,1,-1],[1,1,-1],[-1,-1,1],[1,-1,1],[-1,1,1],[1,1,1]];function ut(t){return t>=26?[...K,...at,...Mt]:t>=18?[...K,...at]:[...K]}function dt(t,e,o,r,s={},i,n){const{tolerance:l=0,thresholdMode:c="symmetric",percent:f=0,calMin:a=0,calMax:u=1,connectivity:h=6,maxDistanceMM:p=Number.POSITIVE_INFINITY,is2D:C=!1,sliceAxis:b=0,penValue:N=1}=s,{dimX:y,dimY:g,dimZ:x}=o,O=y*g*x,[M,I,A]=t;if(M<0||I<0||A<0||M>=y||I>=g||A>=x)return{filledCount:0,seedIntensity:0,intensityMin:0,intensityMax:0};const E=lt(M,I,A,o,i),d=Number(r[E]);let w,m,v;if(s.intensityMin!==void 0&&s.intensityMax!==void 0)w=s.intensityMin,m=s.intensityMax;else{let L=c==="auto"?"symmetric":c;switch(c==="auto"&&(v=d>(a+u)*.5,L=v?"bright":"dark"),L){case"percent":{const S=d===0?.01:d;w=S*(1-f),m=S*(1+f);break}case"bright":if(f>0){const S=d===0?.01:d;w=S*(1-f),m=S*(1+f)}else l>0?(w=d,m=d+l):(w=d,m=Number.POSITIVE_INFINITY);break;case"dark":if(f>0){const S=d===0?.01:d;w=S*(1-f),m=S*(1+f)}else l>0?(w=d-l,m=d):(w=Number.NEGATIVE_INFINITY,m=d);break;default:w=d-l,m=d+l;break}}const nt=ut(h),Z=n?n[0]:1,F=n?n[1]:1,et=n?n[2]:1,D=p*p;function ot(L,S,H){if(!Number.isFinite(D))return!0;const P=(L-M)*Z,q=(S-I)*F,$=(H-A)*et;return P*P+q*q+$*$<=D}let z=-1,G=-1;C&&(b===0?(z=2,G=A):b===1?(z=1,G=I):b===2&&(z=0,G=M));const it=new Uint8Array(O),yt=k(M,I,A,o);if(d<w||d>m)return{filledCount:0,seedIntensity:d,intensityMin:w,intensityMax:m,isBright:v};const rt=[yt];it[yt]=1;let gt=0;for(;rt.length>0;){const L=rt.shift(),S=Math.floor(L/(y*g)),H=Math.floor((L-S*y*g)/y),P=L-S*y*g-H*y;e[L]=N,gt++;for(const[q,$,St]of nt){const B=P+q,W=H+$,T=S+St;if(B<0||W<0||T<0||B>=y||W>=g||T>=x||z>=0&&(z===0?B:z===1?W:T)!==G)continue;const st=k(B,W,T,o);if(it[st]||(it[st]=1,!ot(B,W,T)))continue;const At=Number(r[lt(B,W,T,o,i)]);At<w||At>m||rt.push(st)}}return{filledCount:gt,seedIntensity:d,intensityMin:w,intensityMax:m,isBright:v}}function It(t,e,o,r,s=6){const{dimX:i,dimY:n,dimZ:l}=o,[c,f,a]=t;if(c<0||f<0||a<0||c>=i||f>=n||a>=l)return 0;const u=k(c,f,a,o),h=e[u];if(h===r||h===0)return 0;const p=i*n*l,C=new Uint8Array(p),b=ut(s),N=[u];C[u]=1;let y=0;for(;N.length>0;){const g=N.shift();e[g]=r,y++;const x=Math.floor(g/(i*n)),O=Math.floor((g-x*i*n)/i),M=g-x*i*n-O*i;for(const[I,A,E]of b){const d=M+I,w=O+A,m=x+E;if(d<0||w<0||m<0||d>=i||w>=n||m>=l)continue;const v=k(d,w,m,o);C[v]||(C[v]=1,e[v]===h&&N.push(v))}}return y}const R=self.postMessage.bind(self),Ft={findBoundarySlices(t){const e=t.sliceType,o=new Uint8Array(t.drawBitmap),r=t.dims;return{result:bt(e,o,r)}},interpolateMaskSlices(t){const e=new Uint8Array(t.drawBitmap),o=t.dims,r=t.imageData?new Float32Array(t.imageData):null,s=t.maxVal??1,i=t.sliceIndexLow,n=t.sliceIndexHigh,l=t.options??{},c=t.rasMap??void 0;return{drawBitmap:xt(e,o,r,s,i,n,l,c).buffer}},magicWand(t){const e=t.seed,o=new Uint8Array(t.drawBitmap),r=t.dims,s=new Float32Array(t.imageData),i=t.options??{},n=t.rasMap??void 0,l=t.voxelSizeMM??void 0,c=dt(e,o,r,s,i,n,l);return{drawBitmap:o.buffer,result:c}},magicWandFromBitmap(t){const e=t.seed,o=new Uint8Array(t.drawBitmap),r=t.dims,s=t.newColor??0,i=t.connectivity??6,n=It(e,o,r,s,i);return{drawBitmap:o.buffer,count:n}}};let V=null,Q=null,_=null,tt=null,mt,ht;self.onmessage=t=>{const e=t.data;if(e.type==="initShared"){V=new Uint8Array(e.workingBuffer),Q=new Float32Array(e.imageBuffer),_=new Uint8Array(e.committedBuffer),tt=e.dims,mt=e.rasMap??void 0,ht=e.voxelSizeMM??void 0,R({type:"initSharedDone"});return}if(e.type==="updateCommitted"){_=new Uint8Array(e.committed);return}if(e.type==="magicWandShared"){if(!V||!Q||!_||!tt){R({type:"magicWandSharedResult",error:"Not initialized",gen:e.gen});return}V.set(_);const n=dt(e.seed,V,tt,Q,e.options??{},mt,ht);R({type:"magicWandSharedResult",result:n,gen:e.gen});return}const{_wbId:o,name:r,...s}=e,i=Ft[r];if(!i){R({_wbId:o,_wbError:`Unknown operation: ${r}`});return}try{const n=i(s),l=[];for(const c of Object.values(n))c instanceof ArrayBuffer&&l.push(c);R({_wbId:o,...n},l)}catch(n){R({_wbId:o,_wbError:n instanceof Error?n.message:String(n)})}}})();\n', h = typeof self < "u" && self.Blob && new Blob([g], { type: "text/javascript;charset=utf-8" });
3
- function w(e) {
4
- let t;
5
- try {
6
- if (t = h && (self.URL || self.webkitURL).createObjectURL(h), !t) throw "";
7
- const n = new Worker(t, {
8
- name: e == null ? void 0 : e.name
9
- });
10
- return n.addEventListener("error", () => {
11
- (self.URL || self.webkitURL).revokeObjectURL(t);
12
- }), n;
13
- } catch {
14
- return new Worker(
15
- "data:text/javascript;charset=utf-8," + encodeURIComponent(g),
16
- {
17
- name: e == null ? void 0 : e.name
18
- }
19
- );
20
- } finally {
21
- t && (self.URL || self.webkitURL).revokeObjectURL(t);
22
- }
23
- }
24
- function y(e) {
25
- if (e instanceof Float32Array) return e;
26
- const t = new Float32Array(e.length);
27
- for (let n = 0; n < e.length; n++) t[n] = e[n];
28
- return t;
29
- }
30
- let m = null;
31
- function d() {
32
- return m || (m = new p(() => new w())), m;
33
- }
34
- async function M(e, t, n) {
35
- const r = t.buffer.slice(
36
- t.byteOffset,
37
- t.byteOffset + t.byteLength
38
- );
39
- return (await d().execute(
40
- {
41
- name: "findBoundarySlices",
42
- sliceType: e,
43
- drawBitmap: r,
44
- dims: n
45
- },
46
- [r]
47
- )).result;
48
- }
49
- async function S(e, t, n, r, o, s, i) {
50
- const a = e.buffer.slice(
51
- e.byteOffset,
52
- e.byteOffset + e.byteLength
53
- ), c = [a];
54
- let f = null;
55
- if (n) {
56
- const l = y(n);
57
- f = l.buffer.slice(
58
- l.byteOffset,
59
- l.byteOffset + l.byteLength
60
- ), c.push(f);
61
- }
62
- const u = await d().execute(
63
- {
64
- name: "interpolateMaskSlices",
65
- drawBitmap: a,
66
- dims: t,
67
- imageData: f,
68
- maxVal: r,
69
- sliceIndexLow: o,
70
- sliceIndexHigh: s,
71
- options: i ?? {},
72
- rasMap: null
73
- },
74
- c
75
- );
76
- return new Uint8Array(u.drawBitmap);
77
- }
78
- async function x(e, t, n, r, o, s) {
79
- const i = t.buffer.slice(
80
- t.byteOffset,
81
- t.byteOffset + t.byteLength
82
- ), a = y(r), c = a.buffer.slice(
83
- a.byteOffset,
84
- a.byteOffset + a.byteLength
85
- ), f = [i, c], u = await d().execute(
86
- {
87
- name: "magicWand",
88
- seed: e,
89
- drawBitmap: i,
90
- dims: n,
91
- imageData: c,
92
- options: o ?? {},
93
- rasMap: null,
94
- voxelSizeMM: s ?? null
95
- },
96
- f
97
- );
98
- return { bitmap: new Uint8Array(u.drawBitmap), result: u.result };
99
- }
100
- async function I(e, t, n, r, o = 6) {
101
- const s = t.buffer.slice(
102
- t.byteOffset,
103
- t.byteOffset + t.byteLength
104
- ), i = await d().execute(
105
- {
106
- name: "magicWandFromBitmap",
107
- seed: e,
108
- drawBitmap: s,
109
- dims: n,
110
- newColor: r,
111
- connectivity: o
112
- },
113
- [s]
114
- );
115
- return { bitmap: new Uint8Array(i.drawBitmap), count: i.count };
116
- }
117
- class L {
118
- constructor(t, n, r, o) {
119
- this._gen = 0;
120
- const s = t.dimX * t.dimY * t.dimZ, i = new SharedArrayBuffer(s);
121
- this.bitmap = new Uint8Array(i), this.bitmap.set(r);
122
- const a = y(n), c = new SharedArrayBuffer(a.byteLength);
123
- new Float32Array(c).set(a);
124
- const f = r.slice();
125
- this._worker = new w(), this.ready = new Promise((u) => {
126
- const l = (b) => {
127
- b.data.type === "initSharedDone" && (this._worker.removeEventListener("message", l), u());
128
- };
129
- this._worker.addEventListener("message", l);
130
- }), this._worker.postMessage(
131
- {
132
- type: "initShared",
133
- workingBuffer: i,
134
- imageBuffer: c,
135
- committedBuffer: f.buffer,
136
- dims: t,
137
- rasMap: null,
138
- voxelSizeMM: o ?? null
139
- },
140
- [f.buffer]
141
- );
142
- }
143
- /**
144
- * Send a new committed bitmap to the worker.
145
- * Call after commit, undo, or clear.
146
- */
147
- updateCommitted(t) {
148
- const n = t.slice();
149
- this._worker.postMessage(
150
- { type: "updateCommitted", committed: n.buffer },
151
- [n.buffer]
152
- );
153
- }
154
- /**
155
- * Run a magic wand preview. The worker restores the committed bitmap
156
- * into the shared working buffer, runs the flood fill, and returns
157
- * the result summary. By the time the promise resolves, `this.bitmap`
158
- * already contains the updated drawing.
159
- *
160
- * Returns `null` if a newer preview superseded this one.
161
- */
162
- preview(t, n) {
163
- const r = ++this._gen;
164
- return new Promise((o) => {
165
- const s = (i) => {
166
- if (i.data.type === "magicWandSharedResult") {
167
- if (this._worker.removeEventListener("message", s), i.data.gen !== r) {
168
- o(null);
169
- return;
170
- }
171
- o(i.data.result);
172
- }
173
- };
174
- this._worker.addEventListener("message", s), this._worker.postMessage({
175
- type: "magicWandShared",
176
- seed: t,
177
- options: n ?? {},
178
- gen: r
179
- });
180
- });
181
- }
182
- /** Terminate the worker and release resources. */
183
- dispose() {
184
- this._worker.terminate();
185
- }
186
- }
187
- export {
188
- L as MagicWandShared,
189
- M as findDrawingBoundarySlices,
190
- S as interpolateMaskSlices,
191
- x as magicWand,
192
- I as magicWandFromBitmap
193
- };
@@ -1,54 +0,0 @@
1
- /**
2
- * Pure drawing interpolation algorithms.
3
- * No DOM or Worker dependencies — runs in either main thread or worker.
4
- */
5
- /** Slice orientation constants (mirrors @niivue/niivue SLICE_TYPE values). */
6
- export declare const SLICE_TYPE: {
7
- readonly AXIAL: 0;
8
- readonly CORONAL: 1;
9
- readonly SAGITTAL: 2;
10
- };
11
- export type SliceType = (typeof SLICE_TYPE)[keyof typeof SLICE_TYPE];
12
- export interface DrawingDims {
13
- dimX: number;
14
- dimY: number;
15
- dimZ: number;
16
- }
17
- /**
18
- * Mapping from RAS voxel coordinates to a flat index in header-space image
19
- * data. For RAS voxel (rx, ry, rz) the flat index is:
20
- * start[0] + rx*step[0] + start[1] + ry*step[1] + start[2] + rz*step[2]
21
- *
22
- * When the volume is already in RAS orientation (identity permutation, no
23
- * flips) this simplifies to the standard row-major layout and can be omitted.
24
- */
25
- export interface RASIndexMap {
26
- img2RASstep: number[];
27
- img2RASstart: number[];
28
- }
29
- export interface InterpolationOptions {
30
- sliceType?: SliceType;
31
- intensityWeight?: number;
32
- binaryThreshold?: number;
33
- intensitySigma?: number;
34
- applySmoothingToSlices?: boolean;
35
- useIntensityGuided?: boolean;
36
- }
37
- /**
38
- * Find the first and last slices containing drawing data along a given axis.
39
- */
40
- export declare function findBoundarySlices(sliceType: SliceType, drawBitmap: Uint8Array, dims: DrawingDims): {
41
- first: number;
42
- last: number;
43
- } | null;
44
- /**
45
- * Interpolate between drawn slices to fill gaps in the drawing bitmap.
46
- *
47
- * The drawing bitmap must be in RAS voxel order (matching the pen tool).
48
- * The optional imageData may be in header order; pass rasMap to handle
49
- * the RAS↔header mapping. If rasMap is omitted, imageData is assumed to
50
- * be in the same order as the bitmap.
51
- *
52
- * Modifies `drawBitmap` in-place and returns it.
53
- */
54
- export declare function interpolateMaskSlices(drawBitmap: Uint8Array, dims: DrawingDims, imageData: ArrayLike<number> | null, maxVal: number, sliceIndexLow: number | undefined, sliceIndexHigh: number | undefined, options: InterpolationOptions, rasMap?: RASIndexMap): Uint8Array;
@@ -1,110 +0,0 @@
1
- import { DrawingDims, RASIndexMap } from './drawing';
2
- /** Neighbour connectivity for the flood fill BFS. */
3
- export type Connectivity = 6 | 18 | 26;
4
- /**
5
- * Threshold mode controls how the intensity range is computed from the seed.
6
- *
7
- * - `"symmetric"` (default) — range = `[seed - tolerance, seed + tolerance]`.
8
- * - `"percent"` — range = `[seed * (1 - percent), seed * (1 + percent)]`.
9
- * Requires {@link MagicWandOptions.percent}.
10
- * - `"bright"` — only voxels **≥** seed are included:
11
- * range = `[seed, seed + tolerance]` (or `[seed, Infinity]` when tolerance is 0).
12
- * - `"dark"` — only voxels **≤** seed are included:
13
- * range = `[seed - tolerance, seed]` (or `[-Infinity, seed]` when tolerance is 0).
14
- * - `"auto"` — automatically choose `"bright"` or `"dark"` based on whether
15
- * the seed intensity is above or below the midpoint of `calMin`/`calMax`.
16
- * Requires {@link MagicWandOptions.calMin} and {@link MagicWandOptions.calMax}.
17
- * When combined with `percent`, the range is computed as percent-based
18
- * but only on the bright or dark side.
19
- */
20
- export type ThresholdMode = 'symmetric' | 'percent' | 'bright' | 'dark' | 'auto';
21
- /** Options that control the magic-wand behaviour. */
22
- export interface MagicWandOptions {
23
- /**
24
- * Absolute tolerance around the seed intensity (default 0).
25
- * Range becomes `[seed - tolerance, seed + tolerance]`.
26
- * Ignored when `intensityMin`/`intensityMax` are both provided,
27
- * or when `thresholdMode` is `"percent"`.
28
- */
29
- tolerance?: number;
30
- /** Explicit minimum intensity threshold (overrides all other range logic). */
31
- intensityMin?: number;
32
- /** Explicit maximum intensity threshold (overrides all other range logic). */
33
- intensityMax?: number;
34
- /**
35
- * How the intensity range is derived from the seed (default `"symmetric"`).
36
- * See {@link ThresholdMode} for details.
37
- */
38
- thresholdMode?: ThresholdMode;
39
- /**
40
- * Percentage threshold (0–1) used when `thresholdMode` is `"percent"` or
41
- * `"auto"`. Range becomes `[seed * (1-pct), seed * (1+pct)]`.
42
- * Equivalent to `clickToSegmentPercent` in the old niivue API.
43
- */
44
- percent?: number;
45
- /**
46
- * Volume calibration minimum — needed for `thresholdMode: "auto"` to
47
- * determine bright vs dark. Equivalent to `back.cal_min`.
48
- */
49
- calMin?: number;
50
- /**
51
- * Volume calibration maximum — needed for `thresholdMode: "auto"` to
52
- * determine bright vs dark. Equivalent to `back.cal_max`.
53
- */
54
- calMax?: number;
55
- /** Neighbour connectivity (default 6). */
56
- connectivity?: Connectivity;
57
- /** Maximum Euclidean distance from the seed in **mm** (default Infinity). */
58
- maxDistanceMM?: number;
59
- /** If true, constrain fill to the seed's 2-D slice. */
60
- is2D?: boolean;
61
- /** Slice axis when `is2D` is true (0 = axial/Z, 1 = coronal/Y, 2 = sagittal/X). */
62
- sliceAxis?: number;
63
- /** Pen colour to write into the bitmap (default 1). */
64
- penValue?: number;
65
- }
66
- /** Result returned by `magicWand`. */
67
- export interface MagicWandResult {
68
- /** Number of voxels that were filled. */
69
- filledCount: number;
70
- /** The seed voxel intensity. */
71
- seedIntensity: number;
72
- /** Actual intensity min used. */
73
- intensityMin: number;
74
- /** Actual intensity max used. */
75
- intensityMax: number;
76
- /**
77
- * Whether the auto-detect resolved to "bright" (`true`) or "dark" (`false`).
78
- * Only meaningful when `thresholdMode` was `"auto"`.
79
- */
80
- isBright?: boolean;
81
- }
82
- /**
83
- * Flood-fill (magic wand / click-to-segment) starting from a **RAS-voxel**
84
- * seed coordinate.
85
- *
86
- * @param seed - `[x, y, z]` RAS-voxel coordinate of the seed.
87
- * @param drawBitmap - Drawing bitmap in RAS voxel order (modified **in-place**).
88
- * @param dims - Volume dimensions in RAS space.
89
- * @param imageData - Background intensity volume (may be header-ordered).
90
- * @param options - See {@link MagicWandOptions}.
91
- * @param rasMap - Optional RAS→header index mapping for `imageData`.
92
- * @param voxelSizeMM - Voxel size in mm `[sx, sy, sz]` for distance constraints.
93
- * Required when `maxDistanceMM` is finite.
94
- * @returns Result summary (number of voxels filled, thresholds used, etc.).
95
- */
96
- export declare function magicWand(seed: [number, number, number], drawBitmap: Uint8Array, dims: DrawingDims, imageData: ArrayLike<number>, options?: MagicWandOptions, rasMap?: RASIndexMap, voxelSizeMM?: [number, number, number]): MagicWandResult;
97
- /**
98
- * Flood-fill a connected cluster of identically-coloured voxels
99
- * in the drawing bitmap, starting from `seed`. Useful for erasing
100
- * or recolouring an existing drawn region without reference to the
101
- * underlying image intensity.
102
- *
103
- * @param seed - `[x, y, z]` RAS-voxel coordinate of the seed.
104
- * @param drawBitmap - Drawing bitmap in RAS voxel order (modified **in-place**).
105
- * @param dims - Volume dimensions in RAS space.
106
- * @param newColor - Colour to apply (0 = erase).
107
- * @param connectivity - Neighbour connectivity (default 6).
108
- * @returns Number of voxels changed.
109
- */
110
- export declare function magicWandFromBitmap(seed: [number, number, number], drawBitmap: Uint8Array, dims: DrawingDims, newColor: number, connectivity?: Connectivity): number;
package/dist/worker.d.ts DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Web Worker for nv-ext-drawing operations.
3
- *
4
- * Protocol (NVWorker bridge):
5
- * Request: { _wbId, name, ...payload }
6
- * Response: { _wbId, ...result }
7
- * Error: { _wbId, _wbError: string }
8
- */
9
- export {};