diff --git a/assets/index-pvyZ93tw.js b/assets/index-BpgbaWPX.js similarity index 99% rename from assets/index-pvyZ93tw.js rename to assets/index-BpgbaWPX.js index db11530..7374306 100644 --- a/assets/index-pvyZ93tw.js +++ b/assets/index-BpgbaWPX.js @@ -1825,4 +1825,4 @@ void main(void) { <\/script> `}async saveHTML(u="untitled.html",h="gl1",g){const S=await this.generateHTML(h,g);return NVUtilities.download(S,u,"application/html")}json(){return this.document.opts=this.opts,this.document.scene=this.scene,this.document.volumes=this.volumes,this.document.meshes=this.meshes,this.drawScene(),this.document.previewImageDataURL=this.canvas.toDataURL(),this.document.json()}async saveDocument(u="untitled.nvd",h=!0){return this.document.title=u,log.debug("saveDocument",this.volumes[0]),this.drawScene(),this.document.previewImageDataURL=this.canvas.toDataURL(),this.document.volumes=this.volumes,this.document.meshes=this.meshes,this.document.download(u,h)}async loadImages(u){const h=[],g=[];for(const S of u)if("url"in S){const v=this.getFileExt(S.url);if(this.loaders[v]){const C=this.loaders[v].toExt.toUpperCase();MESH_EXTENSIONS.includes(C)?g.push(S):h.push(S);continue}MESH_EXTENSIONS.includes(v.toUpperCase())?g.push(S):h.push(S)}return h.length>0&&await this.loadVolumes(h),g.length>0&&await this.loadMeshes(g),this}async loadVolumes(u){return this.loadingText="loading...",this.drawScene(),this.thumbnailVisible?(this.deferredVolumes=u,this):(this.volumes=[],this.gl.clearColor(0,0,0,1),this.gl.clear(this.gl.COLOR_BUFFER_BIT),await this.addVolumesFromUrl(u),this)}async addMeshFromUrl(u){const h=this.getFileExt(u.url);if(h==="JCON"||h==="JSON"){const v=await(await fetch(u.url,{})).json(),C=this.loadConnectomeAsMesh(v);return this.mediaUrlMap.set(C,u.url),this.onMeshAddedFromUrl(u,C),this.addMesh(C),C}const g=await NVMesh2.loadFromUrl({...u,gl:this.gl});return this.mediaUrlMap.set(g,u.url),this.onMeshAddedFromUrl(u,g),this.addMesh(g),g}async addMeshesFromUrl(u){const h=u.map(async S=>{const v=this.getFileExt(S.name||S.url);if(this.loaders[v]){let D=S.url;const _=this.loaders[v].toExt;let P=S.name||S.url;if(P=P.split("/").pop(),typeof S.url=="string"){const e=S.url;try{const O=await fetch(e);if(!O.ok)throw new Error(`Failed to load file: ${O.statusText}`);D=await O.arrayBuffer()}catch(O){throw new Error(`Failed to load url ${e}: ${O}`)}}const{positions:L,indices:m}=await this.loaders[v].loader(D);S.name=`${P}.${_}`;const k=NVMeshUtilities.createMZ3(L,m,!1);S.buffer=k}if(v==="JCON"||v==="JSON"){const _=await(await fetch(S.url,{})).json(),P=this.loadConnectomeAsMesh(_);return this.mediaUrlMap.set(P,S.url),this.onMeshAddedFromUrl(S,P),P}const C=await NVMesh2.loadFromUrl({...S,gl:this.gl});return this.mediaUrlMap.set(C,S.url),this.onMeshAddedFromUrl(S,C),C}),g=await Promise.all(h);for(let S=0;S0&&(v[H]=P);const L=this.r16Tex(null,TEXTURE12_GC_STRENGTH0,this.back.dims,v),m=this.r16Tex(null,TEXTURE13_GC_STRENGTH1,this.back.dims,v);h.bindVertexArray(this.genericVAO);const k=this.growCutShader;k.use(h);const e=128;h.uniform1i(k.uniforms.finalPass,0),h.uniform1i(k.uniforms.backTex,11);for(let H=0;Hu[0]&&(D=1),h[1]>u[1]&&(_=1),h[2]>u[2]&&(P=1);let L=u[0],m=u[1],k=u[2];const e=h[0],O=h[1],q=h[2];if(S>=v&&S>=C){let Q=2*v-S,i0=2*C-S;for(;L!==e;)L+=D,Q>=0&&(m+=_,Q-=2*S),i0>=0&&(k+=P,i0-=2*S),Q+=2*v,i0+=2*C,this.drawPt(L,m,k,g)}else if(v>=S&&v>=C){let Q=2*S-v,i0=2*C-v;for(;m!==O;)m+=_,Q>=0&&(L+=D,Q-=2*v),i0>=0&&(k+=P,i0-=2*v),Q+=2*S,i0+=2*C,this.drawPt(L,m,k,g)}else{let Q=2*v-C,i0=2*S-C;for(;k!==q;)k+=P,Q>=0&&(m+=_,Q-=2*C),i0>=0&&(L+=D,i0-=2*C),Q+=2*v,i0+=2*S,this.drawPt(L,m,k,g)}}drawFloodFillCore(u,h,g=6){var L;if(!((L=this.back)!=null&&L.dims))throw new Error("back.dims undefined");const S=[this.back.dims[1],this.back.dims[2],this.back.dims[3]],v=S[0],C=v*S[1];function D(m){return m[0]+m[1]*v+m[2]*C}function _(m){const k=Math.floor(m/C),e=Math.floor((m-k*C)/v);return[Math.floor(m%v),e,k]}const P=[];for(P.push(h),u[h]=2;P.length>0;){let m=function(O){const q=e.slice();if(q[0]+=O[0],q[1]+=O[1],q[2]+=O[2],q[0]<0||q[1]<0||q[2]<0||q[0]>=S[0]||q[1]>=S[1]||q[2]>=S[2])return;const Q=D(q);u[Q]===1&&(u[Q]=2,P.push(Q))};const k=P[0];P.shift();const e=_(k);m([0,0,-1]),m([0,0,1]),m([0,-1,0]),m([0,1,0]),m([-1,0,0]),m([1,0,0]),!(g<=6)&&(m([-1,-1,0]),m([1,1,0]),m([-1,1,0]),m([1,1,0]),m([0,-1,-1]),m([0,1,-1]),m([-1,0,-1]),m([1,0,-1]),m([0,-1,1]),m([0,1,1]),m([-1,0,1]),m([1,0,1]),!(g<=18)&&(m([-1,-1,-1]),m([1,-1,-1]),m([-1,1,-1]),m([1,1,-1]),m([-1,-1,1]),m([1,-1,1]),m([-1,1,1]),m([1,1,1])))}}drawFloodFill(u,h=0,g=0,S=NaN,v=NaN,C=6){var q;if(!this.drawBitmap)throw new Error("drawBitmap undefined");if(!((q=this.back)!=null&&q.dims))throw new Error("back.dims undefined");h=Math.abs(h);const D=[this.back.dims[1],this.back.dims[2],this.back.dims[3]];if(u[0]<0||u[1]<0||u[2]<0||u[0]>=D[0]||u[1]>=D[1]||u[2]>=D[2])return;const _=D[0],P=_*D[1],L=P*D[2],m=this.drawBitmap.slice();if(m.length!==P*D[2])return;function k(Q){return Q[0]+Q[1]*_+Q[2]*P}const e=k(u),O=m[e];if(O===h){g!==0?log.debug("drawFloodFill selected voxel is not part of a drawing"):log.debug("drawFloodFill selected voxel is already desired color");return}for(let Q=1;Q=j&&Q[Y]<=i0&&(m[Y]=1);this.drawFloodFillCore(m,e,C),h=O}for(let Q=1;Qq[0]&&(Y=1),Q[1]>q[1]&&(T=1);let H=q[0],G=q[1];const K=Q[0],e0=Q[1];if(i0>=j){let z=2*j-i0;for(;H!==K;)H+=Y,z>=0&&(G+=T,z-=2*i0),z+=2*j,C[H+G*v[0]]=D}else{let z=2*i0-j;for(;G!==e0;)G+=T,z>=0&&(H+=Y,z-=2*j),z+=2*i0,C[H+G*v[0]]=D}}const P=[this.drawPenFillPts[0][g],this.drawPenFillPts[0][S]];let L=P;for(let q=1;q=v[0]||q[1]>=v[1])return;const Q=q[0]+q[1]*v[0];C[Q]===0&&(m.push(q),C[Q]=2)}for(let q=0;q0;){const q=m.shift();k([q[0]-1,q[1]]),k([q[0]+1,q[1]]),k([q[0],q[1]-1]),k([q[0],q[1]+1])}D=this.opts.penValue;const e=this.drawPenFillPts[0][3-(g+S)];if(!this.drawBitmap)throw new Error("drawBitmap undefined");if(h===0){const q=e*v[0]*v[1];for(let Q=0;Q0){const q=this.drawBitmap.length,Q=decodeRLE(this.drawUndoBitmaps[this.currentDrawUndoBitmap],q);for(let i0=0;i0{const v=new Image;v.onload=()=>{if(!this.bmpShader)return;let C;h===4?(this.bmpTexture!==null&&this.gl.deleteTexture(this.bmpTexture),this.bmpTexture=this.gl.createTexture(),C=this.bmpTexture,this.bmpTextureWH=v.width/v.height,this.gl.activeTexture(TEXTURE4_THUMBNAIL),this.bmpShader.use(this.gl),this.gl.uniform1i(this.bmpShader.uniforms.bmpTexture,4)):h===5?(this.gl.activeTexture(TEXTURE5_MATCAP),this.matCapTexture!==null&&this.gl.deleteTexture(this.matCapTexture),this.matCapTexture=this.gl.createTexture(),C=this.matCapTexture):(this.fontShader.use(this.gl),this.gl.activeTexture(TEXTURE3_FONT),this.gl.uniform1i(this.fontShader.uniforms.fontTexture,3),this.fontTexture!==null&&this.gl.deleteTexture(this.fontTexture),this.fontTexture=this.gl.createTexture(),C=this.fontTexture),this.gl.bindTexture(this.gl.TEXTURE_2D,C),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.gl.RGBA,this.gl.UNSIGNED_BYTE,v),g(C),h!==4&&this.drawScene()},v.onerror=S,this.requestCORSIfNotSameOrigin(v,u),v.src=u})}async loadFontTexture(u){return this.loadPngAsTexture(u,3)}async loadBmpTexture(u){return this.loadPngAsTexture(u,4)}async loadMatCapTexture(u){return this.loadPngAsTexture(u,5)}initFontMets(){if(!this.fontMetrics)throw new Error("fontMetrics undefined");this.fontMets={distanceRange:this.fontMetrics.atlas.distanceRange,size:this.fontMetrics.atlas.size,mets:{}};for(let g=0;g<256;g++)this.fontMets.mets[g]={xadv:0,uv_lbwh:[0,0,0,0],lbwh:[0,0,0,0]};const u=this.fontMetrics.atlas.width,h=this.fontMetrics.atlas.height;for(let g=0;g=this.meshes.length){log.debug("Unable to change shader until mesh is loaded (maybe you need async)");return}this.meshes[S].meshShaderIndex=g,this.updateGLVolume(),this.onMeshShaderChanged(S,g)}createCustomMeshShader(u,h="Custom"){if(!u)throw new Error("Need fragment shader");const g=this.meshShaderNameToNumber(h);g>=0&&(this.gl.deleteProgram(this.meshShaders[g].shader.program),this.meshShaders.splice(g,1));const S=new Shader(this.gl,vertMeshShader,u);return S.use(this.gl),{Name:h,Frag:u,shader:S}}setCustomMeshShader(u="",h="Custom"){const g=this.createCustomMeshShader(u,h);return this.meshShaders.push(g),this.onCustomMeshShaderAdded(u,h),this.meshShaders.length-1}meshShaderNames(u=!0){const h=[];for(let g=0;g0&&(await this.loadBmpTexture(this.opts.thumbnail),this.thumbnailVisible=!0),this.updateGLVolume(),this.initialized=!0,this.resizeListener(),this.drawScene(),this}gradientGL(u){const h=this.gl,g=[0,0,0,0,1,0,1,0,0,1,1,0],S=h.createVertexArray();h.bindVertexArray(S);const v=h.createBuffer();h.bindBuffer(h.ARRAY_BUFFER,v),h.bufferData(h.ARRAY_BUFFER,new Float32Array(g),h.STATIC_DRAW),h.enableVertexAttribArray(0),h.vertexAttribPointer(0,3,h.FLOAT,!1,0,0);const C=h.createFramebuffer();h.bindFramebuffer(h.FRAMEBUFFER,C),h.disable(h.CULL_FACE),h.viewport(0,0,u.dims[1],u.dims[2]),h.disable(h.BLEND);const D=this.rgbaTex(null,TEXTURE8_GRADIENT_TEMP,u.dims),_=this.blurShader;_.use(h),h.activeTexture(TEXTURE0_BACK_VOL),h.bindTexture(h.TEXTURE_3D,this.volumeTexture);const P=.7;h.uniform1i(_.uniforms.intensityVol,0),h.uniform1f(_.uniforms.dX,P/u.dims[1]),h.uniform1f(_.uniforms.dY,P/u.dims[2]),h.uniform1f(_.uniforms.dZ,P/u.dims[3]),h.bindVertexArray(S);for(let k=0;k0&&(this.furthestVertexFromOrigin=this.volumeObject3D.furthestVertexFromOrigin),this.meshes)for(let g=0;g0)for(let K=0;K0&&u.frame4D1&&v===0)return;let C=null;if(!this.back)throw new Error("back undefined");this.gl.bindVertexArray(this.unusedVAO),this.crosshairs3D&&(this.crosshairs3D.mm[0]=NaN);let D=clone$2(u.toRAS);if(h===0){this.volumeObject3D=u.toNiivueObject3D(this.VOLUME_ID,this.gl),invert(D,D),this.back.matRAS=u.matRAS,this.back.dims=u.dimsRAS,this.back.pixDims=u.pixDimsRAS,C=this.rgbaTex(this.volumeTexture,TEXTURE0_BACK_VOL,u.dimsRAS);const{volScale:G,vox:K}=this.sliceScale(!0);if(this.volScale=G,this.vox=K,this.volumeObject3D.scale=G,!this.renderShader)throw new Error("renderShader undefined");this.renderShader.use(this.gl),this.gl.uniform3fv(this.renderShader.uniforms.texVox,K),this.gl.uniform3fv(this.renderShader.uniforms.volScale,G);const e0=this.pickingImageShader;e0.use(this.gl),this.gl.uniform1i(e0.uniforms.volume,0),this.gl.uniform1i(e0.uniforms.colormap,1),this.gl.uniform1i(e0.uniforms.overlay,2),this.gl.uniform3fv(e0.uniforms.volScale,G),log.debug(this.volumeObject3D)}else{((H=this.back)==null?void 0:H.dims)===void 0&&log.error("Fatal error: Unable to render overlay: background dimensions not defined!");const G=this.mm2frac(u.mm000,0,!0);let K=this.mm2frac(u.mm100,0,!0),e0=this.mm2frac(u.mm010,0,!0),z=this.mm2frac(u.mm001,0,!0);K=subtract$1(K,K,G),e0=subtract$1(e0,e0,G),z=subtract$1(z,z,G),D=fromValues$3(K[0],e0[0],z[0],G[0],K[1],e0[1],z[1],G[1],K[2],e0[2],z[2],G[2],0,0,0,1),invert(D,D),h===1?(C=this.rgbaTex(this.overlayTexture,TEXTURE2_OVERLAY_VOL,this.back.dims),this.overlayTexture=C,this.overlayTextureID=C):C=this.overlayTextureID}const _=this.gl.createFramebuffer();this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,_),this.gl.disable(this.gl.CULL_FACE),this.gl.viewport(0,0,this.back.dims[1],this.back.dims[2]),this.gl.disable(this.gl.BLEND);const P=this.gl.createTexture();this.gl.activeTexture(TEXTURE9_ORIENT),this.gl.bindTexture(this.gl.TEXTURE_3D,P),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MIN_FILTER,this.gl.NEAREST),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MAG_FILTER,this.gl.NEAREST),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_R,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1);let L=this.orientShaderU;if(!g)throw new Error("hdr undefined");if(!S)throw new Error("img undefined");if(g.datatypeCode===2)g.intent_code===1002&&(L=this.orientShaderAtlasU),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R8UI,g.dims[1],g.dims[2],g.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g.dims[1],g.dims[2],g.dims[3],this.gl.RED_INTEGER,this.gl.UNSIGNED_BYTE,S);else if(g.datatypeCode===4)L=this.orientShaderI,g.intent_code===1002&&(L=this.orientShaderAtlasI),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R16I,g.dims[1],g.dims[2],g.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g.dims[1],g.dims[2],g.dims[3],this.gl.RED_INTEGER,this.gl.SHORT,S);else if(g.datatypeCode===16)this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R32F,g.dims[1],g.dims[2],g.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g.dims[1],g.dims[2],g.dims[3],this.gl.RED,this.gl.FLOAT,S),L=this.orientShaderF;else if(g.datatypeCode===64){let G=new Float32Array;G=Float32Array.from(S),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R32F,g.dims[1],g.dims[2],g.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g.dims[1],g.dims[2],g.dims[3],this.gl.RED,this.gl.FLOAT,G),L=this.orientShaderF}else g.datatypeCode===128?(L=this.orientShaderRGBU,L.use(this.gl),this.gl.uniform1i(L.uniforms.hasAlpha,0),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.RGB8UI,g.dims[1],g.dims[2],g.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g.dims[1],g.dims[2],g.dims[3],this.gl.RGB_INTEGER,this.gl.UNSIGNED_BYTE,S)):g.datatypeCode===512?(g.intent_code===1002&&(L=this.orientShaderAtlasU),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R16UI,g.dims[1],g.dims[2],g.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g.dims[1],g.dims[2],g.dims[3],this.gl.RED_INTEGER,this.gl.UNSIGNED_SHORT,S)):g.datatypeCode===2304&&(L=this.orientShaderRGBU,L.use(this.gl),this.gl.uniform1i(L.uniforms.hasAlpha,1),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.RGBA8UI,g.dims[1],g.dims[2],g.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g.dims[1],g.dims[2],g.dims[3],this.gl.RGBA_INTEGER,this.gl.UNSIGNED_BYTE,S));u.global_min===void 0&&u.calMinMax();let m=null;if(this.gl.bindVertexArray(this.genericVAO),h>1){if(!this.back.dims)throw new Error("back.dims undefined");m=this.rgbaTex(m,TEXTURE10_BLEND,this.back.dims),this.gl.bindTexture(this.gl.TEXTURE_3D,m);const G=this.passThroughShader;G.use(this.gl),this.gl.uniform1i(G.uniforms.in3D,2);for(let K=0;K7){const G=u.colormapLabel.max-u.colormapLabel.min+1;k=this.createColormapTexture(k,1,G),this.gl.texSubImage2D(this.gl.TEXTURE_2D,0,0,0,G,1,this.gl.RGBA,this.gl.UNSIGNED_BYTE,u.colormapLabel.lut),this.gl.uniform1f(L.uniforms.cal_min,u.colormapLabel.min-.5),this.gl.uniform1f(L.uniforms.cal_max,u.colormapLabel.max+.5),this.gl.bindTexture(this.gl.TEXTURE_2D,k)}else this.gl.bindTexture(this.gl.TEXTURE_2D,this.colormapTexture),this.gl.uniform1f(L.uniforms.cal_min,u.cal_min),this.gl.uniform1f(L.uniforms.cal_max,u.cal_max);this.gl.uniform1i(L.uniforms.isAlphaThreshold,u.alphaThreshold),this.gl.uniform1i(L.uniforms.isAdditiveBlend,this.opts.isAdditiveBlend?1:0);let e=Number.POSITIVE_INFINITY,O=Number.NEGATIVE_INFINITY;if(u.colormapNegative.length>0&&(e=Math.min(-u.cal_min,-u.cal_max),O=Math.max(-u.cal_min,-u.cal_max),isFinite(u.cal_minNeg)&&isFinite(u.cal_maxNeg)&&(e=Math.min(u.cal_minNeg,u.cal_maxNeg),O=Math.max(u.cal_minNeg,u.cal_maxNeg))),!L)throw new Error("orientShader undefined");this.gl.uniform1f(L.uniforms.layer??null,h),this.gl.uniform1f(L.uniforms.cal_minNeg??null,e),this.gl.uniform1f(L.uniforms.cal_maxNeg??null,O),this.gl.bindTexture(this.gl.TEXTURE_3D,P),this.gl.uniform1i(L.uniforms.intensityVol??null,9),this.gl.uniform1i(L.uniforms.blend3D??null,10),this.gl.uniform1i(L.uniforms.colormap??null,1),this.gl.uniform1f(L.uniforms.scl_inter??null,g.scl_inter),this.gl.uniform1f(L.uniforms.scl_slope??null,g.scl_slope),this.gl.uniform1f(L.uniforms.opacity??null,v),this.gl.uniform1i(L.uniforms.modulationVol??null,7);let q=null;if(u.modulationImage!==null&&u.modulationImage>=0&&u.modulationImage0;let d0=this.volumes[u.modulationImage].cal_min,v0=this.volumes[u.modulationImage].cal_max;isFinite(this.volumes[u.modulationImage].cal_minNeg)&&isFinite(this.volumes[u.modulationImage].cal_maxNeg)&&(d0=this.volumes[u.modulationImage].cal_minNeg,v0=this.volumes[u.modulationImage].cal_minNeg),d0=Math.abs(d0),v0=Math.abs(v0),d0>v0&&([d0,v0]=[v0,d0]);const g0=1/(v0-d0);let C0=Math.abs(u.modulateAlpha);C0=Math.max(C0,1);const x0=this.volumes[u.modulationImage].frame4D*K;for(let M0=0;M00?this.gradientGL(g):(this.gradientTexture!==null&&this.gl.deleteTexture(this.gradientTexture),this.gradientTexture=null)),!this.renderShader)throw new Error("renderShader undefined");this.renderShader.use(this.gl);const i0=this.sliceScale(!0),j=i0.vox,Y=i0.volScale;if(this.gl.uniform1f(this.renderShader.uniforms.overlays,this.overlays),this.gl.uniform4fv(this.renderShader.uniforms.clipPlaneColor,this.opts.clipPlaneColor),this.gl.uniform1f(this.renderShader.uniforms.clipThick,this.opts.clipThick),this.gl.uniform3fv(this.renderShader.uniforms.clipLo,this.opts.clipVolumeLow),this.gl.uniform3fv(this.renderShader.uniforms.clipHi,this.opts.clipVolumeHigh),this.gl.uniform1f(this.renderShader.uniforms.backOpacity,this.volumes[0].opacity),this.gl.uniform1f(this.renderShader.uniforms.renderOverlayBlend,this.opts.renderOverlayBlend),this.gl.uniform4fv(this.renderShader.uniforms.clipPlane,this.scene.clipPlane),this.gl.uniform3fv(this.renderShader.uniforms.texVox,j),this.gl.uniform3fv(this.renderShader.uniforms.volScale,Y),!this.pickingImageShader)throw new Error("pickingImageShader undefined");this.pickingImageShader.use(this.gl),this.gl.uniform1f(this.pickingImageShader.uniforms.overlays,this.overlays.length),this.gl.uniform3fv(this.pickingImageShader.uniforms.texVox,j),this.gl.uniform3fv(this.pickingImageShader.uniforms.clipLo,this.opts.clipVolumeLow),this.gl.uniform3fv(this.pickingImageShader.uniforms.clipHi,this.opts.clipVolumeHigh);let T=this.sliceMMShader;if(this.opts.isV1SliceShader&&(T=this.sliceV1Shader),!T)throw new Error("slice shader undefined");T.use(this.gl),this.gl.uniform1f(T.uniforms.overlays,this.overlays.length),this.gl.uniform1f(T.uniforms.drawOpacity,this.drawOpacity),k!==null&&(this.gl.deleteTexture(k),this.gl.activeTexture(TEXTURE1_COLORMAPS),this.gl.bindTexture(this.gl.TEXTURE_2D,this.colormapTexture)),this.gl.uniform1i(T.uniforms.drawing,7),this.gl.activeTexture(TEXTURE7_DRAW),this.gl.bindTexture(this.gl.TEXTURE_3D,this.drawTexture),this.updateInterpolation(h)}colormaps(){return cmapper.colormaps()}addColormap(u,h){cmapper.addColormap(u,h)}setColormap(u,h){const g=this.getVolumeIndexByID(u);this.volumes[g].colormap=h,this.updateGLVolume()}idx(u,h,g,S){return g*S[0]*S[1]+h*S[0]+u}check_previous_slice(u,h,g,S,v,C,D,_){const P=new Uint32Array(27);let L=0;if(!v)return 0;const m=u[this.idx(g,S,v,C)];if(D>=6){const k=this.idx(g,S,v-1,C);m===u[k]&&(P[L++]=h[k])}if(D>=18){if(g){const k=this.idx(g-1,S,v-1,C);m===u[k]&&(P[L++]=h[k])}if(S){const k=this.idx(g,S-1,v-1,C);m===u[k]&&(P[L++]=h[k])}if(g=6){if(k){const q=this.idx(k-1,m,L,h);O===u[q]&&(P[e++]=_[q])}if(m){const q=this.idx(k,m-1,L,h);O===u[q]&&(P[e++]=_[q])}}if(g>=18){if(m&&k){const q=this.idx(k-1,m-1,L,h);O===u[q]&&(P[e++]=_[q])}if(m&&k=C){C+=v;const q=new Uint32Array(C);q.set(D),D=q}D[S-1]=S,S++}}}for(let L=0;L100){log.info(` -Ooh no!!`);break}v[_]=P,D=Math.min(D,P)}for(let _=0;_u.cal_min){C=u.cal_min,D=u.cal_max;const Y=(g-h)/(D-C);return log.info(" Robust Rescale: min: "+C+" max: "+D+" scale: "+Y),console.log("Robust Rescale: min: "+C+" max: "+D+" scale: "+Y),[C,Y]}const _=u.img,P=u.hdr.dims[1]*u.hdr.dims[2]*u.hdr.dims[3];if(u.hdr.scl_slope!==1||u.hdr.scl_inter!==0){const Y=u.img,T=new Float32Array(u.img.length);for(let H=0;H=1e-15&&L++;const m=1e3,k=(D-C)/m,e=new Array(m).fill(0);for(let Y=0;Y=q);)Q++;const i0=C;for(C=Q*k+i0,q=P-Math.floor((1-v)*L),Q=0;Q=q);)Q++;D=Q*k+i0;let j=1;return C!==D&&(j=(g-h)/(D-C)),log.info(" Rescale: min: "+C+" max: "+D+" scale: "+j),[C,j]}conformVox2Vox(u,h,g=256,S=1,v=!1){const C=h.flat(),D=fromValues$3(C[0],C[1],C[2],C[3],C[4],C[5],C[6],C[7],C[8],C[9],C[10],C[11],C[12],C[13],C[14],C[15]),_=fromValues$1(u[1]/2,u[2]/2,u[3]/2,1),P=create$1(),L=create$3();transpose(L,D),transformMat4(P,_,L);const m=fromValues$2(P[0],P[1],P[2]),k=fromValues$2(S,S,S);let e=fromValues$3(-1,0,0,0,0,0,1,0,0,-1,0,0,0,0,0,1);v&&(e=fromValues$3(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)),transpose(e,e);const O=fromValues$1(g,g,g,1),q=create$3();scale$3(q,e,k);const Q=fromValues$1(O[0],O[1],O[2],1);transformMat4(Q,Q,q),scale$1(Q,Q,.5);const i0=create$2();subtract$1(i0,m,fromValues$2(Q[0],Q[1],Q[2]));const j=create$3();transpose(j,q),j[3]=i0[0],j[7]=i0[1],j[11]=i0[2];const Y=create$3();invert(Y,j);const T=create$3();mul(T,D,Y);const H=create$3();return invert(H,T),[j,T,H]}async createNiftiArray(u=[256,256,256],h=[1,1,1],g=[1,0,0,-128,0,1,0,-128,0,0,1,-128,0,0,0,1],S=2,v=new Uint8Array){return await NVImage.createNiftiArray(u,h,g,S,v)}async niftiArray2NVImage(u=new Uint8Array){return await NVImage.loadFromUrl({url:u})}async loadFromUrl(u){return await NVImage.loadFromUrl({url:u})}async conform(u,h=!1,g=!0,S=!1,v=!1){const _=this.conformVox2Vox(u.hdr.dims,u.hdr.affine.flat(),256,1,h),P=_[0],L=_[2],m=256*256*256,k=new Float32Array(m),e=new Float32Array(u.img),O=u.hdr.dims[1]*u.hdr.dims[2]*u.hdr.dims[3];if(u.hdr.scl_slope!==1||u.hdr.scl_inter!==0)for(let f0=0;f0=q||ke>=Q||D0>=i0)continue;const Be=C0-ee,Ge=x0-re,ti=M0-Fe,ai=1-Be,ri=1-Ge,qe=1-ti,be=T(ee,re,Fe);let J0=0;J0+=e[be]*ai*ri*qe,J0+=e[be+j]*ai*ri*ti,J0+=e[be+q]*ai*Ge*qe,J0+=e[be+q+j]*ai*Ge*ti,J0+=e[be+1]*Be*ri*qe,J0+=e[be+1+j]*Be*ri*ti,J0+=e[be+1+q]*Be*Ge*qe,J0+=e[be+1+q+j]*Be*Ge*ti,k[Y]=J0}}else for(let f0=0;f0<256;f0++)for(let o0=0;o0<256;o0++){const c0=o0*L[1]+f0*L[2]+L[3],d0=o0*L[5]+f0*L[6]+L[7],v0=o0*L[9]+f0*L[10]+L[11];for(let g0=0;g0<256;g0++){const C0=Math.round(g0*H+c0),x0=Math.round(g0*G+d0),M0=Math.round(g0*K+v0);Y++,!(C0<0||x0<0||M0<0)&&(C0>=q||x0>=Q||M0>=i0||(k[Y]=e[T(C0,x0,M0)]))}}let e0=0;v&&(e0=NaN);let z=new Uint8Array;if(S){const f0=await this.getScale(u,0,1,e0),o0=await this.scalecropFloat32(k,0,1,f0[0],f0[1]);z=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),16,new Uint8Array(o0.buffer))}else{const f0=await this.getScale(u,0,255,e0),o0=await this.scalecropUint8(k,0,255,f0[0],f0[1]);z=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),2,o0)}return await this.niftiArray2NVImage(z)}setRenderDrawAmbientOcclusion(u){if(!this.renderShader)throw new Error("renderShader undefined");this.renderDrawAmbientOcclusion=u,this.renderShader.use(this.gl),this.gl.uniform1fv(this.renderShader.uniforms.renderDrawAmbientOcclusion,[this.renderDrawAmbientOcclusion,1]),this.drawScene()}setColorMap(u,h){this.setColormap(u,h)}setColormapNegative(u,h){const g=this.getVolumeIndexByID(u);this.volumes[g].colormapNegative=h,this.updateGLVolume()}setModulationImage(u,h,g=0){const S=this.getVolumeIndexByID(u);let v=null;h.length>0&&(v=this.getVolumeIndexByID(h)),this.volumes[S].modulationImage=v,this.volumes[S].modulateAlpha=g,this.updateGLVolume()}setGamma(u=1){this.scene.gamma=u,cmapper.gamma=u,this.updateGLVolume()}async loadDeferred4DVolumes(u){const h=this.getVolumeIndexByID(u),g=this.volumes[h];if(g.nTotalFrame4D<=g.nFrame4D)return;let S;g.fileObject?S=await NVImage.loadFromFile({file:g.fileObject}):S=await NVImage.loadFromUrl({url:g.url}),S&&(g.img=S.img.slice(),g.nTotalFrame4D=S.nTotalFrame4D,g.nFrame4D=S.nFrame4D,this.updateGLVolume())}setFrame4D(u,h){const g=this.getVolumeIndexByID(u),S=this.volumes[g];h>S.nFrame4D-1&&(h=S.nFrame4D-1),h<0&&(h=0),h!==S.frame4D&&(S.frame4D=h,this.updateGLVolume(),this.onFrameChange(S,h),this.createOnLocationChange())}getFrame4D(u){const h=this.getVolumeIndexByID(u);return this.volumes[h].frame4D}colormapFromKey(u){return cmapper.colormapFromKey(u)}colormap(u="",h=!1){return cmapper.colormap(u,h)}createColormapTexture(u=null,h=0,g=256){return u!==null&&this.gl.deleteTexture(u),h<1||g<1?null:(u=this.gl.createTexture(),this.gl.activeTexture(TEXTURE1_COLORMAPS),this.gl.bindTexture(this.gl.TEXTURE_2D,u),this.gl.texStorage2D(this.gl.TEXTURE_2D,1,this.gl.RGBA8,g,h),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_R,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),u)}addColormapList(u="",h=NaN,g=NaN,S=!1,v=!1,C=!0,D=!1){u.length<1&&(C=!1),this.colormapLists.push({name:u,min:h,max:g,alphaThreshold:S,negative:v,visible:C,invert:D})}refreshColormaps(){if(this.colormapLists=[],this.volumes.length<1&&this.meshes.length<1)return;const u=this.volumes.length;if(u>0)for(let C=0;C0)for(let C=0;CS[0]&&h>S[1]&&u=0&&this.screenSlices[g].axCorSag===4?g:-1}sliceScroll3D(u=0){if(u!==0){if(this.volumes.length>0&&this.scene.clipPlaneDepthAziElev[0]<1.8){const h=this.scene.clipPlaneDepthAziElev.slice();return u>0&&(h[0]=Math.min(1.5,h[0]+.025)),u<0&&(h[0]=Math.max(-1.5,h[0]-.025)),h[0]!==this.scene.clipPlaneDepthAziElev[0]?(this.scene.clipPlaneDepthAziElev=h,this.setClipPlane(this.scene.clipPlaneDepthAziElev)):void 0}u>0&&(this.scene.volScaleMultiplier=Math.min(2,this.scene.volScaleMultiplier*1.1)),u<0&&(this.scene.volScaleMultiplier=Math.max(.5,this.scene.volScaleMultiplier*.9)),this.drawScene()}}deleteThumbnail(){this.bmpTexture&&(this.gl.deleteTexture(this.bmpTexture),this.bmpTexture=null,this.thumbnailVisible=!1)}inGraphTile(u,h){if(this.graph.opacity<=0||this.volumes.length<1||this.volumes[0].nFrame4D<1||!this.graph.plotLTWH||this.graph.plotLTWH[2]<1||this.graph.plotLTWH[3]<1)return!1;const g=[(u-this.graph.LTWH[0])/this.graph.LTWH[2],(h-this.graph.LTWH[1])/this.graph.LTWH[3]];return g[0]>0&&g[1]>0&&g[0]<=1&&g[1]<=1}mouseClick(u,h,g=0,S=!0){if(u*=this.uiData.dpr,h*=this.uiData.dpr,this.canvas.focus(),this.thumbnailVisible){this.thumbnailVisible=!1,Promise.all([this.loadVolumes(this.deferredVolumes),this.loadMeshes(this.deferredMeshes)]).catch(v=>{throw v});return}if(this.inGraphTile(u,h)){if(!this.graph.plotLTWH)throw new Error("plotLTWH undefined");const v=[(u-this.graph.plotLTWH[0])/this.graph.plotLTWH[2],(h-this.graph.plotLTWH[1])/this.graph.plotLTWH[3]];if(v[0]>0&&v[1]>0&&v[0]<=1&&v[1]<=1){const C=Math.round(v[0]*(this.volumes[0].nFrame4D-1));this.setFrame4D(this.volumes[0].id,C);return}v[0]>.5&&v[1]>1&&this.loadDeferred4DVolumes(this.volumes[0].id).catch(C=>{throw C});return}if(this.inRenderTile(u,h)>=0){this.sliceScroll3D(g),this.drawScene();return}if(!(this.screenSlices.length<1||this.gl.canvas.height<1||this.gl.canvas.width<1))for(let v=0;v=0&&this.drawPenAxCorSag!==C||C>2)continue;const D=this.screenXY2TextureFrac(u,h,v,!1);if(!(D[0]<0)){if(!S){this.scene.crosshairPos[2-C]=g,this.drawScene();return}if(g!==0){let _=1;g<0&&(_=-1);const P=[0,0,0];P[2-C]=_,this.moveCrosshairInVox(P[0],P[1],P[2]),this.drawScene(),this.createOnLocationChange(C);return}if(this.opts.isForceMouseClickToVoxelCenters?this.scene.crosshairPos=clone$1(this.vox2frac(this.frac2vox(D))):this.scene.crosshairPos=clone$1(D),this.opts.drawingEnabled){const _=this.frac2vox(this.scene.crosshairPos),P=this.frac2mm(this.scene.crosshairPos);if(this.opts.clickToSegment){const L=this.opts.clickToSegmentRadius,m=this.opts.clickToSegmentSteps,k=this.opts.clickToSegmentBright?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;this.drawPenFillPts=[],this.drawPenAxCorSag=C;for(let O=1;O<=m;O++){const q=O/m*2*Math.PI,Q=P[0]+L*Math.cos(q),i0=P[1]+L*Math.sin(q),j=P[2],Y=this.back.mm2vox([Q,i0,j])[0],T=this.back.mm2vox([Q,i0,j])[1],H=this.back.mm2vox([Q,i0,j])[2];this.drawPt(Y,T,H,this.opts.penValue),this.drawPenFillPts.push([Y,T,_[2]]),O===m&&this.drawFloodFill([Y,T,_[2]],0,k,NaN,NaN,this.opts.floodFillNeighbors)}this.drawScene(),this.createOnLocationChange(C);const e=this.getDescriptives(0,[],!0);this.onClickToSegment({mL:e.volumeML,mm3:e.volumeMM3});return}if(!isFinite(this.opts.penValue)||this.opts.penValue<0||Object.is(this.opts.penValue,-0)){isFinite(this.opts.penValue)?this.drawFloodFill(_,Math.abs(this.opts.penValue),this.opts.penValue,NaN,NaN,this.opts.floodFillNeighbors):this.drawFloodFill(_,0,this.opts.penValue,NaN,NaN,this.opts.floodFillNeighbors);return}if(isNaN(this.drawPenLocation[0]))this.drawPenAxCorSag=C,this.drawPenFillPts=[],this.drawPt(..._,this.opts.penValue);else{if(_[0]===this.drawPenLocation[0]&&_[1]===this.drawPenLocation[1]&&_[2]===this.drawPenLocation[2])return;this.drawPenLine(_,this.drawPenLocation,this.opts.penValue)}this.drawPenLocation=_,this.opts.isFilledPen&&this.drawPenFillPts.push(_),this.refreshDrawing(!1)}this.drawScene(),this.createOnLocationChange(C);return}}}drawRuler(){let u=[],h=[];for(let _=0;_1){h=this.screenSlices[_].leftTopWidthHeight,u=this.screenSlices[_].fovMM;break}if(h.length<4)return;const S=100/u[0]*h[2],v=h[0]+.5*h[2]-.5*S,C=h[1]+h[3]-2*this.opts.rulerWidth,D=[v,C,v+S,C];this.drawRuler10cm(D)}drawRuler10cm(u){if(!this.lineShader)throw new Error("lineShader undefined");this.gl.bindVertexArray(this.genericVAO),this.lineShader.use(this.gl),this.gl.uniform4fv(this.lineShader.uniforms.lineColor,this.opts.rulerColor),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,this.opts.rulerWidth),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,u),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4);const h=-.1*(u[0]-u[2]),g=u[1],S=g-2*this.opts.rulerWidth,v=g-4*this.opts.rulerWidth;for(let C=0;C<11;C++){const D=u[0]+C*h,_=[D,g,D,S];C%5===0&&(_[3]=v),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,_),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}this.gl.bindVertexArray(this.unusedVAO)}screenXY2mm(u,h,g=-1){let S;for(let v=0;v=0&&(C=g),this.screenSlices[C].axCorSag>2)continue;const _=this.screenSlices[C].leftTopWidthHeight;if(u<_[0]||h<_[1]||u>_[0]+_[2]||h>_[1]+_[3]||(S=this.screenXY2TextureFrac(u,h,C,!1),S[0]<0))continue;const P=this.frac2mm(S);return fromValues$1(P[0],P[1],P[2],C)}return fromValues$1(NaN,NaN,NaN,NaN)}dragForPanZoom(u){const h=this.screenXY2mm(u[2],u[3]);if(isNaN(h[0]))return;const g=this.screenXY2mm(u[0],u[1],h[3]);if(isNaN(g[0])||isNaN(h[0])||isNaN(h[3]))return;const S=create$1(),v=this.uiData.pan2DxyzmmAtMouseDown[3];sub(S,h,g),this.scene.pan2Dxyzmm[0]=this.uiData.pan2DxyzmmAtMouseDown[0]+v*S[0],this.scene.pan2Dxyzmm[1]=this.uiData.pan2DxyzmmAtMouseDown[1]+v*S[1],this.scene.pan2Dxyzmm[2]=this.uiData.pan2DxyzmmAtMouseDown[2]+v*S[2],this.canvas.focus()}dragForCenterButton(u){this.dragForPanZoom(u)}dragForSlicer3D(u){let h=this.uiData.pan2DxyzmmAtMouseDown[3];const g=u[3]-u[1];h+=g*.01,h=Math.max(h,.1),h=Math.min(h,10);const v=this.scene.pan2Dxyzmm[3]-h;this.opts.yoke3Dto2DZoom&&(this.scene.volScaleMultiplier=h),this.scene.pan2Dxyzmm[3]=h;const C=this.frac2mm(this.scene.crosshairPos);this.scene.pan2Dxyzmm[0]+=v*C[0],this.scene.pan2Dxyzmm[1]+=v*C[1],this.scene.pan2Dxyzmm[2]+=v*C[2]}drawMeasurementTool(u){const h=this.gl;if(h.bindVertexArray(this.genericVAO),h.depthFunc(h.ALWAYS),h.enable(h.BLEND),h.blendFunc(h.SRC_ALPHA,h.ONE_MINUS_SRC_ALPHA),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),h.uniform4fv(this.lineShader.uniforms.lineColor,this.opts.rulerColor),h.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[h.canvas.width,h.canvas.height]),h.uniform1f(this.lineShader.uniforms.thickness,this.opts.rulerWidth),h.uniform4fv(this.lineShader.uniforms.startXYendXY,u),h.drawArrays(h.TRIANGLE_STRIP,0,4);const g=this.opts.rulerColor;g[3]=1,h.uniform4fv(this.lineShader.uniforms.lineColor,g);const S=this.opts.rulerWidth;h.uniform1f(this.lineShader.uniforms.thickness,S*2);let v=[u[0],u[1]-S,u[0],u[1]+S];h.uniform4fv(this.lineShader.uniforms.startXYendXY,v),h.drawArrays(h.TRIANGLE_STRIP,0,4),v=[u[2],u[3]-S,u[2],u[3]+S],h.uniform4fv(this.lineShader.uniforms.startXYendXY,v),h.drawArrays(h.TRIANGLE_STRIP,0,4);let C=this.canvasPos2frac([u[0],u[1]]),D=this.canvasPos2frac([u[2],u[3]]);if(C[0]>=0&&D[0]>=0){const _=this.frac2mm(C);C=fromValues$2(_[0],_[1],_[2]);const P=this.frac2mm(D);D=fromValues$2(P[0],P[1],P[2]);const L=create$2();sub$1(L,C,D);const m=len(L);let k=2;m>9&&(k=1),m>99&&(k=0);const e=m.toFixed(k);this.drawTextBetween(u,e,1,g)}h.bindVertexArray(this.unusedVAO)}drawRect(u,h=[1,0,0,-1]){if(h[3]<0&&(h=this.opts.crosshairColor),!this.rectShader)throw new Error("rectShader undefined");this.rectShader.use(this.gl),this.gl.enable(this.gl.BLEND),this.gl.uniform4fv(this.rectShader.uniforms.lineColor,h),this.gl.uniform2fv(this.rectShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform4f(this.rectShader.uniforms.leftTopWidthHeight,u[0],u[1],u[2],u[3]),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawCircle(u,h=this.opts.fontColor,g=1){if(!this.circleShader)throw new Error("circleShader undefined");this.circleShader.use(this.gl),this.gl.enable(this.gl.BLEND),this.gl.uniform4fv(this.circleShader.uniforms.circleColor,h),this.gl.uniform2fv(this.circleShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform4f(this.circleShader.uniforms.leftTopWidthHeight,u[0],u[1],u[2],u[3]),this.gl.uniform1f(this.circleShader.uniforms.fillPercent,g),this.gl.uniform4fv(this.circleShader.uniforms.circleColor,h),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawSelectionBox(u){this.drawRect(u,this.opts.selectionBoxColor)}effectiveCanvasHeight(){return this.gl.canvas.height-this.colorbarHeight}effectiveCanvasWidth(){return this.gl.canvas.width-this.getLegendPanelWidth()}getAllLabels(){const S=this.meshes.filter(C=>C.type==="connectome").flatMap(C=>C.nodes).map(C=>C.label).filter(C=>C!==void 0);return[...this.document.labels,...S]}getConnectomeLabels(){const S=this.meshes.filter(D=>D.type==="connectome").flatMap(D=>D.nodes).map(D=>D.label).filter(D=>D!==void 0),v=this.document.labels.filter(D=>D.anchor==null||D.anchor===0),C=new Set(S);for(const D of v)C.add(D);return Array.from(C)}getBulletMarginWidth(){let u=0;const h=this.getConnectomeLabels();if(h.length===0)return 0;const g=h.length===1?h[0].style.bulletScale:h.reduce((C,D)=>C.style.bulletScale>D.style.bulletScale?C:D).style.bulletScale,S=h.length===1?h[0]:h.reduce((C,D)=>{const _=this.opts.textHeight*this.gl.canvas.height*C.style.textScale,P=this.opts.textHeight*this.gl.canvas.height*D.style.textScale;return this.textHeight(_,C.text)>this.textHeight(P,D.text)?C:D}),v=this.opts.textHeight*this.gl.canvas.height*S.style.textScale;return u=this.textHeight(v,S.text)*g,u+=v,u}getLegendPanelWidth(){const u=this.getConnectomeLabels();if(!this.opts.showLegend||u.length===0)return 0;const g=this.opts.textHeight*this.gl.canvas.height*1;let S=0;const v=u.reduce((P,L)=>{const m=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,k=this.opts.textHeight*this.gl.canvas.height*L.style.textScale;return this.textWidth(m,P.text)>this.textWidth(k,L.text)?P:L}),C=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textWidth(C,v.text),_=this.getBulletMarginWidth();return D&&(S=_+D,S+=g*2),S>=this.gl.canvas.width?0:S}getLegendPanelHeight(){const u=this.getConnectomeLabels();let h=0;const S=this.opts.textHeight*this.gl.canvas.height*1;for(const v of u){const C=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textHeight(C,v.text);h+=D}return h&&(h+=S/2*(u.length+1)),h}reserveColorbarPanel(){let u=Math.max(this.opts.textHeight,.01);u=u*Math.min(this.gl.canvas.height,this.gl.canvas.width);const h=3*u,g=[0,this.gl.canvas.height-h,this.gl.canvas.width,h];return this.colorbarHeight=g[3]+1,g}drawColorbarCore(u=0,h=[0,0,0,0],g=!1,S=0,v=1,C){if(h[2]<=0||h[3]<=0)return;let D=Math.max(this.opts.textHeight,.01);D=D*Math.min(this.gl.canvas.height,this.gl.canvas.width);let _=D;const P=3*D;let L=D;if(h[3]0&&(O=S,S=0),S===v||D<1)return;const q=Math.abs(v-S);let[Q,i0]=tickSpacing(S,v);i0S.includes(_)).reduce((D,_)=>D.lbwh[3]>_.lbwh[3]?D:_).lbwh[3];return u*C}drawChar(u,h,g){if(!this.fontShader)throw new Error("fontShader undefined");const S=this.fontMets.mets[g],v=u[0]+h*S.lbwh[0],C=-(h*S.lbwh[1]),D=h*S.lbwh[2],_=h*S.lbwh[3],P=u[1]+(C-_)+h;return this.gl.uniform4f(this.fontShader.uniforms.leftTopWidthHeight,v,P,D,_),this.gl.uniform4fv(this.fontShader.uniforms.uvLeftTopWidthHeight,S.uv_lbwh),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),h*S.xadv}drawLoadingText(u){if(!this.canvas)throw new Error("canvas undefined");this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height),this.gl.enable(this.gl.CULL_FACE),this.gl.enable(this.gl.BLEND),this.drawTextBelow([this.canvas.width/2,this.canvas.height/2],u,3)}drawText(u,h,g=1,S=null){if(this.opts.textHeight<=0)return;if(!this.fontShader)throw new Error("fontShader undefined");this.fontShader.use(this.gl);const v=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*g;this.gl.enable(this.gl.BLEND),this.gl.uniform2f(this.fontShader.uniforms.canvasWidthHeight,this.gl.canvas.width,this.gl.canvas.height),S===null&&(S=this.opts.fontColor),this.gl.uniform4fv(this.fontShader.uniforms.fontColor,S);let C=v/this.fontMets.size*this.fontMets.distanceRange;C=Math.max(C,1),this.gl.uniform1f(this.fontShader.uniforms.screenPxRange,C);const D=new TextEncoder().encode(h);this.gl.bindVertexArray(this.genericVAO);for(let _=0;_.8?P=[0,0,0,.5]:P=[1,1,1,.5],this.drawRect(_,P),this.drawText(v,h,g,S)}drawTextBelow(u,h,g=1,S=null){if(this.opts.textHeight<=0)return;if(!this.canvas)throw new Error("canvas undefined");let v=this.opts.textHeight*this.gl.canvas.height*g,C=this.textWidth(v,h);C>this.canvas.width&&(g*=(this.canvas.width-2)/C,v=this.opts.textHeight*this.gl.canvas.height*g,C=this.textWidth(v,h)),u[0]-=.5*this.textWidth(v,h),u[0]=Math.max(u[0],1),u[0]=Math.min(u[0],this.canvas.width-C-1),this.drawText(u,h,g,S)}updateInterpolation(u,h=!1){let g=this.gl.LINEAR;!h&&this.opts.isNearestInterpolation&&(g=this.gl.NEAREST),u===0?this.gl.activeTexture(TEXTURE0_BACK_VOL):this.gl.activeTexture(TEXTURE2_OVERLAY_VOL),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MIN_FILTER,g),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MAG_FILTER,g)}setAtlasOutline(u){this.opts.atlasOutline=u,this.updateGLVolume(),this.drawScene()}setInterpolation(u){this.opts.isNearestInterpolation=u;const h=this.volumes.length;if(!(h<1)){for(let g=0;g_&&(this.drawTextBelow([u[0]+u[2]*.5,u[1]+g[1]-_],S),C=!1);const P=this.textWidth(_,v)+2;g[0]>P&&(this.drawTextRight([u[0]+g[0]-P,u[1]+u[3]*.5],v),D=!1)}C&&this.drawTextBelow([u[0]+u[2]*.5,u[1]],S),D&&this.drawTextRight([u[0],u[1]+u[3]*.5],v)}xyMM2xyzMM(u,h){let g=2;u===1&&(g=1),u===2&&(g=0);let S=[0,0,0],v=[1,1,0],C=[1,0,1];S[g]=h,v[g]=h,C[g]=h,S=this.frac2mm(S),v=this.frac2mm(v),C=this.frac2mm(C),S=this.swizzleVec3MM(fromValues$2(S[0],S[1],S[2]),u),v=this.swizzleVec3MM(fromValues$2(v[0],v[1],v[2]),u),C=this.swizzleVec3MM(fromValues$2(C[0],C[1],C[2]),u);const D=(v[0]-S[0])*(C[1]-S[1])-(C[0]-S[0])*(v[1]-S[1]);let _=(v[0]-S[0])*(C[2]-S[2])-(C[0]-S[0])*(v[2]-S[2]);_/=D;let P=(v[1]-S[1])*(C[2]-S[2])-(C[1]-S[1])*(v[2]-S[2]);P/=D;const L=[0,0,0,0,0];return L[0]=S[0],L[1]=S[1],L[2]=S[2],L[3]=P,L[4]=_,L}draw2DMain(u,h,g=NaN){let S=this.volumes[0].frac2mm.slice(),v=this.screenFieldOfViewExtendedMM(h),C=create$3();this.opts.isSliceMM||(S=this.volumes[0].frac2mmOrtho.slice(),C=clone$2(this.volumes[0].mm2ortho),v=this.screenFieldOfViewExtendedVox(h));let D=this.opts.isRadiologicalConvention&&h<2;g===1/0||g===-1/0?(D=g!==1/0,h===1&&(D=!D)):this.opts.sagittalNoseLeft&&h===2&&(D=!D);let _=0,P=0;h===2?P=D?90:-90:h===1?P=D?180:0:(P=D?180:0,_=D?-90:90);const L=this.gl;let m=!1;if(u[2]===0||u[3]===0){m=!0;const j=L.canvas.width/v.fovMM[0],Y=L.canvas.height/v.fovMM[1],T=Math.min(j,Y),H=j/T,G=Y/T;v.fovMM[0]*=H,v.fovMM[1]*=G;let K=(v.mnMM[0]+v.mxMM[0])*.5;v.mnMM[0]=K-v.fovMM[0]*.5,v.mxMM[0]=K+v.fovMM[0]*.5,K=(v.mnMM[1]+v.mxMM[1])*.5,v.mnMM[1]=K-v.fovMM[1]*.5,v.mxMM[1]=K+v.fovMM[1]*.5,u=[0,0,L.canvas.width,L.canvas.height]}if(isNaN(g)){const j=this.scene.pan2Dxyzmm,Y=this.swizzleVec3MM(fromValues$2(j[0],j[1],j[2]),h),T=this.scene.pan2Dxyzmm[3];v.mnMM[0]-=Y[0],v.mxMM[0]-=Y[0],v.mnMM[1]-=Y[1],v.mxMM[1]-=Y[1],v.mnMM[0]/=T,v.mxMM[0]/=T,v.mnMM[1]/=T,v.mxMM[1]/=T}let k=2;h===1&&(k=1),h===2&&(k=0);let e=this.scene.crosshairPos[k],O=this.frac2mm(this.scene.crosshairPos);!isNaN(g)&&g!==1/0&&g!==-1/0&&(O=this.frac2mm([.5,.5,.5]),O[k]=g,e=this.mm2frac(O)[k]);const q=O[k];L.clear(L.DEPTH_BUFFER_BIT);let Q=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,1/0,0,P,_,D);if(g===1/0||g===-1/0){const j=u.slice();this.draw3D(u,Q.modelViewProjectionMatrix,Q.modelMatrix,Q.normalMatrix,P,_);const Y=this.screenSlices[this.screenSlices.length-1];Y.leftTopWidthHeight=j,Y.axCorSag=h,Y.sliceFrac=1/0,Y.AxyzMxy=this.xyMM2xyzMM(h,e),Y.leftTopMM=Q.leftTopMM,Y.fovMM=Q.fovMM;return}L.enable(L.DEPTH_TEST),L.blendFunc(L.SRC_ALPHA,L.ONE_MINUS_SRC_ALPHA),L.disable(L.BLEND),L.depthFunc(L.GREATER),L.disable(L.CULL_FACE);let i0=this.sliceMMShader;if(this.opts.isV1SliceShader&&(i0=this.sliceV1Shader),!i0)throw new Error("slice Shader undefined");if(i0.use(this.gl),L.uniform1f(i0.uniforms.overlayOutlineWidth,this.overlayOutlineWidth),L.uniform1f(i0.uniforms.overlayAlphaShader,this.overlayAlphaShader),L.uniform1i(i0.uniforms.isAlphaClipDark,this.isAlphaClipDark?1:0),L.uniform1i(i0.uniforms.backgroundMasksOverlays,this.backgroundMasksOverlays),L.uniform1f(i0.uniforms.drawOpacity,this.drawOpacity),L.enable(L.BLEND),L.blendFunc(L.SRC_ALPHA,L.ONE_MINUS_SRC_ALPHA),L.uniform1f(i0.uniforms.opacity,this.volumes[0].opacity),L.uniform1i(i0.uniforms.axCorSag,h),L.uniform1f(i0.uniforms.slice,e),L.uniformMatrix4fv(i0.uniforms.frac2mm,!1,S),L.uniformMatrix4fv(i0.uniforms.mvpMtx,!1,Q.modelViewProjectionMatrix.slice()),L.bindVertexArray(this.genericVAO),L.drawArrays(L.TRIANGLE_STRIP,0,4),L.bindVertexArray(this.unusedVAO),this.screenSlices.push({leftTopWidthHeight:u,axCorSag:h,sliceFrac:e,AxyzMxy:this.xyMM2xyzMM(h,e),leftTopMM:Q.leftTopMM,screen2frac:[],fovMM:Q.fovMM}),isNaN(g)&&this.drawCrosshairs3D(!0,1,Q.modelViewProjectionMatrix,!0,this.opts.isSliceMM),this.opts.meshThicknessOn2D>0){this.opts.meshThicknessOn2D!==1/0&&(Q=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,this.opts.meshThicknessOn2D,q,P,_,D));const j=clone$2(Q.modelViewProjectionMatrix);multiply(j,j,C),this.drawMesh3D(!0,1,j,Q.modelMatrix,Q.normalMatrix)}isNaN(g)&&this.drawCrosshairs3D(!1,.15,Q.modelViewProjectionMatrix,!0,this.opts.isSliceMM),m&&this.drawSliceOrientationText(u,h),this.readyForSync=!0}draw2D(u,h,g=NaN,S=[NaN,NaN]){const v=[NaN,NaN];if(isNaN(S[0]))this.draw2DMain(u,h,g);else{const C=u.slice();v[0]=Math.floor(.5*(C[2]-S[0])),v[1]=Math.floor(.5*(C[3]-S[1])),C[0]+=v[0],C[1]+=v[1],C[2]=S[0],C[3]=S[1],this.draw2DMain(C,h,g)}g===1/0||g===-1/0||h===4||u[2]!==0&&u[3]!==0&&this.drawSliceOrientationText(u,h,v)}calculateMvpMatrix(u,h=[0,0,0,0],g,S){(h[2]===0||h[3]===0)&&(h=[0,0,this.gl.canvas.width,this.gl.canvas.height]);const v=h[2]/h[3];let C=this.furthestFromPivot;const D=this.pivot3D,_=create$3();C=.8*C/this.scene.volScaleMultiplier,v<1?ortho(_,-C,C,-C/v,C/v,C*.01,C*8):ortho(_,-C*v,C*v,-C,C,C*.01,C*8);const P=create$3();P[0]=-1;const L=fromValues$2(0,0,-C*1.8);translate(P,P,L),this.position&&translate(P,P,this.position),rotateX(P,P,deg2rad(270-S)),rotateZ(P,P,deg2rad(g-180)),translate(P,P,[-D[0],-D[1],-D[2]]);const m=create$3();invert(m,P);const k=create$3();transpose(k,m);const e=create$3();return multiply(e,_,P),[e,P,k]}calculateModelMatrix(u,h){if(!this.back)throw new Error("back undefined");const g=create$3();if(g[0]=-1,rotateX(g,g,deg2rad(270-h)),rotateZ(g,g,deg2rad(u-180)),this.back.obliqueRAS){const S=clone$2(this.back.obliqueRAS);multiply(g,g,S)}return g}calculateRayDirection(u,h){const g=this.calculateModelMatrix(u,h),S=fromValues$3(1,0,0,0,0,-1,0,0,0,0,-1,0,0,0,0,1),v=create$3();multiply(v,S,g);const C=create$3();invert(C,v);const D=fromValues$1(0,0,-1,1);transformMat4(D,D,C);const _=fromValues$2(D[0],D[1],D[2]);normalize$1(_,_);const P=5e-5;return Math.abs(_[0])0){if(!this.volumeObject3D)throw new Error("volumeObject3D undefined");h=fromValues$2(this.volumeObject3D.extentsMin[0],this.volumeObject3D.extentsMin[1],this.volumeObject3D.extentsMin[2]),g=fromValues$2(this.volumeObject3D.extentsMax[0],this.volumeObject3D.extentsMax[1],this.volumeObject3D.extentsMax[2]),u||(h=fromValues$2(this.volumes[0].extentsMinOrtho[0],this.volumes[0].extentsMinOrtho[1],this.volumes[0].extentsMinOrtho[2]),g=fromValues$2(this.volumes[0].extentsMaxOrtho[0],this.volumes[0].extentsMaxOrtho[1],this.volumes[0].extentsMaxOrtho[2]))}if(this.meshes.length>0){if(this.volumes.length<1){const v=this.meshes[0].extentsMin,C=this.meshes[0].extentsMax;h=fromValues$2(v[0],v[1],v[2]),g=fromValues$2(C[0],C[1],C[2])}for(let v=0;vthis.gl.canvas.width||u.LTWH[1]+u.LTWH[3]>this.gl.canvas.height)return;u.backColor=[.15,.15,.15,u.opacity],u.lineColor=[1,1,1,1],this.opts.backColor[0]+this.opts.backColor[1]+this.opts.backColor[2]>1.5&&(u.backColor=[.95,.95,.95,u.opacity],u.lineColor=[0,0,0,1]),u.textColor=u.lineColor.slice(),u.lineThickness=4,u.lineAlpha=1,u.lines=[];const g=[];if(u.vols.length<1)this.volumes[0]!=null&&g.push(0);else for(let o0=0;o0v){const o0=C-v;for(let c0=0;c0=C&&(C=v+1),this.drawRect(u.LTWH,u.backColor);const[D,_,P]=tickSpacing(v,C),L=Math.max(0,-1*Math.floor(Math.log(D)/Math.log(10)));v=Math.min(_,v),C=Math.max(P,C);function m(o0){return o0.toFixed(6).replace(/\.?0*$/,"")}const e=.07*(Math.min(u.LTWH[2],u.LTWH[3])/(this.fontMets.size*this.uiData.dpr));let O=this.opts.textHeight*this.gl.canvas.height*e;O<16&&(O=0);let q=0,Q=_;if(O>0)for(;Q<=C;){const o0=Q.toFixed(L),c0=this.textWidth(O,o0);q=Math.max(c0,q),Q+=D}const i0=.05,j=Math.abs(u.LTWH[2]),Y=Math.abs(u.LTWH[3]),T=[u.LTWH[0]+i0*j+q,u.LTWH[1]+i0*Y,u.LTWH[2]-q-2*i0*j,u.LTWH[3]-O-2*i0*Y];this.graph.LTWH=u.LTWH,this.graph.plotLTWH=T,this.drawRect(T,this.opts.backColor);const H=C-v,G=T[3]/H,K=T[2]/(u.lines[0].length-1),e0=T[1]+T[3];Q=_+.5*D;const z=u.lineColor.slice();for(z[3]=.25*u.lineColor[3];Q<=C;){const o0=e0-(Q-v)*G;this.drawLine([T[0],o0,T[0]+T[2],o0],.5*u.lineThickness,z),Q+=D}Q=_;const f=.5*u.lineThickness;for(;Q<=C;){const o0=e0-(Q-v)*G;this.drawLine([T[0]-f,o0,T[0]+T[2]+u.lineThickness,o0],u.lineThickness,u.lineColor);const c0=Q.toFixed(L);O>0&&this.drawTextLeft([T[0]-6,o0],c0,e,u.textColor),Q+=D}let f0=1;for(;u.lines[0].length/f0>20;)f0*=5;for(let o0=0;o00&&this.drawTextBelow([c0,2+T[1]+T[3]],v0,e,u.textColor),this.drawLine([c0,T[1],c0,T[1]+T[3]],d0,u.lineColor)}}for(let o0=0;o0=0&&u.selectedColumnk/255);return}const D=unpackFloatFromVec4i(C);if(D>1)return;const _=(this.mousePos[0]-u[0])/u[2],P=(g.canvas.height-this.mousePos[1]-u[1])/u[3],L=unProject(_,P,D,h),m=this.mm2frac(L,0,!0);m[0]<0||m[0]>1||m[1]<0||m[1]>1||m[2]<0||m[2]>1||(this.scene.crosshairPos=this.mm2frac(L,0,!0))}drawImage3D(u,h,g){if(this.volumes.length===0)return;const S=this.gl,v=this.calculateRayDirection(h,g),C=this.volumeObject3D;if(C){S.enable(S.BLEND),S.blendFunc(S.SRC_ALPHA,S.ONE_MINUS_SRC_ALPHA),S.enable(S.CULL_FACE),S.cullFace(S.FRONT);let D=this.renderShader;if(this.uiData.mouseDepthPicker&&(D=this.pickingImageShader),D.use(this.gl),S.uniform1i(D.uniforms.backgroundMasksOverlays,this.backgroundMasksOverlays),this.gradientTextureAmount>0){S.activeTexture(TEXTURE6_GRADIENT),S.bindTexture(S.TEXTURE_3D,this.gradientTexture);const _=this.calculateModelMatrix(h,g),P=create$3();invert(P,_);const L=create$3();transpose(L,P),S.uniformMatrix4fv(D.uniforms.normMtx,!1,L)}this.drawBitmap&&this.drawBitmap.length>8?S.uniform2f(D.uniforms.renderDrawAmbientOcclusionXY,this.renderDrawAmbientOcclusion,this.drawOpacity):S.uniform2f(D.uniforms.renderDrawAmbientOcclusionXY,this.renderDrawAmbientOcclusion,0),S.uniformMatrix4fv(D.uniforms.mvpMtx,!1,u),S.uniformMatrix4fv(D.uniforms.matRAS,!1,this.back.matRAS),S.uniform3fv(D.uniforms.rayDir,v),this.gradientTextureAmount<0?S.uniform4fv(D.uniforms.clipPlane,[this.scene.crosshairPos[0],this.scene.crosshairPos[1],this.scene.crosshairPos[2],30]):S.uniform4fv(D.uniforms.clipPlane,this.scene.clipPlane),S.uniform1f(D.uniforms.drawOpacity,1),S.bindVertexArray(C.vao),S.drawElements(C.mode,C.indexCount,S.UNSIGNED_SHORT,0),S.bindVertexArray(this.unusedVAO)}}drawOrientationCube(u,h=0,g=0){if(!this.opts.isOrientCube)return;const S=.05*Math.min(u[2],u[3]);if(S<5)return;const v=this.gl;v.enable(v.CULL_FACE),v.cullFace(v.BACK),this.orientCubeShader.use(this.gl),v.bindVertexArray(this.orientCubeShaderVAO);const C=create$3(),D=create$3();ortho(D,0,v.canvas.width,0,v.canvas.height,-10*S,10*S);let _=0;u[1]===0&&(_=v.canvas.height-this.effectiveCanvasHeight()),translate(C,C,[1.8*S+u[0],_+1.8*S+u[1],0]),scale$3(C,C,[S,S,S]),rotateX(C,C,deg2rad(270-g)),rotateZ(C,C,deg2rad(-h));const P=create$3();multiply(P,D,C),v.uniformMatrix4fv(this.orientCubeShader.uniforms.u_matrix,!1,P),v.drawArrays(v.TRIANGLE_STRIP,0,168),v.bindVertexArray(this.unusedVAO),this.gl.disable(this.gl.CULL_FACE)}createOnLocationChange(u=NaN){const[h,g,S]=this.sceneExtentsMinMax(!0),v=Math.max(Math.max(S[0],S[1]),S[2]);function C(k){return Math.max(0,-Math.ceil(Math.log10(Math.abs(k))))}let D=C(v*.001);const _=this.frac2mm(this.scene.crosshairPos,0,!0);function P(k,e=0){return parseFloat(k.toFixed(e))}let L=P(_[0],D)+"×"+P(_[1],D)+"×"+P(_[2],D);if(this.volumes.length>0&&this.volumes[0].nFrame4D>0&&(L+="×"+P(this.volumes[0].frame4D)),this.volumes.length>0){let k=" = ";for(let q=0;q=0&&j=0&&(k+="+"),k+=P(i0,D)),k+=" "}L+=k;const e=this.back.dimsRAS,O=e[1]*e[2]*e[3];if(this.drawBitmap&&this.drawBitmap.length===O){const q=this.frac2vox(this.scene.crosshairPos),Q=q[0]+q[1]*e[1]+q[2]*e[1]*e[2];L+=" "+this.drawLut.labels[this.drawBitmap[Q]]}}const m={mm:this.frac2mm(this.scene.crosshairPos,0,!0),axCorSag:u,vox:this.frac2vox(this.scene.crosshairPos),frac:this.scene.crosshairPos,xy:[this.mousePos[0],this.mousePos[1]],values:this.volumes.map(k=>{const e=this.frac2mm(this.scene.crosshairPos,0,!0),O=k.mm2vox(e),q=k.getValue(O[0],O[1],O[2],k.frame4D);return{name:k.name,value:q,id:k.id,mm:e,vox:O}}),string:L};this.onLocationChange(m)}addLabel(u,h,g,S,v){const C={textColor:this.opts.legendTextColor,textScale:1,textAlignment:"left",lineWidth:0,lineColor:this.opts.legendTextColor,lineTerminator:"none",bulletScale:0,bulletColor:this.opts.legendTextColor},D=h?{...C,...h}:{...C},_=new NVLabel3D(u,{...D},g,S,v);return this.document.labels.push(_),_}calculateScreenPoint(u,h,g){const S=create$1();return transformMat4(S,[...u,1],h),S[3]!==0&&(S[0]=(S[0]/S[3]+1)*.5*g[2],S[1]=(1-S[1]/S[3])*.5*g[3],S[2]/=S[3],S[0]+=g[0],S[1]+=g[1]),S}getLabelAtPoint(u){const g=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,S=this.opts.textHeight*this.gl.canvas.height*1;for(const L of this.document.labels){if(L.anchor==null||L.anchor===0)continue;const m=this.opts.textHeight*this.gl.canvas.height*L.style.textScale,k=this.textHeight(m,L.text),e=this.textWidth(m,L.text);if(!(L.anchor&1&&u[0]>e)&&!(L.anchor&2&&(u[0]<(this.gl.canvas.width-e)/2||u[0]>(this.gl.canvas.width+e)/2))&&!(L.anchor&4&&u[0]k+S/2))&&!(L.anchor&16&&(u[1]<(this.gl.canvas.height-k-S)/2||u[1]>(this.gl.canvas.height+k-S/2)/2))&&!(L.anchor&32&&(u[1]this.gl.canvas.height-S/2)))return L}log.debug("screenPoint",u);const v=this.getLegendPanelHeight(),C=this.getLegendPanelWidth(),D=this.gl.canvas.width-C;let _=(this.canvas.height-v)/2;if(log.debug("panelrect",D,_,D+C,_+v),u[0]D+C||u[1]>_+v)return null;const P=this.getConnectomeLabels();for(const L of P){const m=this.opts.textHeight*this.gl.canvas.height*L.style.textScale,k=this.textHeight(m,L.text);if(u[1]>=_&&u[1]<=_+k+g/2)return L;_+=k,_+=g/2}return null}drawLabelLine(u,h,g,S,v=!1){const C=Array.isArray(u.points)&&Array.isArray(u.points[0])?u.points:[u.points];for(const D of C){const _=this.calculateScreenPoint(D,g,S);v?this.drawDottedLine([...h,_[0],_[1]],u.style.lineWidth,u.style.lineColor):this.draw3DLine(h,[_[0],_[1],_[2]],u.style.lineWidth,u.style.lineColor)}}draw3DLabel(u,h,g,S,v,C,D){const _=u.text,P=h[0],L=h[1],m=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,k=this.textHeight(u.style.textScale,_)*m;if(u.style.lineWidth>0&&Array.isArray(u.points)&&this.drawLabelLine(u,[P,L+k],g,S,D),u.style.bulletScale){const O=u.style.bulletScale*k,q=k-O,Q=L+q/2+O/2,i0=P+(v-O)/2;this.drawCircle([i0,Q,O,O],u.style.bulletColor)}let e=P;if(C)if(u.style.textAlignment!=="left"){const O=this.textWidth(u.style.textScale,u.text)*m;if(u.style.textAlignment==="right")e=P+C-m*1.5-O;else{const q=C-(v||m);e+=(q-O)/2}}else e+=v;this.drawText([e,L],_,u.style.textScale,u.style.textColor)}draw3DLabels(u,h,g=!1){const S=this.getConnectomeLabels();if(!this.opts.showLegend||S.length===0)return;if(!this.canvas)throw new Error("canvas undefined");const v=this.gl;v.disable(v.CULL_FACE),v.viewport(0,0,this.canvas.width,this.canvas.height);const D=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,_=this.getBulletMarginWidth(),P=this.getLegendPanelHeight(),L=this.getLegendPanelWidth(),m=v.canvas.width-L;let k=(this.canvas.height-P)/2;this.drawRect([v.canvas.width-L,k,L-D,P],this.opts.legendBackgroundColor);const e=v.getParameter(v.BLEND),O=v.getParameter(v.DEPTH_FUNC);g||(v.disable(v.BLEND),v.depthFunc(v.GREATER));for(const q of S){this.draw3DLabel(q,[m,k],u,h,_,L,g);const Q=this.opts.textHeight*this.gl.canvas.height*q.style.textScale,i0=this.textHeight(Q,q.text);k+=i0,k+=D/2}g||(v.depthFunc(O),e&&v.enable(v.BLEND))}drawAnchoredLabels(){const u=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,h=this.document.labels.filter(g=>g.anchor!=null&&g.anchor!==0);for(const g of h){const S=g.text,v=this.textHeight(g.style.textScale,S)*u,C=this.textWidth(g.style.textScale,S)*u;let D,_;const L=this.opts.textHeight*this.gl.canvas.height*1,m=L;let k=L/4,e=0,O=0;g.anchor&1&&(D=0),g.anchor&4&&(D=this.canvas.width-C,e-=L/4),g.anchor&2&&(D=(this.canvas.width-C)/2,e-=L/4,k+=L/4),g.anchor&8&&(_=0),g.anchor&16&&(_=(this.canvas.height-v-L)/2,O-=L/4),g.anchor&32&&(_=this.canvas.height-v-L,O-=L/4),this.drawRect([D+e,_+O,C+k,v+m],g.style.backgroundColor),this.draw3DLabel(g,[D,_])}}draw3D(u=[0,0,0,0],h=null,g=null,S=null,v=null,C=0){const D=v!==null;this.setPivot3D(),D||(v=this.scene.renderAzimuth,C=this.scene.renderElevation);const _=this.gl;h===null&&([h,g,S]=this.calculateMvpMatrix(null,u,v,C));let P=[...u];if(u[2]===0||u[3]===0?(u=[0,0,_.canvas.width,_.canvas.height],P=[...u],this.screenSlices.push({leftTopWidthHeight:u,axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(g),0]})):(this.screenSlices.push({leftTopWidthHeight:u.slice(),axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(g),0]}),u[1]=_.canvas.height-u[3]-u[1]),_.enable(_.DEPTH_TEST),_.depthFunc(_.ALWAYS),_.depthMask(!0),_.clearDepth(0),this.draw3DLabels(h,P,!1),_.viewport(u[0],u[1],u[2],u[3]),this.volumes.length>0&&(this.updateInterpolation(0,!0),this.updateInterpolation(1,!0),this.drawImage3D(h,v,C)),this.updateInterpolation(0),this.updateInterpolation(1),D||this.drawCrosshairs3D(!0,1,h),this.drawMesh3D(!0,1,h,g,S),this.uiData.mouseDepthPicker){this.depthPicker(u,h),this.createOnLocationChange(),this.draw3D(u,h,g,S,v,C);return}this.opts.meshXRay>0&&this.drawMesh3D(!1,this.opts.meshXRay,h,g,S),this.draw3DLabels(h,P,!1),_.viewport(u[0],u[1],u[2],u[3]),D||this.drawCrosshairs3D(!1,.15,h),_.viewport(0,0,_.canvas.width,_.canvas.height),this.drawOrientationCube(u,v,C);const L="azimuth: "+this.scene.renderAzimuth.toFixed(0)+" elevation: "+this.scene.renderElevation.toFixed(0);return this.readyForSync=!0,this.sync(),this.draw3DLabels(h,P,!0),L}drawMesh3D(u=!0,h=1,g,S,v){if(this.meshes.length<1)return;const C=this.gl;g||([g,S,v]=this.calculateMvpMatrix(this.volumeObject3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),C.enable(C.DEPTH_TEST),C.blendFunc(C.SRC_ALPHA,C.ONE_MINUS_SRC_ALPHA),C.disable(C.BLEND),C.depthFunc(C.GREATER),C.disable(C.CULL_FACE),u?(C.disable(C.BLEND),C.depthFunc(C.GREATER)):(C.enable(C.BLEND),C.depthFunc(C.ALWAYS),C.enable(C.CULL_FACE)),C.cullFace(C.BACK);let D=this.meshShaders[0].shader,_=!1;for(let P=0;P=3&&this.meshes[P].fiberRadius>0||(C.bindVertexArray(this.meshes[P].vaoFiber),C.drawElements(C.LINE_STRIP,this.meshes[P].indexCount,C.UNSIGNED_INT,0),C.bindVertexArray(this.unusedVAO)));C.enable(C.BLEND),C.depthFunc(C.ALWAYS),this.readyForSync=!0}drawCrosshairs3D(u=!0,h=1,g=null,S=!1,v=!0){if(!this.opts.show3Dcrosshair&&!S||this.opts.crosshairWidth<=0&&S)return;const C=this.gl,D=this.frac2mm(this.scene.crosshairPos,0,v);if(this.crosshairs3D===null||this.crosshairs3D.mm[0]!==D[0]||this.crosshairs3D.mm[1]!==D[1]||this.crosshairs3D.mm[2]!==D[2]){this.crosshairs3D!==null&&(C.deleteBuffer(this.crosshairs3D.indexBuffer),C.deleteBuffer(this.crosshairs3D.vertexBuffer));const[L,m,k]=this.sceneExtentsMinMax(v);let e=1;if(this.volumes.length>0){if(!this.back)throw new Error("back undefined");e=.5*Math.min(Math.min(this.back.pixDims[1],this.back.pixDims[2]),this.back.pixDims[3])}else(k[0]<50||k[0]>1e3)&&(e=k[0]*.02);e*=this.opts.crosshairWidth,this.crosshairs3D=NiivueObject3D.generateCrosshairs(this.gl,1,D,L,m,e,20,this.opts.crosshairGap),this.crosshairs3D.mm=D}if(!this.surfaceShader)throw new Error("surfaceShader undefined");const _=this.surfaceShader;_.use(this.gl),g==null&&([g]=this.calculateMvpMatrix(this.crosshairs3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),C.uniformMatrix4fv(_.uniforms.mvpMtx,!1,g),C.bindBuffer(C.ELEMENT_ARRAY_BUFFER,this.crosshairs3D.indexBuffer),C.enable(C.DEPTH_TEST);const P=[...this.opts.crosshairColor];u?(C.disable(C.BLEND),C.depthFunc(C.GREATER)):(C.enable(C.BLEND),C.blendFunc(C.SRC_ALPHA,C.ONE_MINUS_SRC_ALPHA),C.depthFunc(C.ALWAYS)),P[3]=h,C.uniform4fv(_.uniforms.surfaceColor,P),C.bindVertexArray(this.crosshairs3D.vao),C.drawElements(C.TRIANGLES,this.crosshairs3D.indexCount,C.UNSIGNED_INT,0),C.bindVertexArray(this.unusedVAO)}mm2frac(u,h=0,g=!1){if(this.volumes.length<1){const S=fromValues$2(.1,.5,.5),[v,C,D]=this.sceneExtentsMinMax();return S[0]=(u[0]-v[0])/D[0],S[1]=(u[1]-v[1])/D[1],S[2]=(u[2]-v[2])/D[2],isFinite(S)||(isFinite(S[0])||(S[0]=.5),isFinite(S[1])||(S[1]=.5),isFinite(S[2])||(S[2]=.5),this.meshes.length<1&&log.error("mm2frac() not finite: objects not (yet) loaded.")),S}return this.volumes[h].convertMM2Frac(u,g||this.opts.isSliceMM)}vox2frac(u,h=0){return this.volumes[h].convertVox2Frac(u)}frac2vox(u,h=0){return this.volumes.length<=h?[0,0,0]:this.volumes[h].convertFrac2Vox(u)}moveCrosshairInVox(u,h,g){const S=this.frac2vox(this.scene.crosshairPos);S[0]+=u,S[1]+=h,S[2]+=g,S[0]=clamp(S[0],0,this.volumes[0].dimsRAS[1]-1),S[1]=clamp(S[1],0,this.volumes[0].dimsRAS[2]-1),S[2]=clamp(S[2],0,this.volumes[0].dimsRAS[3]-1),this.scene.crosshairPos=this.vox2frac(S),this.createOnLocationChange(),this.drawScene()}frac2mm(u,h=0,g=!1){const S=fromValues$1(u[0],u[1],u[2],1);if(this.volumes.length>0)return this.volumes[h].convertFrac2MM(u,g||this.opts.isSliceMM);{const[v,C]=this.sceneExtentsMinMax(),D=(_,P,L)=>_*(1-L)+P*L;S[0]=D(v[0],C[0],u[0]),S[1]=D(v[1],C[1],u[1]),S[2]=D(v[2],C[2],u[2])}return S}screenXY2TextureFrac(u,h,g,S=!0){const v=fromValues$2(-1,-1,-1),C=this.screenSlices[g].axCorSag;if(C>2)return v;const D=this.screenSlices[g].leftTopWidthHeight.slice();let _=!1;D[2]<0&&(_=!0,D[0]+=D[2],D[2]=-D[2]);let P=(u-D[0])/D[2];_&&(P=1-P);const L=1-(h-D[1])/D[3];if(P<0||P>1||L<0||L>1||this.screenSlices[g].AxyzMxy.length<4)return v;let m=fromValues$2(0,0,0);m[0]=this.screenSlices[g].leftTopMM[0]+P*this.screenSlices[g].fovMM[0],m[1]=this.screenSlices[g].leftTopMM[1]+L*this.screenSlices[g].fovMM[1];const k=this.screenSlices[g].AxyzMxy;m[2]=k[2]+k[4]*(m[1]-k[1])-k[3]*(m[0]-k[0]),C===1&&(m=swizzleVec3(m,[0,2,1])),C===2&&(m=swizzleVec3(m,[2,0,1]));const e=this.mm2frac(m);return S&&(e[0]<0||e[0]>1||e[1]<0||e[1]>1||e[2]<0||e[2]>1)?v:e}canvasPos2frac(u){for(let h=0;h=0)return g}return[-1,-1,-1]}scaleSlice(u,h,g=0,S=0){const v=this.effectiveCanvasWidth()-g,C=this.effectiveCanvasHeight()-S;let D=v/u;h*D>C&&(D=C/h);const _=u*D,P=h*D;return[(v-_)*.5,(C-P)*.5,_,P,D]}drawThumbnail(){if(!this.bmpShader)throw new Error("bmpShader undefined");this.bmpShader.use(this.gl),this.gl.uniform2f(this.bmpShader.uniforms.canvasWidthHeight,this.gl.canvas.width,this.gl.canvas.height);let u=this.gl.canvas.height,h=this.gl.canvas.height*this.bmpTextureWH;h>this.gl.canvas.width&&(u=this.gl.canvas.width/this.bmpTextureWH,h=this.gl.canvas.width),this.gl.uniform4f(this.bmpShader.uniforms.leftTopWidthHeight,0,0,h,u),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawLine(u,h=1,g=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),g[3]<0&&(g=this.opts.crosshairColor),this.gl.uniform4fv(this.lineShader.uniforms.lineColor,g),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,h),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,u),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}draw3DLine(u,h,g=1,S=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.line3DShader)throw new Error("line3DShader undefined");this.line3DShader.use(this.gl),S[3]<0&&(S=this.opts.crosshairColor),this.gl.uniform4fv(this.line3DShader.uniforms.lineColor,S),this.gl.uniform2fv(this.line3DShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.line3DShader.uniforms.thickness,g),this.gl.uniform2fv(this.line3DShader.uniforms.startXY,u),this.gl.uniform3fv(this.line3DShader.uniforms.endXYZ,h),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawDottedLine(u,h=1,g=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl);const S=g[3]<0?[...this.opts.crosshairColor]:[...g];S[3]=.3;const v=fromValues(u[2]-u[0],u[3]-u[1]),C=length(v);normalize(v,v);const _=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1;scale(v,v,_/2);const P=length(v);let L=Math.floor(C/P);C%P&&L++;const m=[u[0],u[1]];this.gl.uniform4fv(this.lineShader.uniforms.lineColor,S),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,h);for(let k=0;k0&&h===0){const e=D,O=1;for(let q=0;q0&&h===1){const e=D,O=2;for(let q=0;q0&&h===2){const e=D,O=2;for(let q=0;q0&&h===0){const e=D,O=0;for(let q=0;q0&&h===1){const e=D,O=0;for(let q=0;q0&&h===2){const e=D,O=1;for(let q=0;q0){const P=C.leftTopWidthHeight.slice();let L=2;h===0&&(L=1);const m=this.frac2mm([.5,.5,.5]);for(let k=0;k0){const P=C.leftTopWidthHeight.slice(),L=C.fovMM[0]<0;let m=0;h===2&&(m=1);const k=this.frac2mm([.5,.5,.5]);for(let e=0;e<_.length;e++){k[m]=_[e];const O=this.mm2frac(k);L?this.drawRect([P[0]+(P[2]-O[m]*P[2]),P[1],1,P[3]]):this.drawRect([P[0]+O[m]*P[2],P[1],1,P[3]])}}}drawMosaic(u){if(this.volumes.length===0){log.debug("Unable to draw mosaic until voxel-based image is loaded");return}this.screenSlices=[];const h=this.screenFieldOfViewMM(0,!0),g=this.screenFieldOfViewMM(0);u=u.replaceAll(";"," ;").trim();const S=[],v=[],C=[],D=u.split(/\s+/);let _=1;const P=this.opts.textHeight;let L=0,m=0;for(let k=0;k<2;k++){let e=!1,O=!1;e=!1;let q=0,Q=0,i0=0,j=0,Y=!1,T=0;for(let K=0;K"u"){if(this.meshes.length>0){this.screenSlices=[],this.opts.sliceType=4,this.draw3D(),this.opts.isColorbar&&this.drawColorbar();return}this.drawLoadingText(this.loadingText);return}if(this.back===null)return;if(this.uiData.isDragging&&this.scene.clipPlaneDepthAziElev[0]<1.8&&this.inRenderTile(this.uiData.dragStart[0],this.uiData.dragStart[1])>=0){const v=this.uiData.dragStart[0]-this.uiData.dragEnd[0],C=this.uiData.dragStart[1]-this.uiData.dragEnd[1],D=this.uiData.dragClipPlaneStartDepthAziElev.slice();if(D[1]-=v,D[1]=D[1]%360,D[2]+=C,D[1]!==this.scene.clipPlaneDepthAziElev[1]||D[2]!==this.scene.clipPlaneDepthAziElev[2])return this.scene.clipPlaneDepthAziElev=D,this.setClipPlane(this.scene.clipPlaneDepthAziElev)}if(this.sliceMosaicString.length<1&&this.opts.sliceType===4){this.opts.isColorbar&&this.reserveColorbarPanel(),this.screenSlices=[],this.draw3D(),this.opts.isColorbar&&this.drawColorbar();return}this.opts.isColorbar&&this.reserveColorbarPanel();const h=this.getMaxVols(),g=this.opts.sliceType===3&&h>1&&this.graph.autoSizeMultiplanar&&this.graph.opacity>0;if(this.sliceMosaicString.length>0)this.drawMosaic(this.sliceMosaicString);else if(this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height),this.screenSlices=[],this.opts.sliceType===0)this.draw2D([0,0,0,0],0);else if(this.opts.sliceType===1)this.draw2D([0,0,0,0],1);else if(this.opts.sliceType===2)this.draw2D([0,0,0,0],2);else{let v=!1;this.opts.multiplanarForceRender?(v=!0,this.opts.multiplanarForceRender?this.opts.multiplanarShowRender=1:this.opts.multiplanarShowRender=2,delete this.opts.multiplanarForceRender):this.opts.multiplanarShowRender===1&&(v=!0);const C=isFinite(this.drawPenLocation[0])&&this.opts.drawingEnabled,{volScale:D}=this.sliceScale(),_=D.slice();this.opts.multiplanarEqualSize&&(D[0]=1,D[1]=1,D[2]=1),typeof this.opts.multiplanarPadPixels!="number"&&log.debug("multiplanarPadPixels must be numeric");const P=parseFloat(`${this.opts.multiplanarPadPixels}`),L=this.scaleSlice(D[0]+D[1],D[1]+D[2],P*1,P*1),m=Math.max(Math.max(D[1],D[2]),D[0]),k=this.scaleSlice(D[0]+D[0]+D[1],Math.max(D[1],D[2]),P*2),e=this.scaleSlice(D[0]+D[0]+D[1]+m,Math.max(D[1],D[2]),P*3),O=this.scaleSlice(m,D[1]+D[2]+D[2],0,P*2),q=this.scaleSlice(m,D[1]+D[2]+D[2]+m,0,P*3);let Q=!C&&(h<2||!g),i0=!1,j=!1,Y=!1;this.opts.multiplanarLayout===1?i0=!0:this.opts.multiplanarLayout===2?j=!0:this.opts.multiplanarLayout===3?Y=!0:O[4]>k[4]&&O[4]>L[4]?i0=!0:k[4]>L[4]?Y=!0:j=!0;let T=L;i0?(T=O,v||this.opts.multiplanarShowRender===2&&q[4]>=O[4]?T=q:Q=!1):Y&&(T=k,v||this.opts.multiplanarShowRender===2&&e[4]>=k[4]?T=e:Q=!1);const H=D[0]*T[4],G=D[1]*T[4],K=D[2]*T[4],e0=_[0]*T[4],z=_[1]*T[4],f=_[2]*T[4];if(i0){if(this.draw2D([T[0],T[1],H,G],0,NaN,[e0,z]),this.draw2D([T[0],T[1]+G+P,H,K],1,NaN,[e0,f]),this.draw2D([T[0],T[1]+G+P+K+P,G,K],2,NaN,[z,f]),Q){const f0=m*T[4];this.draw3D([T[0],T[1]+G+K+K+P*3,f0,f0])}}else if(Y){if(this.draw2D([T[0],T[1],H,G],0,NaN,[e0,z]),this.draw2D([T[0]+H+P,T[1],H,K],1,NaN,[e0,f]),this.draw2D([T[0]+H+H+P*2,T[1],G,K],2,NaN,[z,f]),Q){const f0=m*T[4];this.draw3D([T[0]+H+H+G+P*3,T[1],f0,f0])}}else j&&(v||(Q=!1),this.opts.multiplanarShowRender===2&&(Q=!0),this.draw2D([T[0],T[1]+K+P,H,G],0,NaN,[e0,z]),this.draw2D([T[0],T[1],H,K],1,NaN,[e0,f]),this.draw2D([T[0]+H+P,T[1],G,K],2,NaN,[z,f]),Q&&this.draw3D([T[0]+H+P,T[1]+K+P,G,G]))}if(this.opts.isRuler&&this.drawRuler(),this.opts.isColorbar&&this.drawColorbar(),g&&this.drawGraph(),this.uiData.isDragging){if(this.uiData.mouseButtonCenterDown){this.dragForCenterButton([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}if(this.opts.dragMode===4){this.dragForSlicer3D([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}if(this.opts.dragMode===3){this.dragForPanZoom([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}if(this.inRenderTile(this.uiData.dragStart[0],this.uiData.dragStart[1])>=0)return;if(this.opts.dragMode===2){this.drawMeasurementTool([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}const v=Math.abs(this.uiData.dragStart[0]-this.uiData.dragEnd[0]),C=Math.abs(this.uiData.dragStart[1]-this.uiData.dragEnd[1]);this.drawSelectionBox([Math.min(this.uiData.dragStart[0],this.uiData.dragEnd[0]),Math.min(this.uiData.dragStart[1],this.uiData.dragEnd[1]),v,C])}if(this.opts.clickToSegment){const v=this.mousePos[0],C=this.mousePos[1],D=this.tileIndex(v,C);if(D>-1){const _=this.screenSlices[D].fovMM,L=this.screenSlices[D].leftTopWidthHeight[2]/_[0],m=this.opts.crosshairColor,k=[m[0],m[1],m[2],.4],e=this.opts.clickToSegmentRadius*L;this.drawCircle([v-e,C-e,e*2,e*2],k,1)}}const S=this.frac2mm([this.scene.crosshairPos[0],this.scene.crosshairPos[1],this.scene.crosshairPos[2]]);return u=S[0].toFixed(2)+"×"+S[1].toFixed(2)+"×"+S[2].toFixed(2),this.readyForSync=!0,this.sync(),this.drawAnchoredLabels(),u}drawScene(){if(this.isBusy){this.needsRefresh=!0;return}this.isBusy=!1,this.needsRefresh=!1;let u=this.drawSceneCore();return this._gl!==null&&this.gl.finish(),this.needsRefresh&&(u=this.drawScene()),u}get gl(){if(!this._gl)throw new Error("unable to get WebGL context. Maybe the browser doesn't support WebGL2.");return this._gl}set gl(u){this._gl=u}},Dcm2niix=class{constructor(){this.worker=null}init(){return this.worker=new Worker(new URL(""+new URL("worker-BxHQgUvF.js",import.meta.url).href,import.meta.url),{type:"module"}),new Promise((w,u)=>{this.worker.onmessage=h=>{h.data&&h.data.type==="ready"&&w(!0)},this.worker.onerror=h=>{u(new Error(`Worker failed to load: ${h.message}`))}})}conformFileList(w){return Array.from(w).map(h=>({file:h,webkitRelativePath:h.webkitRelativePath||h._webkitRelativePath||""}))}input(w){const u=this.conformFileList(w);return new Processor({worker:this.worker,fileList:u})}inputFromWebkitDirectory(w){const u=this.conformFileList(w);return new Processor({worker:this.worker,fileList:u})}inputFromDropItems(w){const u=this.conformFileList(w);return new Processor({worker:this.worker,fileList:u})}},Processor=class{constructor({worker:w,fileList:u}){this.worker=w,this.fileList=u,this.commands=[]}_addCommand(w,...u){return this.commands.push(w,...u.map(String)),this}version(){return this._addCommand("--version")}compressionLevel(w){return this._addCommand(`-${w}`)}a(w){return this._addCommand("-a",w)}adjacent(w){return this.a(w)}b(w){return this._addCommand("-b",w)}bids(w){return this.b(w)}ba(w){return this._addCommand("-ba",w)}bidsAnonymize(w){return this.ba(w)}c(w){return this._addCommand("-c",w)}comment(w){return this.c(w)}d(w){return this._addCommand("-d",w)}directorySearchDepth(w){return this.d(w)}e(w){return this._addCommand("-e",w)}exportFormat(w){return this.e(w)}f(w){return this._addCommand("-f",w)}filenameformat(w){return this.f(w)}i(w){return this._addCommand("-i",w)}ignoreDerived(w){return this.i(w)}l(w){return this._addCommand("-l",w)}losslessScale(w){return this.l(w)}m(w){return this._addCommand("-m",w)}merge2DSlices(w){return this.m(w)}n(w){return this._addCommand("-n",w)}seriesCRC(w){return this.n(w)}p(w){return this._addCommand("-p",w)}philipsPreciseFloat(w){return this.p(w)}q(w){return this._addCommand("-q",w)}searchDirectory(w){return this.q(w)}r(w){return this._addCommand("-r",w)}renameOnly(w){return this.r(w)}s(w){return this._addCommand("-s",w)}singleFileMode(w){return this.s(w)}v(w){return this._addCommand("-v",w)}verbose(w){return this.v(w)}w(w){return this._addCommand("-w",w)}writeBehavior(w){return this.w(w)}x(w){return this._addCommand("-x",w)}crop(w){return this.x(w)}z(w){return this._addCommand("-z",w)}gzip(w){return this.z(w)}bigEndian(w){return this._addCommand("--big-endian",w)}ignoreTriggerTimes(){return this._addCommand("--ignore_trigger_times")}terse(){return this._addCommand("--terse")}xml(){return this._addCommand("--xml")}async run(){return new Promise((w,u)=>{this.worker.onmessage=g=>{if(g.data.type==="error")u(new Error(g.data.message));else{const{convertedFiles:S,exitCode:v}=g.data;v===0||v===3?w(S):u(new Error(`dcm2niix processing failed with exit code ${v}`))}};const h=[...this.commands];this.worker===null&&u(new Error("Worker not initialized. Did you await the init() method?")),this.worker.postMessage({fileList:this.fileList,cmd:h})})}};const nv=new Niivue({dragAndDropEnabled:!1});let resultFileList=[],conversionTime=0;const handleSaveButtonClick=()=>{const w=nv.volumes[0].name;nv.volumes[0].saveToDisk(w)},showSaveButton=()=>{document.getElementById("saveButton").classList.remove("hidden")},hideSaveButton=()=>{document.getElementById("saveButton").classList.add("hidden")},showLoadingCircle=()=>{loadingCircle.classList.remove("hidden")},hideLoadingCircle=()=>{loadingCircle.classList.add("hidden")},hideFileSelect=()=>{fileSelect.classList.add("hidden")},showFileSelect=()=>{fileSelect.classList.remove("hidden")},handleLocationChange=w=>{document.getElementById("intensity").innerHTML=w.string},showText=w=>{document.getElementById("intensity").innerHTML=w},removeAllVolumes=()=>{const w=nv.volumes;for(let u=0;u{if(resultFileList.length===0){console.log("No files to select from");return}const u=parseInt(w.target.value);if(u===-1)return;removeAllVolumes();const h=resultFileList[u];console.log(h);const g=await NVImage.loadFromFile({file:h,name:h.name});await nv.addVolume(g),showSaveButton()},removeSelectItems=()=>{const w=document.getElementById("fileSelect");for(;w.firstChild;)w.removeChild(w.firstChild)},updateSelectItems=w=>{removeSelectItems();const u=document.getElementById("fileSelect");u.innerHTML="";for(let g=0;g{try{hideSaveButton(),showLoadingCircle();const u=new Dcm2niix;await u.init();const h=Date.now();resultFileList=await u.input(w).run(),conversionTime=(Date.now()-h)/1e3,showText(`Conversion time: ${conversionTime} seconds`),resultFileList=resultFileList.filter(v=>v.name.endsWith(".nii")),updateSelectItems(resultFileList),console.log(resultFileList),hideLoadingCircle(),showFileSelect(),fileSelect.value=0;const S=new Event("change");fileSelect.dispatchEvent(S)}catch(u){console.error(u),resultFileList=[],hideLoadingCircle(),hideFileSelect(),showText("Error converting files. Check the console for more information.")}},ensureObjectOfObjects=w=>w.length?w:{0:w};async function handleDrop(w){w.preventDefault();const u=w.dataTransfer.items;try{showLoadingCircle();const h=[];for(let v=0;vv.name.endsWith(".nii")),updateSelectItems(resultFileList),console.log(resultFileList),hideLoadingCircle(),showFileSelect(),fileSelect.value=0;const S=new Event("change");fileSelect.dispatchEvent(S),showText("")}catch(h){console.error(h),hideLoadingCircle(),hideFileSelect(),showText("Error converting files. Check the console for more information.")}}async function traverseFileTree(w,u="",h){return new Promise(g=>{if(w.isFile)w.file(S=>{S.fullPath=u+S.name,S._webkitRelativePath=u+S.name,h.push(S),g()});else if(w.isDirectory){const S=w.createReader(),v=()=>{S.readEntries(C=>{if(C.length>0){const D=[];for(const _ of C)D.push(traverseFileTree(_,u+w.name+"/",h));Promise.all(D).then(v)}else g()})};v()}})}async function main(){fileInput.addEventListener("change",async u=>{if(u.target.files.length===0){console.log("No files selected");return}console.log("Selected files:",u.target.files);const h=u.target.files,g=ensureObjectOfObjects(h);await runDcm2niix(g)}),fileSelect.onchange=handleFileSelectChange,dropTarget.ondrop=handleDrop,dropTarget.ondragover=u=>{u.preventDefault()},saveButton.onclick=handleSaveButtonClick,nv.onLocationChange=handleLocationChange;const w=document.getElementById("gl");nv.attachToCanvas(w),nv.opts.yoke3Dto2DZoom=!0,nv.opts.crosshairGap=5,nv.setInterpolation(!0),nv.setMultiplanarLayout(MULTIPLANAR_TYPE.GRID),nv.setSliceType(SLICE_TYPE.MULTIPLANAR),nv.opts.multiplanarShowRender=SHOW_RENDER.ALWAYS,nv.opts.dragMode=DRAG_MODE.slicer3D}main(); +Ooh no!!`);break}v[_]=P,D=Math.min(D,P)}for(let _=0;_u.cal_min){C=u.cal_min,D=u.cal_max;const Y=(g-h)/(D-C);return log.info(" Robust Rescale: min: "+C+" max: "+D+" scale: "+Y),console.log("Robust Rescale: min: "+C+" max: "+D+" scale: "+Y),[C,Y]}const _=u.img,P=u.hdr.dims[1]*u.hdr.dims[2]*u.hdr.dims[3];if(u.hdr.scl_slope!==1||u.hdr.scl_inter!==0){const Y=u.img,T=new Float32Array(u.img.length);for(let H=0;H=1e-15&&L++;const m=1e3,k=(D-C)/m,e=new Array(m).fill(0);for(let Y=0;Y=q);)Q++;const i0=C;for(C=Q*k+i0,q=P-Math.floor((1-v)*L),Q=0;Q=q);)Q++;D=Q*k+i0;let j=1;return C!==D&&(j=(g-h)/(D-C)),log.info(" Rescale: min: "+C+" max: "+D+" scale: "+j),[C,j]}conformVox2Vox(u,h,g=256,S=1,v=!1){const C=h.flat(),D=fromValues$3(C[0],C[1],C[2],C[3],C[4],C[5],C[6],C[7],C[8],C[9],C[10],C[11],C[12],C[13],C[14],C[15]),_=fromValues$1(u[1]/2,u[2]/2,u[3]/2,1),P=create$1(),L=create$3();transpose(L,D),transformMat4(P,_,L);const m=fromValues$2(P[0],P[1],P[2]),k=fromValues$2(S,S,S);let e=fromValues$3(-1,0,0,0,0,0,1,0,0,-1,0,0,0,0,0,1);v&&(e=fromValues$3(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)),transpose(e,e);const O=fromValues$1(g,g,g,1),q=create$3();scale$3(q,e,k);const Q=fromValues$1(O[0],O[1],O[2],1);transformMat4(Q,Q,q),scale$1(Q,Q,.5);const i0=create$2();subtract$1(i0,m,fromValues$2(Q[0],Q[1],Q[2]));const j=create$3();transpose(j,q),j[3]=i0[0],j[7]=i0[1],j[11]=i0[2];const Y=create$3();invert(Y,j);const T=create$3();mul(T,D,Y);const H=create$3();return invert(H,T),[j,T,H]}async createNiftiArray(u=[256,256,256],h=[1,1,1],g=[1,0,0,-128,0,1,0,-128,0,0,1,-128,0,0,0,1],S=2,v=new Uint8Array){return await NVImage.createNiftiArray(u,h,g,S,v)}async niftiArray2NVImage(u=new Uint8Array){return await NVImage.loadFromUrl({url:u})}async loadFromUrl(u){return await NVImage.loadFromUrl({url:u})}async conform(u,h=!1,g=!0,S=!1,v=!1){const _=this.conformVox2Vox(u.hdr.dims,u.hdr.affine.flat(),256,1,h),P=_[0],L=_[2],m=256*256*256,k=new Float32Array(m),e=new Float32Array(u.img),O=u.hdr.dims[1]*u.hdr.dims[2]*u.hdr.dims[3];if(u.hdr.scl_slope!==1||u.hdr.scl_inter!==0)for(let f0=0;f0=q||ke>=Q||D0>=i0)continue;const Be=C0-ee,Ge=x0-re,ti=M0-Fe,ai=1-Be,ri=1-Ge,qe=1-ti,be=T(ee,re,Fe);let J0=0;J0+=e[be]*ai*ri*qe,J0+=e[be+j]*ai*ri*ti,J0+=e[be+q]*ai*Ge*qe,J0+=e[be+q+j]*ai*Ge*ti,J0+=e[be+1]*Be*ri*qe,J0+=e[be+1+j]*Be*ri*ti,J0+=e[be+1+q]*Be*Ge*qe,J0+=e[be+1+q+j]*Be*Ge*ti,k[Y]=J0}}else for(let f0=0;f0<256;f0++)for(let o0=0;o0<256;o0++){const c0=o0*L[1]+f0*L[2]+L[3],d0=o0*L[5]+f0*L[6]+L[7],v0=o0*L[9]+f0*L[10]+L[11];for(let g0=0;g0<256;g0++){const C0=Math.round(g0*H+c0),x0=Math.round(g0*G+d0),M0=Math.round(g0*K+v0);Y++,!(C0<0||x0<0||M0<0)&&(C0>=q||x0>=Q||M0>=i0||(k[Y]=e[T(C0,x0,M0)]))}}let e0=0;v&&(e0=NaN);let z=new Uint8Array;if(S){const f0=await this.getScale(u,0,1,e0),o0=await this.scalecropFloat32(k,0,1,f0[0],f0[1]);z=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),16,new Uint8Array(o0.buffer))}else{const f0=await this.getScale(u,0,255,e0),o0=await this.scalecropUint8(k,0,255,f0[0],f0[1]);z=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),2,o0)}return await this.niftiArray2NVImage(z)}setRenderDrawAmbientOcclusion(u){if(!this.renderShader)throw new Error("renderShader undefined");this.renderDrawAmbientOcclusion=u,this.renderShader.use(this.gl),this.gl.uniform1fv(this.renderShader.uniforms.renderDrawAmbientOcclusion,[this.renderDrawAmbientOcclusion,1]),this.drawScene()}setColorMap(u,h){this.setColormap(u,h)}setColormapNegative(u,h){const g=this.getVolumeIndexByID(u);this.volumes[g].colormapNegative=h,this.updateGLVolume()}setModulationImage(u,h,g=0){const S=this.getVolumeIndexByID(u);let v=null;h.length>0&&(v=this.getVolumeIndexByID(h)),this.volumes[S].modulationImage=v,this.volumes[S].modulateAlpha=g,this.updateGLVolume()}setGamma(u=1){this.scene.gamma=u,cmapper.gamma=u,this.updateGLVolume()}async loadDeferred4DVolumes(u){const h=this.getVolumeIndexByID(u),g=this.volumes[h];if(g.nTotalFrame4D<=g.nFrame4D)return;let S;g.fileObject?S=await NVImage.loadFromFile({file:g.fileObject}):S=await NVImage.loadFromUrl({url:g.url}),S&&(g.img=S.img.slice(),g.nTotalFrame4D=S.nTotalFrame4D,g.nFrame4D=S.nFrame4D,this.updateGLVolume())}setFrame4D(u,h){const g=this.getVolumeIndexByID(u),S=this.volumes[g];h>S.nFrame4D-1&&(h=S.nFrame4D-1),h<0&&(h=0),h!==S.frame4D&&(S.frame4D=h,this.updateGLVolume(),this.onFrameChange(S,h),this.createOnLocationChange())}getFrame4D(u){const h=this.getVolumeIndexByID(u);return this.volumes[h].frame4D}colormapFromKey(u){return cmapper.colormapFromKey(u)}colormap(u="",h=!1){return cmapper.colormap(u,h)}createColormapTexture(u=null,h=0,g=256){return u!==null&&this.gl.deleteTexture(u),h<1||g<1?null:(u=this.gl.createTexture(),this.gl.activeTexture(TEXTURE1_COLORMAPS),this.gl.bindTexture(this.gl.TEXTURE_2D,u),this.gl.texStorage2D(this.gl.TEXTURE_2D,1,this.gl.RGBA8,g,h),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_R,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),u)}addColormapList(u="",h=NaN,g=NaN,S=!1,v=!1,C=!0,D=!1){u.length<1&&(C=!1),this.colormapLists.push({name:u,min:h,max:g,alphaThreshold:S,negative:v,visible:C,invert:D})}refreshColormaps(){if(this.colormapLists=[],this.volumes.length<1&&this.meshes.length<1)return;const u=this.volumes.length;if(u>0)for(let C=0;C0)for(let C=0;CS[0]&&h>S[1]&&u=0&&this.screenSlices[g].axCorSag===4?g:-1}sliceScroll3D(u=0){if(u!==0){if(this.volumes.length>0&&this.scene.clipPlaneDepthAziElev[0]<1.8){const h=this.scene.clipPlaneDepthAziElev.slice();return u>0&&(h[0]=Math.min(1.5,h[0]+.025)),u<0&&(h[0]=Math.max(-1.5,h[0]-.025)),h[0]!==this.scene.clipPlaneDepthAziElev[0]?(this.scene.clipPlaneDepthAziElev=h,this.setClipPlane(this.scene.clipPlaneDepthAziElev)):void 0}u>0&&(this.scene.volScaleMultiplier=Math.min(2,this.scene.volScaleMultiplier*1.1)),u<0&&(this.scene.volScaleMultiplier=Math.max(.5,this.scene.volScaleMultiplier*.9)),this.drawScene()}}deleteThumbnail(){this.bmpTexture&&(this.gl.deleteTexture(this.bmpTexture),this.bmpTexture=null,this.thumbnailVisible=!1)}inGraphTile(u,h){if(this.graph.opacity<=0||this.volumes.length<1||this.volumes[0].nFrame4D<1||!this.graph.plotLTWH||this.graph.plotLTWH[2]<1||this.graph.plotLTWH[3]<1)return!1;const g=[(u-this.graph.LTWH[0])/this.graph.LTWH[2],(h-this.graph.LTWH[1])/this.graph.LTWH[3]];return g[0]>0&&g[1]>0&&g[0]<=1&&g[1]<=1}mouseClick(u,h,g=0,S=!0){if(u*=this.uiData.dpr,h*=this.uiData.dpr,this.canvas.focus(),this.thumbnailVisible){this.thumbnailVisible=!1,Promise.all([this.loadVolumes(this.deferredVolumes),this.loadMeshes(this.deferredMeshes)]).catch(v=>{throw v});return}if(this.inGraphTile(u,h)){if(!this.graph.plotLTWH)throw new Error("plotLTWH undefined");const v=[(u-this.graph.plotLTWH[0])/this.graph.plotLTWH[2],(h-this.graph.plotLTWH[1])/this.graph.plotLTWH[3]];if(v[0]>0&&v[1]>0&&v[0]<=1&&v[1]<=1){const C=Math.round(v[0]*(this.volumes[0].nFrame4D-1));this.setFrame4D(this.volumes[0].id,C);return}v[0]>.5&&v[1]>1&&this.loadDeferred4DVolumes(this.volumes[0].id).catch(C=>{throw C});return}if(this.inRenderTile(u,h)>=0){this.sliceScroll3D(g),this.drawScene();return}if(!(this.screenSlices.length<1||this.gl.canvas.height<1||this.gl.canvas.width<1))for(let v=0;v=0&&this.drawPenAxCorSag!==C||C>2)continue;const D=this.screenXY2TextureFrac(u,h,v,!1);if(!(D[0]<0)){if(!S){this.scene.crosshairPos[2-C]=g,this.drawScene();return}if(g!==0){let _=1;g<0&&(_=-1);const P=[0,0,0];P[2-C]=_,this.moveCrosshairInVox(P[0],P[1],P[2]),this.drawScene(),this.createOnLocationChange(C);return}if(this.opts.isForceMouseClickToVoxelCenters?this.scene.crosshairPos=clone$1(this.vox2frac(this.frac2vox(D))):this.scene.crosshairPos=clone$1(D),this.opts.drawingEnabled){const _=this.frac2vox(this.scene.crosshairPos),P=this.frac2mm(this.scene.crosshairPos);if(this.opts.clickToSegment){const L=this.opts.clickToSegmentRadius,m=this.opts.clickToSegmentSteps,k=this.opts.clickToSegmentBright?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;this.drawPenFillPts=[],this.drawPenAxCorSag=C;for(let O=1;O<=m;O++){const q=O/m*2*Math.PI,Q=P[0]+L*Math.cos(q),i0=P[1]+L*Math.sin(q),j=P[2],Y=this.back.mm2vox([Q,i0,j])[0],T=this.back.mm2vox([Q,i0,j])[1],H=this.back.mm2vox([Q,i0,j])[2];this.drawPt(Y,T,H,this.opts.penValue),this.drawPenFillPts.push([Y,T,_[2]]),O===m&&this.drawFloodFill([Y,T,_[2]],0,k,NaN,NaN,this.opts.floodFillNeighbors)}this.drawScene(),this.createOnLocationChange(C);const e=this.getDescriptives(0,[],!0);this.onClickToSegment({mL:e.volumeML,mm3:e.volumeMM3});return}if(!isFinite(this.opts.penValue)||this.opts.penValue<0||Object.is(this.opts.penValue,-0)){isFinite(this.opts.penValue)?this.drawFloodFill(_,Math.abs(this.opts.penValue),this.opts.penValue,NaN,NaN,this.opts.floodFillNeighbors):this.drawFloodFill(_,0,this.opts.penValue,NaN,NaN,this.opts.floodFillNeighbors);return}if(isNaN(this.drawPenLocation[0]))this.drawPenAxCorSag=C,this.drawPenFillPts=[],this.drawPt(..._,this.opts.penValue);else{if(_[0]===this.drawPenLocation[0]&&_[1]===this.drawPenLocation[1]&&_[2]===this.drawPenLocation[2])return;this.drawPenLine(_,this.drawPenLocation,this.opts.penValue)}this.drawPenLocation=_,this.opts.isFilledPen&&this.drawPenFillPts.push(_),this.refreshDrawing(!1)}this.drawScene(),this.createOnLocationChange(C);return}}}drawRuler(){let u=[],h=[];for(let _=0;_1){h=this.screenSlices[_].leftTopWidthHeight,u=this.screenSlices[_].fovMM;break}if(h.length<4)return;const S=100/u[0]*h[2],v=h[0]+.5*h[2]-.5*S,C=h[1]+h[3]-2*this.opts.rulerWidth,D=[v,C,v+S,C];this.drawRuler10cm(D)}drawRuler10cm(u){if(!this.lineShader)throw new Error("lineShader undefined");this.gl.bindVertexArray(this.genericVAO),this.lineShader.use(this.gl),this.gl.uniform4fv(this.lineShader.uniforms.lineColor,this.opts.rulerColor),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,this.opts.rulerWidth),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,u),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4);const h=-.1*(u[0]-u[2]),g=u[1],S=g-2*this.opts.rulerWidth,v=g-4*this.opts.rulerWidth;for(let C=0;C<11;C++){const D=u[0]+C*h,_=[D,g,D,S];C%5===0&&(_[3]=v),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,_),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}this.gl.bindVertexArray(this.unusedVAO)}screenXY2mm(u,h,g=-1){let S;for(let v=0;v=0&&(C=g),this.screenSlices[C].axCorSag>2)continue;const _=this.screenSlices[C].leftTopWidthHeight;if(u<_[0]||h<_[1]||u>_[0]+_[2]||h>_[1]+_[3]||(S=this.screenXY2TextureFrac(u,h,C,!1),S[0]<0))continue;const P=this.frac2mm(S);return fromValues$1(P[0],P[1],P[2],C)}return fromValues$1(NaN,NaN,NaN,NaN)}dragForPanZoom(u){const h=this.screenXY2mm(u[2],u[3]);if(isNaN(h[0]))return;const g=this.screenXY2mm(u[0],u[1],h[3]);if(isNaN(g[0])||isNaN(h[0])||isNaN(h[3]))return;const S=create$1(),v=this.uiData.pan2DxyzmmAtMouseDown[3];sub(S,h,g),this.scene.pan2Dxyzmm[0]=this.uiData.pan2DxyzmmAtMouseDown[0]+v*S[0],this.scene.pan2Dxyzmm[1]=this.uiData.pan2DxyzmmAtMouseDown[1]+v*S[1],this.scene.pan2Dxyzmm[2]=this.uiData.pan2DxyzmmAtMouseDown[2]+v*S[2],this.canvas.focus()}dragForCenterButton(u){this.dragForPanZoom(u)}dragForSlicer3D(u){let h=this.uiData.pan2DxyzmmAtMouseDown[3];const g=u[3]-u[1];h+=g*.01,h=Math.max(h,.1),h=Math.min(h,10);const v=this.scene.pan2Dxyzmm[3]-h;this.opts.yoke3Dto2DZoom&&(this.scene.volScaleMultiplier=h),this.scene.pan2Dxyzmm[3]=h;const C=this.frac2mm(this.scene.crosshairPos);this.scene.pan2Dxyzmm[0]+=v*C[0],this.scene.pan2Dxyzmm[1]+=v*C[1],this.scene.pan2Dxyzmm[2]+=v*C[2]}drawMeasurementTool(u){const h=this.gl;if(h.bindVertexArray(this.genericVAO),h.depthFunc(h.ALWAYS),h.enable(h.BLEND),h.blendFunc(h.SRC_ALPHA,h.ONE_MINUS_SRC_ALPHA),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),h.uniform4fv(this.lineShader.uniforms.lineColor,this.opts.rulerColor),h.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[h.canvas.width,h.canvas.height]),h.uniform1f(this.lineShader.uniforms.thickness,this.opts.rulerWidth),h.uniform4fv(this.lineShader.uniforms.startXYendXY,u),h.drawArrays(h.TRIANGLE_STRIP,0,4);const g=this.opts.rulerColor;g[3]=1,h.uniform4fv(this.lineShader.uniforms.lineColor,g);const S=this.opts.rulerWidth;h.uniform1f(this.lineShader.uniforms.thickness,S*2);let v=[u[0],u[1]-S,u[0],u[1]+S];h.uniform4fv(this.lineShader.uniforms.startXYendXY,v),h.drawArrays(h.TRIANGLE_STRIP,0,4),v=[u[2],u[3]-S,u[2],u[3]+S],h.uniform4fv(this.lineShader.uniforms.startXYendXY,v),h.drawArrays(h.TRIANGLE_STRIP,0,4);let C=this.canvasPos2frac([u[0],u[1]]),D=this.canvasPos2frac([u[2],u[3]]);if(C[0]>=0&&D[0]>=0){const _=this.frac2mm(C);C=fromValues$2(_[0],_[1],_[2]);const P=this.frac2mm(D);D=fromValues$2(P[0],P[1],P[2]);const L=create$2();sub$1(L,C,D);const m=len(L);let k=2;m>9&&(k=1),m>99&&(k=0);const e=m.toFixed(k);this.drawTextBetween(u,e,1,g)}h.bindVertexArray(this.unusedVAO)}drawRect(u,h=[1,0,0,-1]){if(h[3]<0&&(h=this.opts.crosshairColor),!this.rectShader)throw new Error("rectShader undefined");this.rectShader.use(this.gl),this.gl.enable(this.gl.BLEND),this.gl.uniform4fv(this.rectShader.uniforms.lineColor,h),this.gl.uniform2fv(this.rectShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform4f(this.rectShader.uniforms.leftTopWidthHeight,u[0],u[1],u[2],u[3]),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawCircle(u,h=this.opts.fontColor,g=1){if(!this.circleShader)throw new Error("circleShader undefined");this.circleShader.use(this.gl),this.gl.enable(this.gl.BLEND),this.gl.uniform4fv(this.circleShader.uniforms.circleColor,h),this.gl.uniform2fv(this.circleShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform4f(this.circleShader.uniforms.leftTopWidthHeight,u[0],u[1],u[2],u[3]),this.gl.uniform1f(this.circleShader.uniforms.fillPercent,g),this.gl.uniform4fv(this.circleShader.uniforms.circleColor,h),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawSelectionBox(u){this.drawRect(u,this.opts.selectionBoxColor)}effectiveCanvasHeight(){return this.gl.canvas.height-this.colorbarHeight}effectiveCanvasWidth(){return this.gl.canvas.width-this.getLegendPanelWidth()}getAllLabels(){const S=this.meshes.filter(C=>C.type==="connectome").flatMap(C=>C.nodes).map(C=>C.label).filter(C=>C!==void 0);return[...this.document.labels,...S]}getConnectomeLabels(){const S=this.meshes.filter(D=>D.type==="connectome").flatMap(D=>D.nodes).map(D=>D.label).filter(D=>D!==void 0),v=this.document.labels.filter(D=>D.anchor==null||D.anchor===0),C=new Set(S);for(const D of v)C.add(D);return Array.from(C)}getBulletMarginWidth(){let u=0;const h=this.getConnectomeLabels();if(h.length===0)return 0;const g=h.length===1?h[0].style.bulletScale:h.reduce((C,D)=>C.style.bulletScale>D.style.bulletScale?C:D).style.bulletScale,S=h.length===1?h[0]:h.reduce((C,D)=>{const _=this.opts.textHeight*this.gl.canvas.height*C.style.textScale,P=this.opts.textHeight*this.gl.canvas.height*D.style.textScale;return this.textHeight(_,C.text)>this.textHeight(P,D.text)?C:D}),v=this.opts.textHeight*this.gl.canvas.height*S.style.textScale;return u=this.textHeight(v,S.text)*g,u+=v,u}getLegendPanelWidth(){const u=this.getConnectomeLabels();if(!this.opts.showLegend||u.length===0)return 0;const g=this.opts.textHeight*this.gl.canvas.height*1;let S=0;const v=u.reduce((P,L)=>{const m=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,k=this.opts.textHeight*this.gl.canvas.height*L.style.textScale;return this.textWidth(m,P.text)>this.textWidth(k,L.text)?P:L}),C=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textWidth(C,v.text),_=this.getBulletMarginWidth();return D&&(S=_+D,S+=g*2),S>=this.gl.canvas.width?0:S}getLegendPanelHeight(){const u=this.getConnectomeLabels();let h=0;const S=this.opts.textHeight*this.gl.canvas.height*1;for(const v of u){const C=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textHeight(C,v.text);h+=D}return h&&(h+=S/2*(u.length+1)),h}reserveColorbarPanel(){let u=Math.max(this.opts.textHeight,.01);u=u*Math.min(this.gl.canvas.height,this.gl.canvas.width);const h=3*u,g=[0,this.gl.canvas.height-h,this.gl.canvas.width,h];return this.colorbarHeight=g[3]+1,g}drawColorbarCore(u=0,h=[0,0,0,0],g=!1,S=0,v=1,C){if(h[2]<=0||h[3]<=0)return;let D=Math.max(this.opts.textHeight,.01);D=D*Math.min(this.gl.canvas.height,this.gl.canvas.width);let _=D;const P=3*D;let L=D;if(h[3]0&&(O=S,S=0),S===v||D<1)return;const q=Math.abs(v-S);let[Q,i0]=tickSpacing(S,v);i0S.includes(_)).reduce((D,_)=>D.lbwh[3]>_.lbwh[3]?D:_).lbwh[3];return u*C}drawChar(u,h,g){if(!this.fontShader)throw new Error("fontShader undefined");const S=this.fontMets.mets[g],v=u[0]+h*S.lbwh[0],C=-(h*S.lbwh[1]),D=h*S.lbwh[2],_=h*S.lbwh[3],P=u[1]+(C-_)+h;return this.gl.uniform4f(this.fontShader.uniforms.leftTopWidthHeight,v,P,D,_),this.gl.uniform4fv(this.fontShader.uniforms.uvLeftTopWidthHeight,S.uv_lbwh),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),h*S.xadv}drawLoadingText(u){if(!this.canvas)throw new Error("canvas undefined");this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height),this.gl.enable(this.gl.CULL_FACE),this.gl.enable(this.gl.BLEND),this.drawTextBelow([this.canvas.width/2,this.canvas.height/2],u,3)}drawText(u,h,g=1,S=null){if(this.opts.textHeight<=0)return;if(!this.fontShader)throw new Error("fontShader undefined");this.fontShader.use(this.gl);const v=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*g;this.gl.enable(this.gl.BLEND),this.gl.uniform2f(this.fontShader.uniforms.canvasWidthHeight,this.gl.canvas.width,this.gl.canvas.height),S===null&&(S=this.opts.fontColor),this.gl.uniform4fv(this.fontShader.uniforms.fontColor,S);let C=v/this.fontMets.size*this.fontMets.distanceRange;C=Math.max(C,1),this.gl.uniform1f(this.fontShader.uniforms.screenPxRange,C);const D=new TextEncoder().encode(h);this.gl.bindVertexArray(this.genericVAO);for(let _=0;_.8?P=[0,0,0,.5]:P=[1,1,1,.5],this.drawRect(_,P),this.drawText(v,h,g,S)}drawTextBelow(u,h,g=1,S=null){if(this.opts.textHeight<=0)return;if(!this.canvas)throw new Error("canvas undefined");let v=this.opts.textHeight*this.gl.canvas.height*g,C=this.textWidth(v,h);C>this.canvas.width&&(g*=(this.canvas.width-2)/C,v=this.opts.textHeight*this.gl.canvas.height*g,C=this.textWidth(v,h)),u[0]-=.5*this.textWidth(v,h),u[0]=Math.max(u[0],1),u[0]=Math.min(u[0],this.canvas.width-C-1),this.drawText(u,h,g,S)}updateInterpolation(u,h=!1){let g=this.gl.LINEAR;!h&&this.opts.isNearestInterpolation&&(g=this.gl.NEAREST),u===0?this.gl.activeTexture(TEXTURE0_BACK_VOL):this.gl.activeTexture(TEXTURE2_OVERLAY_VOL),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MIN_FILTER,g),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MAG_FILTER,g)}setAtlasOutline(u){this.opts.atlasOutline=u,this.updateGLVolume(),this.drawScene()}setInterpolation(u){this.opts.isNearestInterpolation=u;const h=this.volumes.length;if(!(h<1)){for(let g=0;g_&&(this.drawTextBelow([u[0]+u[2]*.5,u[1]+g[1]-_],S),C=!1);const P=this.textWidth(_,v)+2;g[0]>P&&(this.drawTextRight([u[0]+g[0]-P,u[1]+u[3]*.5],v),D=!1)}C&&this.drawTextBelow([u[0]+u[2]*.5,u[1]],S),D&&this.drawTextRight([u[0],u[1]+u[3]*.5],v)}xyMM2xyzMM(u,h){let g=2;u===1&&(g=1),u===2&&(g=0);let S=[0,0,0],v=[1,1,0],C=[1,0,1];S[g]=h,v[g]=h,C[g]=h,S=this.frac2mm(S),v=this.frac2mm(v),C=this.frac2mm(C),S=this.swizzleVec3MM(fromValues$2(S[0],S[1],S[2]),u),v=this.swizzleVec3MM(fromValues$2(v[0],v[1],v[2]),u),C=this.swizzleVec3MM(fromValues$2(C[0],C[1],C[2]),u);const D=(v[0]-S[0])*(C[1]-S[1])-(C[0]-S[0])*(v[1]-S[1]);let _=(v[0]-S[0])*(C[2]-S[2])-(C[0]-S[0])*(v[2]-S[2]);_/=D;let P=(v[1]-S[1])*(C[2]-S[2])-(C[1]-S[1])*(v[2]-S[2]);P/=D;const L=[0,0,0,0,0];return L[0]=S[0],L[1]=S[1],L[2]=S[2],L[3]=P,L[4]=_,L}draw2DMain(u,h,g=NaN){let S=this.volumes[0].frac2mm.slice(),v=this.screenFieldOfViewExtendedMM(h),C=create$3();this.opts.isSliceMM||(S=this.volumes[0].frac2mmOrtho.slice(),C=clone$2(this.volumes[0].mm2ortho),v=this.screenFieldOfViewExtendedVox(h));let D=this.opts.isRadiologicalConvention&&h<2;g===1/0||g===-1/0?(D=g!==1/0,h===1&&(D=!D)):this.opts.sagittalNoseLeft&&h===2&&(D=!D);let _=0,P=0;h===2?P=D?90:-90:h===1?P=D?180:0:(P=D?180:0,_=D?-90:90);const L=this.gl;let m=!1;if(u[2]===0||u[3]===0){m=!0;const j=L.canvas.width/v.fovMM[0],Y=L.canvas.height/v.fovMM[1],T=Math.min(j,Y),H=j/T,G=Y/T;v.fovMM[0]*=H,v.fovMM[1]*=G;let K=(v.mnMM[0]+v.mxMM[0])*.5;v.mnMM[0]=K-v.fovMM[0]*.5,v.mxMM[0]=K+v.fovMM[0]*.5,K=(v.mnMM[1]+v.mxMM[1])*.5,v.mnMM[1]=K-v.fovMM[1]*.5,v.mxMM[1]=K+v.fovMM[1]*.5,u=[0,0,L.canvas.width,L.canvas.height]}if(isNaN(g)){const j=this.scene.pan2Dxyzmm,Y=this.swizzleVec3MM(fromValues$2(j[0],j[1],j[2]),h),T=this.scene.pan2Dxyzmm[3];v.mnMM[0]-=Y[0],v.mxMM[0]-=Y[0],v.mnMM[1]-=Y[1],v.mxMM[1]-=Y[1],v.mnMM[0]/=T,v.mxMM[0]/=T,v.mnMM[1]/=T,v.mxMM[1]/=T}let k=2;h===1&&(k=1),h===2&&(k=0);let e=this.scene.crosshairPos[k],O=this.frac2mm(this.scene.crosshairPos);!isNaN(g)&&g!==1/0&&g!==-1/0&&(O=this.frac2mm([.5,.5,.5]),O[k]=g,e=this.mm2frac(O)[k]);const q=O[k];L.clear(L.DEPTH_BUFFER_BIT);let Q=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,1/0,0,P,_,D);if(g===1/0||g===-1/0){const j=u.slice();this.draw3D(u,Q.modelViewProjectionMatrix,Q.modelMatrix,Q.normalMatrix,P,_);const Y=this.screenSlices[this.screenSlices.length-1];Y.leftTopWidthHeight=j,Y.axCorSag=h,Y.sliceFrac=1/0,Y.AxyzMxy=this.xyMM2xyzMM(h,e),Y.leftTopMM=Q.leftTopMM,Y.fovMM=Q.fovMM;return}L.enable(L.DEPTH_TEST),L.blendFunc(L.SRC_ALPHA,L.ONE_MINUS_SRC_ALPHA),L.disable(L.BLEND),L.depthFunc(L.GREATER),L.disable(L.CULL_FACE);let i0=this.sliceMMShader;if(this.opts.isV1SliceShader&&(i0=this.sliceV1Shader),!i0)throw new Error("slice Shader undefined");if(i0.use(this.gl),L.uniform1f(i0.uniforms.overlayOutlineWidth,this.overlayOutlineWidth),L.uniform1f(i0.uniforms.overlayAlphaShader,this.overlayAlphaShader),L.uniform1i(i0.uniforms.isAlphaClipDark,this.isAlphaClipDark?1:0),L.uniform1i(i0.uniforms.backgroundMasksOverlays,this.backgroundMasksOverlays),L.uniform1f(i0.uniforms.drawOpacity,this.drawOpacity),L.enable(L.BLEND),L.blendFunc(L.SRC_ALPHA,L.ONE_MINUS_SRC_ALPHA),L.uniform1f(i0.uniforms.opacity,this.volumes[0].opacity),L.uniform1i(i0.uniforms.axCorSag,h),L.uniform1f(i0.uniforms.slice,e),L.uniformMatrix4fv(i0.uniforms.frac2mm,!1,S),L.uniformMatrix4fv(i0.uniforms.mvpMtx,!1,Q.modelViewProjectionMatrix.slice()),L.bindVertexArray(this.genericVAO),L.drawArrays(L.TRIANGLE_STRIP,0,4),L.bindVertexArray(this.unusedVAO),this.screenSlices.push({leftTopWidthHeight:u,axCorSag:h,sliceFrac:e,AxyzMxy:this.xyMM2xyzMM(h,e),leftTopMM:Q.leftTopMM,screen2frac:[],fovMM:Q.fovMM}),isNaN(g)&&this.drawCrosshairs3D(!0,1,Q.modelViewProjectionMatrix,!0,this.opts.isSliceMM),this.opts.meshThicknessOn2D>0){this.opts.meshThicknessOn2D!==1/0&&(Q=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,this.opts.meshThicknessOn2D,q,P,_,D));const j=clone$2(Q.modelViewProjectionMatrix);multiply(j,j,C),this.drawMesh3D(!0,1,j,Q.modelMatrix,Q.normalMatrix)}isNaN(g)&&this.drawCrosshairs3D(!1,.15,Q.modelViewProjectionMatrix,!0,this.opts.isSliceMM),m&&this.drawSliceOrientationText(u,h),this.readyForSync=!0}draw2D(u,h,g=NaN,S=[NaN,NaN]){const v=[NaN,NaN];if(isNaN(S[0]))this.draw2DMain(u,h,g);else{const C=u.slice();v[0]=Math.floor(.5*(C[2]-S[0])),v[1]=Math.floor(.5*(C[3]-S[1])),C[0]+=v[0],C[1]+=v[1],C[2]=S[0],C[3]=S[1],this.draw2DMain(C,h,g)}g===1/0||g===-1/0||h===4||u[2]!==0&&u[3]!==0&&this.drawSliceOrientationText(u,h,v)}calculateMvpMatrix(u,h=[0,0,0,0],g,S){(h[2]===0||h[3]===0)&&(h=[0,0,this.gl.canvas.width,this.gl.canvas.height]);const v=h[2]/h[3];let C=this.furthestFromPivot;const D=this.pivot3D,_=create$3();C=.8*C/this.scene.volScaleMultiplier,v<1?ortho(_,-C,C,-C/v,C/v,C*.01,C*8):ortho(_,-C*v,C*v,-C,C,C*.01,C*8);const P=create$3();P[0]=-1;const L=fromValues$2(0,0,-C*1.8);translate(P,P,L),this.position&&translate(P,P,this.position),rotateX(P,P,deg2rad(270-S)),rotateZ(P,P,deg2rad(g-180)),translate(P,P,[-D[0],-D[1],-D[2]]);const m=create$3();invert(m,P);const k=create$3();transpose(k,m);const e=create$3();return multiply(e,_,P),[e,P,k]}calculateModelMatrix(u,h){if(!this.back)throw new Error("back undefined");const g=create$3();if(g[0]=-1,rotateX(g,g,deg2rad(270-h)),rotateZ(g,g,deg2rad(u-180)),this.back.obliqueRAS){const S=clone$2(this.back.obliqueRAS);multiply(g,g,S)}return g}calculateRayDirection(u,h){const g=this.calculateModelMatrix(u,h),S=fromValues$3(1,0,0,0,0,-1,0,0,0,0,-1,0,0,0,0,1),v=create$3();multiply(v,S,g);const C=create$3();invert(C,v);const D=fromValues$1(0,0,-1,1);transformMat4(D,D,C);const _=fromValues$2(D[0],D[1],D[2]);normalize$1(_,_);const P=5e-5;return Math.abs(_[0])0){if(!this.volumeObject3D)throw new Error("volumeObject3D undefined");h=fromValues$2(this.volumeObject3D.extentsMin[0],this.volumeObject3D.extentsMin[1],this.volumeObject3D.extentsMin[2]),g=fromValues$2(this.volumeObject3D.extentsMax[0],this.volumeObject3D.extentsMax[1],this.volumeObject3D.extentsMax[2]),u||(h=fromValues$2(this.volumes[0].extentsMinOrtho[0],this.volumes[0].extentsMinOrtho[1],this.volumes[0].extentsMinOrtho[2]),g=fromValues$2(this.volumes[0].extentsMaxOrtho[0],this.volumes[0].extentsMaxOrtho[1],this.volumes[0].extentsMaxOrtho[2]))}if(this.meshes.length>0){if(this.volumes.length<1){const v=this.meshes[0].extentsMin,C=this.meshes[0].extentsMax;h=fromValues$2(v[0],v[1],v[2]),g=fromValues$2(C[0],C[1],C[2])}for(let v=0;vthis.gl.canvas.width||u.LTWH[1]+u.LTWH[3]>this.gl.canvas.height)return;u.backColor=[.15,.15,.15,u.opacity],u.lineColor=[1,1,1,1],this.opts.backColor[0]+this.opts.backColor[1]+this.opts.backColor[2]>1.5&&(u.backColor=[.95,.95,.95,u.opacity],u.lineColor=[0,0,0,1]),u.textColor=u.lineColor.slice(),u.lineThickness=4,u.lineAlpha=1,u.lines=[];const g=[];if(u.vols.length<1)this.volumes[0]!=null&&g.push(0);else for(let o0=0;o0v){const o0=C-v;for(let c0=0;c0=C&&(C=v+1),this.drawRect(u.LTWH,u.backColor);const[D,_,P]=tickSpacing(v,C),L=Math.max(0,-1*Math.floor(Math.log(D)/Math.log(10)));v=Math.min(_,v),C=Math.max(P,C);function m(o0){return o0.toFixed(6).replace(/\.?0*$/,"")}const e=.07*(Math.min(u.LTWH[2],u.LTWH[3])/(this.fontMets.size*this.uiData.dpr));let O=this.opts.textHeight*this.gl.canvas.height*e;O<16&&(O=0);let q=0,Q=_;if(O>0)for(;Q<=C;){const o0=Q.toFixed(L),c0=this.textWidth(O,o0);q=Math.max(c0,q),Q+=D}const i0=.05,j=Math.abs(u.LTWH[2]),Y=Math.abs(u.LTWH[3]),T=[u.LTWH[0]+i0*j+q,u.LTWH[1]+i0*Y,u.LTWH[2]-q-2*i0*j,u.LTWH[3]-O-2*i0*Y];this.graph.LTWH=u.LTWH,this.graph.plotLTWH=T,this.drawRect(T,this.opts.backColor);const H=C-v,G=T[3]/H,K=T[2]/(u.lines[0].length-1),e0=T[1]+T[3];Q=_+.5*D;const z=u.lineColor.slice();for(z[3]=.25*u.lineColor[3];Q<=C;){const o0=e0-(Q-v)*G;this.drawLine([T[0],o0,T[0]+T[2],o0],.5*u.lineThickness,z),Q+=D}Q=_;const f=.5*u.lineThickness;for(;Q<=C;){const o0=e0-(Q-v)*G;this.drawLine([T[0]-f,o0,T[0]+T[2]+u.lineThickness,o0],u.lineThickness,u.lineColor);const c0=Q.toFixed(L);O>0&&this.drawTextLeft([T[0]-6,o0],c0,e,u.textColor),Q+=D}let f0=1;for(;u.lines[0].length/f0>20;)f0*=5;for(let o0=0;o00&&this.drawTextBelow([c0,2+T[1]+T[3]],v0,e,u.textColor),this.drawLine([c0,T[1],c0,T[1]+T[3]],d0,u.lineColor)}}for(let o0=0;o0=0&&u.selectedColumnk/255);return}const D=unpackFloatFromVec4i(C);if(D>1)return;const _=(this.mousePos[0]-u[0])/u[2],P=(g.canvas.height-this.mousePos[1]-u[1])/u[3],L=unProject(_,P,D,h),m=this.mm2frac(L,0,!0);m[0]<0||m[0]>1||m[1]<0||m[1]>1||m[2]<0||m[2]>1||(this.scene.crosshairPos=this.mm2frac(L,0,!0))}drawImage3D(u,h,g){if(this.volumes.length===0)return;const S=this.gl,v=this.calculateRayDirection(h,g),C=this.volumeObject3D;if(C){S.enable(S.BLEND),S.blendFunc(S.SRC_ALPHA,S.ONE_MINUS_SRC_ALPHA),S.enable(S.CULL_FACE),S.cullFace(S.FRONT);let D=this.renderShader;if(this.uiData.mouseDepthPicker&&(D=this.pickingImageShader),D.use(this.gl),S.uniform1i(D.uniforms.backgroundMasksOverlays,this.backgroundMasksOverlays),this.gradientTextureAmount>0){S.activeTexture(TEXTURE6_GRADIENT),S.bindTexture(S.TEXTURE_3D,this.gradientTexture);const _=this.calculateModelMatrix(h,g),P=create$3();invert(P,_);const L=create$3();transpose(L,P),S.uniformMatrix4fv(D.uniforms.normMtx,!1,L)}this.drawBitmap&&this.drawBitmap.length>8?S.uniform2f(D.uniforms.renderDrawAmbientOcclusionXY,this.renderDrawAmbientOcclusion,this.drawOpacity):S.uniform2f(D.uniforms.renderDrawAmbientOcclusionXY,this.renderDrawAmbientOcclusion,0),S.uniformMatrix4fv(D.uniforms.mvpMtx,!1,u),S.uniformMatrix4fv(D.uniforms.matRAS,!1,this.back.matRAS),S.uniform3fv(D.uniforms.rayDir,v),this.gradientTextureAmount<0?S.uniform4fv(D.uniforms.clipPlane,[this.scene.crosshairPos[0],this.scene.crosshairPos[1],this.scene.crosshairPos[2],30]):S.uniform4fv(D.uniforms.clipPlane,this.scene.clipPlane),S.uniform1f(D.uniforms.drawOpacity,1),S.bindVertexArray(C.vao),S.drawElements(C.mode,C.indexCount,S.UNSIGNED_SHORT,0),S.bindVertexArray(this.unusedVAO)}}drawOrientationCube(u,h=0,g=0){if(!this.opts.isOrientCube)return;const S=.05*Math.min(u[2],u[3]);if(S<5)return;const v=this.gl;v.enable(v.CULL_FACE),v.cullFace(v.BACK),this.orientCubeShader.use(this.gl),v.bindVertexArray(this.orientCubeShaderVAO);const C=create$3(),D=create$3();ortho(D,0,v.canvas.width,0,v.canvas.height,-10*S,10*S);let _=0;u[1]===0&&(_=v.canvas.height-this.effectiveCanvasHeight()),translate(C,C,[1.8*S+u[0],_+1.8*S+u[1],0]),scale$3(C,C,[S,S,S]),rotateX(C,C,deg2rad(270-g)),rotateZ(C,C,deg2rad(-h));const P=create$3();multiply(P,D,C),v.uniformMatrix4fv(this.orientCubeShader.uniforms.u_matrix,!1,P),v.drawArrays(v.TRIANGLE_STRIP,0,168),v.bindVertexArray(this.unusedVAO),this.gl.disable(this.gl.CULL_FACE)}createOnLocationChange(u=NaN){const[h,g,S]=this.sceneExtentsMinMax(!0),v=Math.max(Math.max(S[0],S[1]),S[2]);function C(k){return Math.max(0,-Math.ceil(Math.log10(Math.abs(k))))}let D=C(v*.001);const _=this.frac2mm(this.scene.crosshairPos,0,!0);function P(k,e=0){return parseFloat(k.toFixed(e))}let L=P(_[0],D)+"×"+P(_[1],D)+"×"+P(_[2],D);if(this.volumes.length>0&&this.volumes[0].nFrame4D>0&&(L+="×"+P(this.volumes[0].frame4D)),this.volumes.length>0){let k=" = ";for(let q=0;q=0&&j=0&&(k+="+"),k+=P(i0,D)),k+=" "}L+=k;const e=this.back.dimsRAS,O=e[1]*e[2]*e[3];if(this.drawBitmap&&this.drawBitmap.length===O){const q=this.frac2vox(this.scene.crosshairPos),Q=q[0]+q[1]*e[1]+q[2]*e[1]*e[2];L+=" "+this.drawLut.labels[this.drawBitmap[Q]]}}const m={mm:this.frac2mm(this.scene.crosshairPos,0,!0),axCorSag:u,vox:this.frac2vox(this.scene.crosshairPos),frac:this.scene.crosshairPos,xy:[this.mousePos[0],this.mousePos[1]],values:this.volumes.map(k=>{const e=this.frac2mm(this.scene.crosshairPos,0,!0),O=k.mm2vox(e),q=k.getValue(O[0],O[1],O[2],k.frame4D);return{name:k.name,value:q,id:k.id,mm:e,vox:O}}),string:L};this.onLocationChange(m)}addLabel(u,h,g,S,v){const C={textColor:this.opts.legendTextColor,textScale:1,textAlignment:"left",lineWidth:0,lineColor:this.opts.legendTextColor,lineTerminator:"none",bulletScale:0,bulletColor:this.opts.legendTextColor},D=h?{...C,...h}:{...C},_=new NVLabel3D(u,{...D},g,S,v);return this.document.labels.push(_),_}calculateScreenPoint(u,h,g){const S=create$1();return transformMat4(S,[...u,1],h),S[3]!==0&&(S[0]=(S[0]/S[3]+1)*.5*g[2],S[1]=(1-S[1]/S[3])*.5*g[3],S[2]/=S[3],S[0]+=g[0],S[1]+=g[1]),S}getLabelAtPoint(u){const g=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,S=this.opts.textHeight*this.gl.canvas.height*1;for(const L of this.document.labels){if(L.anchor==null||L.anchor===0)continue;const m=this.opts.textHeight*this.gl.canvas.height*L.style.textScale,k=this.textHeight(m,L.text),e=this.textWidth(m,L.text);if(!(L.anchor&1&&u[0]>e)&&!(L.anchor&2&&(u[0]<(this.gl.canvas.width-e)/2||u[0]>(this.gl.canvas.width+e)/2))&&!(L.anchor&4&&u[0]k+S/2))&&!(L.anchor&16&&(u[1]<(this.gl.canvas.height-k-S)/2||u[1]>(this.gl.canvas.height+k-S/2)/2))&&!(L.anchor&32&&(u[1]this.gl.canvas.height-S/2)))return L}log.debug("screenPoint",u);const v=this.getLegendPanelHeight(),C=this.getLegendPanelWidth(),D=this.gl.canvas.width-C;let _=(this.canvas.height-v)/2;if(log.debug("panelrect",D,_,D+C,_+v),u[0]D+C||u[1]>_+v)return null;const P=this.getConnectomeLabels();for(const L of P){const m=this.opts.textHeight*this.gl.canvas.height*L.style.textScale,k=this.textHeight(m,L.text);if(u[1]>=_&&u[1]<=_+k+g/2)return L;_+=k,_+=g/2}return null}drawLabelLine(u,h,g,S,v=!1){const C=Array.isArray(u.points)&&Array.isArray(u.points[0])?u.points:[u.points];for(const D of C){const _=this.calculateScreenPoint(D,g,S);v?this.drawDottedLine([...h,_[0],_[1]],u.style.lineWidth,u.style.lineColor):this.draw3DLine(h,[_[0],_[1],_[2]],u.style.lineWidth,u.style.lineColor)}}draw3DLabel(u,h,g,S,v,C,D){const _=u.text,P=h[0],L=h[1],m=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,k=this.textHeight(u.style.textScale,_)*m;if(u.style.lineWidth>0&&Array.isArray(u.points)&&this.drawLabelLine(u,[P,L+k],g,S,D),u.style.bulletScale){const O=u.style.bulletScale*k,q=k-O,Q=L+q/2+O/2,i0=P+(v-O)/2;this.drawCircle([i0,Q,O,O],u.style.bulletColor)}let e=P;if(C)if(u.style.textAlignment!=="left"){const O=this.textWidth(u.style.textScale,u.text)*m;if(u.style.textAlignment==="right")e=P+C-m*1.5-O;else{const q=C-(v||m);e+=(q-O)/2}}else e+=v;this.drawText([e,L],_,u.style.textScale,u.style.textColor)}draw3DLabels(u,h,g=!1){const S=this.getConnectomeLabels();if(!this.opts.showLegend||S.length===0)return;if(!this.canvas)throw new Error("canvas undefined");const v=this.gl;v.disable(v.CULL_FACE),v.viewport(0,0,this.canvas.width,this.canvas.height);const D=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,_=this.getBulletMarginWidth(),P=this.getLegendPanelHeight(),L=this.getLegendPanelWidth(),m=v.canvas.width-L;let k=(this.canvas.height-P)/2;this.drawRect([v.canvas.width-L,k,L-D,P],this.opts.legendBackgroundColor);const e=v.getParameter(v.BLEND),O=v.getParameter(v.DEPTH_FUNC);g||(v.disable(v.BLEND),v.depthFunc(v.GREATER));for(const q of S){this.draw3DLabel(q,[m,k],u,h,_,L,g);const Q=this.opts.textHeight*this.gl.canvas.height*q.style.textScale,i0=this.textHeight(Q,q.text);k+=i0,k+=D/2}g||(v.depthFunc(O),e&&v.enable(v.BLEND))}drawAnchoredLabels(){const u=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,h=this.document.labels.filter(g=>g.anchor!=null&&g.anchor!==0);for(const g of h){const S=g.text,v=this.textHeight(g.style.textScale,S)*u,C=this.textWidth(g.style.textScale,S)*u;let D,_;const L=this.opts.textHeight*this.gl.canvas.height*1,m=L;let k=L/4,e=0,O=0;g.anchor&1&&(D=0),g.anchor&4&&(D=this.canvas.width-C,e-=L/4),g.anchor&2&&(D=(this.canvas.width-C)/2,e-=L/4,k+=L/4),g.anchor&8&&(_=0),g.anchor&16&&(_=(this.canvas.height-v-L)/2,O-=L/4),g.anchor&32&&(_=this.canvas.height-v-L,O-=L/4),this.drawRect([D+e,_+O,C+k,v+m],g.style.backgroundColor),this.draw3DLabel(g,[D,_])}}draw3D(u=[0,0,0,0],h=null,g=null,S=null,v=null,C=0){const D=v!==null;this.setPivot3D(),D||(v=this.scene.renderAzimuth,C=this.scene.renderElevation);const _=this.gl;h===null&&([h,g,S]=this.calculateMvpMatrix(null,u,v,C));let P=[...u];if(u[2]===0||u[3]===0?(u=[0,0,_.canvas.width,_.canvas.height],P=[...u],this.screenSlices.push({leftTopWidthHeight:u,axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(g),0]})):(this.screenSlices.push({leftTopWidthHeight:u.slice(),axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(g),0]}),u[1]=_.canvas.height-u[3]-u[1]),_.enable(_.DEPTH_TEST),_.depthFunc(_.ALWAYS),_.depthMask(!0),_.clearDepth(0),this.draw3DLabels(h,P,!1),_.viewport(u[0],u[1],u[2],u[3]),this.volumes.length>0&&(this.updateInterpolation(0,!0),this.updateInterpolation(1,!0),this.drawImage3D(h,v,C)),this.updateInterpolation(0),this.updateInterpolation(1),D||this.drawCrosshairs3D(!0,1,h),this.drawMesh3D(!0,1,h,g,S),this.uiData.mouseDepthPicker){this.depthPicker(u,h),this.createOnLocationChange(),this.draw3D(u,h,g,S,v,C);return}this.opts.meshXRay>0&&this.drawMesh3D(!1,this.opts.meshXRay,h,g,S),this.draw3DLabels(h,P,!1),_.viewport(u[0],u[1],u[2],u[3]),D||this.drawCrosshairs3D(!1,.15,h),_.viewport(0,0,_.canvas.width,_.canvas.height),this.drawOrientationCube(u,v,C);const L="azimuth: "+this.scene.renderAzimuth.toFixed(0)+" elevation: "+this.scene.renderElevation.toFixed(0);return this.readyForSync=!0,this.sync(),this.draw3DLabels(h,P,!0),L}drawMesh3D(u=!0,h=1,g,S,v){if(this.meshes.length<1)return;const C=this.gl;g||([g,S,v]=this.calculateMvpMatrix(this.volumeObject3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),C.enable(C.DEPTH_TEST),C.blendFunc(C.SRC_ALPHA,C.ONE_MINUS_SRC_ALPHA),C.disable(C.BLEND),C.depthFunc(C.GREATER),C.disable(C.CULL_FACE),u?(C.disable(C.BLEND),C.depthFunc(C.GREATER)):(C.enable(C.BLEND),C.depthFunc(C.ALWAYS),C.enable(C.CULL_FACE)),C.cullFace(C.BACK);let D=this.meshShaders[0].shader,_=!1;for(let P=0;P=3&&this.meshes[P].fiberRadius>0||(C.bindVertexArray(this.meshes[P].vaoFiber),C.drawElements(C.LINE_STRIP,this.meshes[P].indexCount,C.UNSIGNED_INT,0),C.bindVertexArray(this.unusedVAO)));C.enable(C.BLEND),C.depthFunc(C.ALWAYS),this.readyForSync=!0}drawCrosshairs3D(u=!0,h=1,g=null,S=!1,v=!0){if(!this.opts.show3Dcrosshair&&!S||this.opts.crosshairWidth<=0&&S)return;const C=this.gl,D=this.frac2mm(this.scene.crosshairPos,0,v);if(this.crosshairs3D===null||this.crosshairs3D.mm[0]!==D[0]||this.crosshairs3D.mm[1]!==D[1]||this.crosshairs3D.mm[2]!==D[2]){this.crosshairs3D!==null&&(C.deleteBuffer(this.crosshairs3D.indexBuffer),C.deleteBuffer(this.crosshairs3D.vertexBuffer));const[L,m,k]=this.sceneExtentsMinMax(v);let e=1;if(this.volumes.length>0){if(!this.back)throw new Error("back undefined");e=.5*Math.min(Math.min(this.back.pixDims[1],this.back.pixDims[2]),this.back.pixDims[3])}else(k[0]<50||k[0]>1e3)&&(e=k[0]*.02);e*=this.opts.crosshairWidth,this.crosshairs3D=NiivueObject3D.generateCrosshairs(this.gl,1,D,L,m,e,20,this.opts.crosshairGap),this.crosshairs3D.mm=D}if(!this.surfaceShader)throw new Error("surfaceShader undefined");const _=this.surfaceShader;_.use(this.gl),g==null&&([g]=this.calculateMvpMatrix(this.crosshairs3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),C.uniformMatrix4fv(_.uniforms.mvpMtx,!1,g),C.bindBuffer(C.ELEMENT_ARRAY_BUFFER,this.crosshairs3D.indexBuffer),C.enable(C.DEPTH_TEST);const P=[...this.opts.crosshairColor];u?(C.disable(C.BLEND),C.depthFunc(C.GREATER)):(C.enable(C.BLEND),C.blendFunc(C.SRC_ALPHA,C.ONE_MINUS_SRC_ALPHA),C.depthFunc(C.ALWAYS)),P[3]=h,C.uniform4fv(_.uniforms.surfaceColor,P),C.bindVertexArray(this.crosshairs3D.vao),C.drawElements(C.TRIANGLES,this.crosshairs3D.indexCount,C.UNSIGNED_INT,0),C.bindVertexArray(this.unusedVAO)}mm2frac(u,h=0,g=!1){if(this.volumes.length<1){const S=fromValues$2(.1,.5,.5),[v,C,D]=this.sceneExtentsMinMax();return S[0]=(u[0]-v[0])/D[0],S[1]=(u[1]-v[1])/D[1],S[2]=(u[2]-v[2])/D[2],isFinite(S)||(isFinite(S[0])||(S[0]=.5),isFinite(S[1])||(S[1]=.5),isFinite(S[2])||(S[2]=.5),this.meshes.length<1&&log.error("mm2frac() not finite: objects not (yet) loaded.")),S}return this.volumes[h].convertMM2Frac(u,g||this.opts.isSliceMM)}vox2frac(u,h=0){return this.volumes[h].convertVox2Frac(u)}frac2vox(u,h=0){return this.volumes.length<=h?[0,0,0]:this.volumes[h].convertFrac2Vox(u)}moveCrosshairInVox(u,h,g){const S=this.frac2vox(this.scene.crosshairPos);S[0]+=u,S[1]+=h,S[2]+=g,S[0]=clamp(S[0],0,this.volumes[0].dimsRAS[1]-1),S[1]=clamp(S[1],0,this.volumes[0].dimsRAS[2]-1),S[2]=clamp(S[2],0,this.volumes[0].dimsRAS[3]-1),this.scene.crosshairPos=this.vox2frac(S),this.createOnLocationChange(),this.drawScene()}frac2mm(u,h=0,g=!1){const S=fromValues$1(u[0],u[1],u[2],1);if(this.volumes.length>0)return this.volumes[h].convertFrac2MM(u,g||this.opts.isSliceMM);{const[v,C]=this.sceneExtentsMinMax(),D=(_,P,L)=>_*(1-L)+P*L;S[0]=D(v[0],C[0],u[0]),S[1]=D(v[1],C[1],u[1]),S[2]=D(v[2],C[2],u[2])}return S}screenXY2TextureFrac(u,h,g,S=!0){const v=fromValues$2(-1,-1,-1),C=this.screenSlices[g].axCorSag;if(C>2)return v;const D=this.screenSlices[g].leftTopWidthHeight.slice();let _=!1;D[2]<0&&(_=!0,D[0]+=D[2],D[2]=-D[2]);let P=(u-D[0])/D[2];_&&(P=1-P);const L=1-(h-D[1])/D[3];if(P<0||P>1||L<0||L>1||this.screenSlices[g].AxyzMxy.length<4)return v;let m=fromValues$2(0,0,0);m[0]=this.screenSlices[g].leftTopMM[0]+P*this.screenSlices[g].fovMM[0],m[1]=this.screenSlices[g].leftTopMM[1]+L*this.screenSlices[g].fovMM[1];const k=this.screenSlices[g].AxyzMxy;m[2]=k[2]+k[4]*(m[1]-k[1])-k[3]*(m[0]-k[0]),C===1&&(m=swizzleVec3(m,[0,2,1])),C===2&&(m=swizzleVec3(m,[2,0,1]));const e=this.mm2frac(m);return S&&(e[0]<0||e[0]>1||e[1]<0||e[1]>1||e[2]<0||e[2]>1)?v:e}canvasPos2frac(u){for(let h=0;h=0)return g}return[-1,-1,-1]}scaleSlice(u,h,g=0,S=0){const v=this.effectiveCanvasWidth()-g,C=this.effectiveCanvasHeight()-S;let D=v/u;h*D>C&&(D=C/h);const _=u*D,P=h*D;return[(v-_)*.5,(C-P)*.5,_,P,D]}drawThumbnail(){if(!this.bmpShader)throw new Error("bmpShader undefined");this.bmpShader.use(this.gl),this.gl.uniform2f(this.bmpShader.uniforms.canvasWidthHeight,this.gl.canvas.width,this.gl.canvas.height);let u=this.gl.canvas.height,h=this.gl.canvas.height*this.bmpTextureWH;h>this.gl.canvas.width&&(u=this.gl.canvas.width/this.bmpTextureWH,h=this.gl.canvas.width),this.gl.uniform4f(this.bmpShader.uniforms.leftTopWidthHeight,0,0,h,u),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawLine(u,h=1,g=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),g[3]<0&&(g=this.opts.crosshairColor),this.gl.uniform4fv(this.lineShader.uniforms.lineColor,g),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,h),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,u),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}draw3DLine(u,h,g=1,S=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.line3DShader)throw new Error("line3DShader undefined");this.line3DShader.use(this.gl),S[3]<0&&(S=this.opts.crosshairColor),this.gl.uniform4fv(this.line3DShader.uniforms.lineColor,S),this.gl.uniform2fv(this.line3DShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.line3DShader.uniforms.thickness,g),this.gl.uniform2fv(this.line3DShader.uniforms.startXY,u),this.gl.uniform3fv(this.line3DShader.uniforms.endXYZ,h),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawDottedLine(u,h=1,g=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl);const S=g[3]<0?[...this.opts.crosshairColor]:[...g];S[3]=.3;const v=fromValues(u[2]-u[0],u[3]-u[1]),C=length(v);normalize(v,v);const _=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1;scale(v,v,_/2);const P=length(v);let L=Math.floor(C/P);C%P&&L++;const m=[u[0],u[1]];this.gl.uniform4fv(this.lineShader.uniforms.lineColor,S),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,h);for(let k=0;k0&&h===0){const e=D,O=1;for(let q=0;q0&&h===1){const e=D,O=2;for(let q=0;q0&&h===2){const e=D,O=2;for(let q=0;q0&&h===0){const e=D,O=0;for(let q=0;q0&&h===1){const e=D,O=0;for(let q=0;q0&&h===2){const e=D,O=1;for(let q=0;q0){const P=C.leftTopWidthHeight.slice();let L=2;h===0&&(L=1);const m=this.frac2mm([.5,.5,.5]);for(let k=0;k0){const P=C.leftTopWidthHeight.slice(),L=C.fovMM[0]<0;let m=0;h===2&&(m=1);const k=this.frac2mm([.5,.5,.5]);for(let e=0;e<_.length;e++){k[m]=_[e];const O=this.mm2frac(k);L?this.drawRect([P[0]+(P[2]-O[m]*P[2]),P[1],1,P[3]]):this.drawRect([P[0]+O[m]*P[2],P[1],1,P[3]])}}}drawMosaic(u){if(this.volumes.length===0){log.debug("Unable to draw mosaic until voxel-based image is loaded");return}this.screenSlices=[];const h=this.screenFieldOfViewMM(0,!0),g=this.screenFieldOfViewMM(0);u=u.replaceAll(";"," ;").trim();const S=[],v=[],C=[],D=u.split(/\s+/);let _=1;const P=this.opts.textHeight;let L=0,m=0;for(let k=0;k<2;k++){let e=!1,O=!1;e=!1;let q=0,Q=0,i0=0,j=0,Y=!1,T=0;for(let K=0;K"u"){if(this.meshes.length>0){this.screenSlices=[],this.opts.sliceType=4,this.draw3D(),this.opts.isColorbar&&this.drawColorbar();return}this.drawLoadingText(this.loadingText);return}if(this.back===null)return;if(this.uiData.isDragging&&this.scene.clipPlaneDepthAziElev[0]<1.8&&this.inRenderTile(this.uiData.dragStart[0],this.uiData.dragStart[1])>=0){const v=this.uiData.dragStart[0]-this.uiData.dragEnd[0],C=this.uiData.dragStart[1]-this.uiData.dragEnd[1],D=this.uiData.dragClipPlaneStartDepthAziElev.slice();if(D[1]-=v,D[1]=D[1]%360,D[2]+=C,D[1]!==this.scene.clipPlaneDepthAziElev[1]||D[2]!==this.scene.clipPlaneDepthAziElev[2])return this.scene.clipPlaneDepthAziElev=D,this.setClipPlane(this.scene.clipPlaneDepthAziElev)}if(this.sliceMosaicString.length<1&&this.opts.sliceType===4){this.opts.isColorbar&&this.reserveColorbarPanel(),this.screenSlices=[],this.draw3D(),this.opts.isColorbar&&this.drawColorbar();return}this.opts.isColorbar&&this.reserveColorbarPanel();const h=this.getMaxVols(),g=this.opts.sliceType===3&&h>1&&this.graph.autoSizeMultiplanar&&this.graph.opacity>0;if(this.sliceMosaicString.length>0)this.drawMosaic(this.sliceMosaicString);else if(this.gl.viewport(0,0,this.gl.canvas.width,this.gl.canvas.height),this.screenSlices=[],this.opts.sliceType===0)this.draw2D([0,0,0,0],0);else if(this.opts.sliceType===1)this.draw2D([0,0,0,0],1);else if(this.opts.sliceType===2)this.draw2D([0,0,0,0],2);else{let v=!1;this.opts.multiplanarForceRender?(v=!0,this.opts.multiplanarForceRender?this.opts.multiplanarShowRender=1:this.opts.multiplanarShowRender=2,delete this.opts.multiplanarForceRender):this.opts.multiplanarShowRender===1&&(v=!0);const C=isFinite(this.drawPenLocation[0])&&this.opts.drawingEnabled,{volScale:D}=this.sliceScale(),_=D.slice();this.opts.multiplanarEqualSize&&(D[0]=1,D[1]=1,D[2]=1),typeof this.opts.multiplanarPadPixels!="number"&&log.debug("multiplanarPadPixels must be numeric");const P=parseFloat(`${this.opts.multiplanarPadPixels}`),L=this.scaleSlice(D[0]+D[1],D[1]+D[2],P*1,P*1),m=Math.max(Math.max(D[1],D[2]),D[0]),k=this.scaleSlice(D[0]+D[0]+D[1],Math.max(D[1],D[2]),P*2),e=this.scaleSlice(D[0]+D[0]+D[1]+m,Math.max(D[1],D[2]),P*3),O=this.scaleSlice(m,D[1]+D[2]+D[2],0,P*2),q=this.scaleSlice(m,D[1]+D[2]+D[2]+m,0,P*3);let Q=!C&&(h<2||!g),i0=!1,j=!1,Y=!1;this.opts.multiplanarLayout===1?i0=!0:this.opts.multiplanarLayout===2?j=!0:this.opts.multiplanarLayout===3?Y=!0:O[4]>k[4]&&O[4]>L[4]?i0=!0:k[4]>L[4]?Y=!0:j=!0;let T=L;i0?(T=O,v||this.opts.multiplanarShowRender===2&&q[4]>=O[4]?T=q:Q=!1):Y&&(T=k,v||this.opts.multiplanarShowRender===2&&e[4]>=k[4]?T=e:Q=!1);const H=D[0]*T[4],G=D[1]*T[4],K=D[2]*T[4],e0=_[0]*T[4],z=_[1]*T[4],f=_[2]*T[4];if(i0){if(this.draw2D([T[0],T[1],H,G],0,NaN,[e0,z]),this.draw2D([T[0],T[1]+G+P,H,K],1,NaN,[e0,f]),this.draw2D([T[0],T[1]+G+P+K+P,G,K],2,NaN,[z,f]),Q){const f0=m*T[4];this.draw3D([T[0],T[1]+G+K+K+P*3,f0,f0])}}else if(Y){if(this.draw2D([T[0],T[1],H,G],0,NaN,[e0,z]),this.draw2D([T[0]+H+P,T[1],H,K],1,NaN,[e0,f]),this.draw2D([T[0]+H+H+P*2,T[1],G,K],2,NaN,[z,f]),Q){const f0=m*T[4];this.draw3D([T[0]+H+H+G+P*3,T[1],f0,f0])}}else j&&(v||(Q=!1),this.opts.multiplanarShowRender===2&&(Q=!0),this.draw2D([T[0],T[1]+K+P,H,G],0,NaN,[e0,z]),this.draw2D([T[0],T[1],H,K],1,NaN,[e0,f]),this.draw2D([T[0]+H+P,T[1],G,K],2,NaN,[z,f]),Q&&this.draw3D([T[0]+H+P,T[1]+K+P,G,G]))}if(this.opts.isRuler&&this.drawRuler(),this.opts.isColorbar&&this.drawColorbar(),g&&this.drawGraph(),this.uiData.isDragging){if(this.uiData.mouseButtonCenterDown){this.dragForCenterButton([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}if(this.opts.dragMode===4){this.dragForSlicer3D([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}if(this.opts.dragMode===3){this.dragForPanZoom([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}if(this.inRenderTile(this.uiData.dragStart[0],this.uiData.dragStart[1])>=0)return;if(this.opts.dragMode===2){this.drawMeasurementTool([this.uiData.dragStart[0],this.uiData.dragStart[1],this.uiData.dragEnd[0],this.uiData.dragEnd[1]]);return}const v=Math.abs(this.uiData.dragStart[0]-this.uiData.dragEnd[0]),C=Math.abs(this.uiData.dragStart[1]-this.uiData.dragEnd[1]);this.drawSelectionBox([Math.min(this.uiData.dragStart[0],this.uiData.dragEnd[0]),Math.min(this.uiData.dragStart[1],this.uiData.dragEnd[1]),v,C])}if(this.opts.clickToSegment){const v=this.mousePos[0],C=this.mousePos[1],D=this.tileIndex(v,C);if(D>-1){const _=this.screenSlices[D].fovMM,L=this.screenSlices[D].leftTopWidthHeight[2]/_[0],m=this.opts.crosshairColor,k=[m[0],m[1],m[2],.4],e=this.opts.clickToSegmentRadius*L;this.drawCircle([v-e,C-e,e*2,e*2],k,1)}}const S=this.frac2mm([this.scene.crosshairPos[0],this.scene.crosshairPos[1],this.scene.crosshairPos[2]]);return u=S[0].toFixed(2)+"×"+S[1].toFixed(2)+"×"+S[2].toFixed(2),this.readyForSync=!0,this.sync(),this.drawAnchoredLabels(),u}drawScene(){if(this.isBusy){this.needsRefresh=!0;return}this.isBusy=!1,this.needsRefresh=!1;let u=this.drawSceneCore();return this._gl!==null&&this.gl.finish(),this.needsRefresh&&(u=this.drawScene()),u}get gl(){if(!this._gl)throw new Error("unable to get WebGL context. Maybe the browser doesn't support WebGL2.");return this._gl}set gl(u){this._gl=u}},Dcm2niix=class{constructor(){this.worker=null}init(){return this.worker=new Worker(new URL(""+new URL("worker-BxHQgUvF.js",import.meta.url).href,import.meta.url),{type:"module"}),new Promise((w,u)=>{this.worker.onmessage=h=>{h.data&&h.data.type==="ready"&&w(!0)},this.worker.onerror=h=>{u(new Error(`Worker failed to load: ${h.message}`))}})}conformFileList(w){return Array.from(w).map(h=>({file:h,webkitRelativePath:h.webkitRelativePath||h._webkitRelativePath||""}))}input(w){const u=this.conformFileList(w);return new Processor({worker:this.worker,fileList:u})}inputFromWebkitDirectory(w){const u=this.conformFileList(w);return new Processor({worker:this.worker,fileList:u})}inputFromDropItems(w){const u=this.conformFileList(w);return new Processor({worker:this.worker,fileList:u})}},Processor=class{constructor({worker:w,fileList:u}){this.worker=w,this.fileList=u,this.commands=[]}_addCommand(w,...u){return this.commands.push(w,...u.map(String)),this}version(){return this._addCommand("--version")}compressionLevel(w){return this._addCommand(`-${w}`)}a(w){return this._addCommand("-a",w)}adjacent(w){return this.a(w)}b(w){return this._addCommand("-b",w)}bids(w){return this.b(w)}ba(w){return this._addCommand("-ba",w)}bidsAnonymize(w){return this.ba(w)}c(w){return this._addCommand("-c",w)}comment(w){return this.c(w)}d(w){return this._addCommand("-d",w)}directorySearchDepth(w){return this.d(w)}e(w){return this._addCommand("-e",w)}exportFormat(w){return this.e(w)}f(w){return this._addCommand("-f",w)}filenameformat(w){return this.f(w)}i(w){return this._addCommand("-i",w)}ignoreDerived(w){return this.i(w)}l(w){return this._addCommand("-l",w)}losslessScale(w){return this.l(w)}m(w){return this._addCommand("-m",w)}merge2DSlices(w){return this.m(w)}n(w){return this._addCommand("-n",w)}seriesCRC(w){return this.n(w)}p(w){return this._addCommand("-p",w)}philipsPreciseFloat(w){return this.p(w)}q(w){return this._addCommand("-q",w)}searchDirectory(w){return this.q(w)}r(w){return this._addCommand("-r",w)}renameOnly(w){return this.r(w)}s(w){return this._addCommand("-s",w)}singleFileMode(w){return this.s(w)}v(w){return this._addCommand("-v",w)}verbose(w){return this.v(w)}w(w){return this._addCommand("-w",w)}writeBehavior(w){return this.w(w)}x(w){return this._addCommand("-x",w)}crop(w){return this.x(w)}z(w){return this._addCommand("-z",w)}gzip(w){return this.z(w)}bigEndian(w){return this._addCommand("--big-endian",w)}ignoreTriggerTimes(){return this._addCommand("--ignore_trigger_times")}terse(){return this._addCommand("--terse")}xml(){return this._addCommand("--xml")}async run(){return new Promise((w,u)=>{this.worker.onmessage=g=>{if(g.data.type==="error")u(new Error(g.data.message));else{const{convertedFiles:S,exitCode:v}=g.data;v===0||v===3?w(S):u(new Error(`dcm2niix processing failed with exit code ${v}`))}};const h=[...this.commands];this.worker===null&&u(new Error("Worker not initialized. Did you await the init() method?")),this.worker.postMessage({fileList:this.fileList,cmd:h})})}};const nv=new Niivue({dragAndDropEnabled:!1});let resultFileList=[],conversionTime=0,downloadFile=null;const handleSaveButtonClick=()=>{let w=URL.createObjectURL(downloadFile);const u=document.createElement("a");u.href=w,u.download=downloadFile.name,u.click()},showSaveButton=()=>{document.getElementById("saveButton").classList.remove("hidden")},hideSaveButton=()=>{document.getElementById("saveButton").classList.add("hidden")},showLoadingCircle=()=>{loadingCircle.classList.remove("hidden")},hideLoadingCircle=()=>{loadingCircle.classList.add("hidden")},hideFileSelect=()=>{fileSelect.classList.add("hidden")},showFileSelect=()=>{fileSelect.classList.remove("hidden")},handleLocationChange=w=>{document.getElementById("intensity").innerHTML=w.string},showText=w=>{document.getElementById("intensity").innerHTML=w},removeAllVolumes=()=>{const w=nv.volumes;for(let u=0;u{if(resultFileList.length===0){console.log("No files to select from");return}const u=parseInt(w.target.value);if(u===-1)return;const h=resultFileList[u];if(downloadFile=h,h.name.endsWith(".nii")){removeAllVolumes(),console.log(h);const g=await NVImage.loadFromFile({file:h,name:h.name});await nv.addVolume(g)}showSaveButton()},removeSelectItems=()=>{const w=document.getElementById("fileSelect");for(;w.firstChild;)w.removeChild(w.firstChild)},updateSelectItems=w=>{removeSelectItems();const u=document.getElementById("fileSelect");u.innerHTML="";for(let g=0;g{try{hideSaveButton(),showLoadingCircle();const u=new Dcm2niix;await u.init();const h=Date.now();resultFileList=await u.input(w).run(),conversionTime=(Date.now()-h)/1e3,showText(`Conversion time: ${conversionTime} seconds`),updateSelectItems(resultFileList),console.log(resultFileList),hideLoadingCircle(),showFileSelect(),fileSelect.value=0;const S=new Event("change");fileSelect.dispatchEvent(S)}catch(u){console.error(u),resultFileList=[],hideLoadingCircle(),hideFileSelect(),showText("Error converting files. Check the console for more information.")}},ensureObjectOfObjects=w=>w.length?w:{0:w};async function handleDrop(w){w.preventDefault();const u=w.dataTransfer.items;try{showLoadingCircle();const h=[];for(let v=0;vv.name.endsWith(".nii")),updateSelectItems(resultFileList),console.log(resultFileList),hideLoadingCircle(),showFileSelect(),fileSelect.value=0;const S=new Event("change");fileSelect.dispatchEvent(S),showText("")}catch(h){console.error(h),hideLoadingCircle(),hideFileSelect(),showText("Error converting files. Check the console for more information.")}}async function traverseFileTree(w,u="",h){return new Promise(g=>{if(w.isFile)w.file(S=>{S.fullPath=u+S.name,S._webkitRelativePath=u+S.name,h.push(S),g()});else if(w.isDirectory){const S=w.createReader(),v=()=>{S.readEntries(C=>{if(C.length>0){const D=[];for(const _ of C)D.push(traverseFileTree(_,u+w.name+"/",h));Promise.all(D).then(v)}else g()})};v()}})}async function main(){fileInput.addEventListener("change",async u=>{if(u.target.files.length===0){console.log("No files selected");return}console.log("Selected files:",u.target.files);const h=u.target.files,g=ensureObjectOfObjects(h);await runDcm2niix(g)}),fileSelect.onchange=handleFileSelectChange,dropTarget.ondrop=handleDrop,dropTarget.ondragover=u=>{u.preventDefault()},saveButton.onclick=handleSaveButtonClick,nv.onLocationChange=handleLocationChange;const w=document.getElementById("gl");nv.attachToCanvas(w),nv.opts.yoke3Dto2DZoom=!0,nv.opts.crosshairGap=5,nv.setInterpolation(!0),nv.setMultiplanarLayout(MULTIPLANAR_TYPE.GRID),nv.setSliceType(SLICE_TYPE.MULTIPLANAR),nv.opts.multiplanarShowRender=SHOW_RENDER.ALWAYS,nv.opts.dragMode=DRAG_MODE.slicer3D}main(); diff --git a/index.html b/index.html index 16288c0..e90e20a 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ NiiVue + dcm2niix WASM - + @@ -32,7 +32,7 @@
  • - +