diff --git a/assets/index-BeZ2s21u.js b/assets/index-CNZOlbQj.js similarity index 99% rename from assets/index-BeZ2s21u.js rename to assets/index-CNZOlbQj.js index 19c888e..347a0e8 100644 --- a/assets/index-BeZ2s21u.js +++ b/assets/index-CNZOlbQj.js @@ -1825,6 +1825,6 @@ 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 loadVolumes(u){if(this.loadingText="loading...",this.drawScene(),this.thumbnailVisible)return this.deferredVolumes=u,this;if(this.volumes=[],this.gl.clearColor(0,0,0,1),this.gl.clear(this.gl.COLOR_BUFFER_BIT),u.length>1)return await this.addVolumesFromUrl(u),this;const h={url:u[0].url,headers:u[0].headers,name:u[0].name,colormap:u[0].colormap?u[0].colormap:u[0].colorMap,colormapNegative:u[0].colormapNegative?u[0].colormapNegative:u[0].colorMapNegative,opacity:u[0].opacity,urlImgData:u[0].urlImgData,cal_min:u[0].cal_min,cal_max:u[0].cal_max,trustCalMinMax:this.opts.trustCalMinMax,isManifest:u[0].isManifest,frame4D:u[0].frame4D,limitFrames4D:u[0].limitFrames4D||this.opts.limitFrames4D,colorbarVisible:u[0].colorbarVisible};return await this.addVolumeFromUrl(h),this}async addMeshFromUrl(u){const h=this.getFileExt(u.url);if(h==="JCON"||h==="JSON"){const v=await(await fetch(u.url,{})).json(),w=this.loadConnectomeAsMesh(v);return this.mediaUrlMap.set(w,u.url),this.onMeshAddedFromUrl(u,w),this.addMesh(w),w}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.url);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 w=await NVMesh2.loadFromUrl({...S,gl:this.gl});return this.mediaUrlMap.set(w,S.url),this.onMeshAddedFromUrl(S,w),w}),g=await Promise.all(h);for(let S=0;S1?(await this.addMeshesFromUrl(u),this.updateGLVolume(),this.drawScene(),this):(await this.addMeshFromUrl(u[0]),this.updateGLVolume(),this.drawScene(),this))}async loadConnectomeFromUrl(u,h={}){const S=await(await fetch(u,{headers:h})).json();return this.loadConnectome(S)}async loadFreeSurferConnectomeFromUrl(u,h={}){const S=await(await fetch(u,{headers:h})).json();return this.loadFreeSurferConnectome(S)}async loadFreeSurferConnectome(u){const h=NVConnectome.convertFreeSurferConnectome(u);return this.loadConnectome(h)}handleNodeAdded(u){const h=u.detail.node,g=[1,1,1,1];this.addLabel(h.name,{textColor:g,bulletScale:1,bulletColor:g,lineWidth:0,lineColor:g,lineTerminator:"none",textScale:1},[h.x,h.y,h.z]),this.drawScene()}loadConnectomeAsMesh(u){let h=u;if("data_type"in u&&u.data_type==="fs_pointset")h=NVConnectome.convertFreeSurferConnectome(u),log.warn("converted FreeSurfer connectome",h);else if("nodes"in u){const g=u.nodes;"names"in g&&"X"in g&&"Y"in g&&"Z"in g&&"Color"in g&&"Size"in g&&(h=NVConnectome.convertLegacyConnectome(u),log.warn("converted legacy connectome",h))}else throw new Error("not a known connectome format");return new NVConnectome(this.gl,h)}loadConnectome(u){this.loadingText="loading...",this.drawScene(),this.meshes=[],this.gl.clearColor(0,0,0,1),this.gl.clear(this.gl.COLOR_BUFFER_BIT);const h=this.loadConnectomeAsMesh(u);return this.addMesh(h),this.drawScene(),this}createEmptyDrawing(){if(this.back===null||!this.back.dims||Math.min(Math.min(this.back.dims[1],this.back.dims[2]),this.back.dims[3])<1)return;const h=this.back.dims[1]*this.back.dims[2]*this.back.dims[3];this.drawBitmap=new Uint8Array(h),this.drawClearAllUndoBitmaps(),this.drawAddUndoBitmap(),this.drawTexture=this.r8Tex(this.drawTexture,TEXTURE7_DRAW,this.back.dims,!0),this.refreshDrawing(!1)}r16Tex(u,h,g,S){u&&this.gl.deleteTexture(u),u=this.gl.createTexture(),this.gl.activeTexture(h),this.gl.bindTexture(this.gl.TEXTURE_3D,u),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),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R16I,g[1],g[2],g[3]);const v=g[1]*g[2]*g[3];return S.length!==v&&(S=new Int16Array(v)),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,g[1],g[2],g[3],this.gl.RED_INTEGER,this.gl.SHORT,S),u}drawGrowCut(){if(!this.back||!this.back.dims)throw new Error("back not defined");const u=this.back.hdr,h=this.gl,g=u.dims[1]*u.dims[2]*u.dims[3];if(!this.drawBitmap||this.drawBitmap.length!==g){log.debug("bitmap dims are wrong");return}const S=h.createFramebuffer();h.bindFramebuffer(h.FRAMEBUFFER,S),h.disable(h.CULL_FACE),h.viewport(0,0,this.back.dims[1],this.back.dims[2]),h.disable(h.BLEND);let v=img2ras16(this.back);const w=this.r16Tex(null,TEXTURE11_GC_BACK,this.back.dims,v);for(let Y=1;Y0&&(v[Y]=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 Y=0;Yu[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],N=h[1],B=h[2];if(S>=v&&S>=w){let Q=2*v-S,e0=2*w-S;for(;L!==e;)L+=D,Q>=0&&(m+=_,Q-=2*S),e0>=0&&(k+=P,e0-=2*S),Q+=2*v,e0+=2*w,this.drawPt(L,m,k,g)}else if(v>=S&&v>=w){let Q=2*S-v,e0=2*w-v;for(;m!==N;)m+=_,Q>=0&&(L+=D,Q-=2*v),e0>=0&&(k+=P,e0-=2*v),Q+=2*S,e0+=2*w,this.drawPt(L,m,k,g)}else{let Q=2*v-w,e0=2*S-w;for(;k!==B;)k+=P,Q>=0&&(m+=_,Q-=2*w),e0>=0&&(L+=D,e0-=2*w),Q+=2*v,e0+=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],w=v*S[1];function D(m){return m[0]+m[1]*v+m[2]*w}function _(m){const k=Math.floor(m/w),e=Math.floor((m-k*w)/v);return[Math.floor(m%v),e,k]}const P=[];for(P.push(h),u[h]=2;P.length>0;){let m=function(N){const B=e.slice();if(B[0]+=N[0],B[1]+=N[1],B[2]+=N[2],B[0]<0||B[1]<0||B[2]<0||B[0]>=S[0]||B[1]>=S[1]||B[2]>=S[2])return;const Q=D(B);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,w=6){var B;if(!this.drawBitmap)throw new Error("drawBitmap undefined");if(!((B=this.back)!=null&&B.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),N=m[e];if(N===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[H]<=e0&&(m[H]=1);this.drawFloodFillCore(m,e,w),h=N}for(let Q=1;QB[0]&&(H=1),Q[1]>B[1]&&(y=1);let Y=B[0],G=B[1];const Z=Q[0],i0=Q[1];if(e0>=j){let z=2*j-e0;for(;Y!==Z;)Y+=H,z>=0&&(G+=y,z-=2*e0),z+=2*j,w[Y+G*v[0]]=D}else{let z=2*e0-j;for(;G!==i0;)G+=y,z>=0&&(Y+=H,z-=2*j),z+=2*e0,w[Y+G*v[0]]=D}}const P=[this.drawPenFillPts[0][g],this.drawPenFillPts[0][S]];let L=P;for(let B=1;B=v[0]||B[1]>=v[1])return;const Q=B[0]+B[1]*v[0];w[Q]===0&&(m.push(B),w[Q]=2)}for(let B=0;B0;){const B=m.shift();k([B[0]-1,B[1]]),k([B[0]+1,B[1]]),k([B[0],B[1]-1]),k([B[0],B[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 B=e*v[0]*v[1];for(let Q=0;Q0){const B=this.drawBitmap.length,Q=decodeRLE(this.drawUndoBitmaps[this.currentDrawUndoBitmap],B);for(let e0=0;e0{const v=new Image;v.onload=()=>{if(!this.bmpShader)return;let w;h===4?(this.bmpTexture!==null&&this.gl.deleteTexture(this.bmpTexture),this.bmpTexture=this.gl.createTexture(),w=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(),w=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(),w=this.fontTexture),this.gl.bindTexture(this.gl.TEXTURE_2D,w),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(w),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 w=h.createFramebuffer();h.bindFramebuffer(h.FRAMEBUFFER,w),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 Z=0;Z0&&u.frame4D1&&v===0)return;let w=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,w=this.rgbaTex(this.volumeTexture,TEXTURE0_BACK_VOL,u.dimsRAS);const{volScale:G,vox:Z}=this.sliceScale(!0);if(this.volScale=G,this.vox=Z,this.volumeObject3D.scale=G,!this.renderShader)throw new Error("renderShader undefined");this.renderShader.use(this.gl),this.gl.uniform3fv(this.renderShader.uniforms.texVox,Z),this.gl.uniform3fv(this.renderShader.uniforms.volScale,G);const i0=this.pickingImageShader;i0.use(this.gl),this.gl.uniform1i(i0.uniforms.volume,0),this.gl.uniform1i(i0.uniforms.colormap,1),this.gl.uniform1i(i0.uniforms.overlay,2),this.gl.uniform3fv(i0.uniforms.volScale,G),log.debug(this.volumeObject3D)}else{((Y=this.back)==null?void 0:Y.dims)===void 0&&log.error("Fatal error: Unable to render overlay: background dimensions not defined!");const G=this.mm2frac(u.mm000,0,!0);let Z=this.mm2frac(u.mm100,0,!0),i0=this.mm2frac(u.mm010,0,!0),z=this.mm2frac(u.mm001,0,!0);Z=subtract$1(Z,Z,G),i0=subtract$1(i0,i0,G),z=subtract$1(z,z,G),D=fromValues$3(Z[0],i0[0],z[0],G[0],Z[1],i0[1],z[1],G[1],Z[2],i0[2],z[2],G[2],0,0,0,1),invert(D,D),h===1?(w=this.rgbaTex(this.overlayTexture,TEXTURE2_OVERLAY_VOL,this.back.dims),this.overlayTexture=w,this.overlayTextureID=w):w=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 Z=0;Z7){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,N=Number.NEGATIVE_INFINITY;if(u.colormapNegative.length>0&&(e=Math.min(-u.cal_min,-u.cal_max),N=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),N=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,N),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 B=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 T0=Math.abs(u.modulateAlpha);T0=Math.max(T0,1);const x0=this.volumes[u.modulationImage].frame4D*Z;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 e0=this.sliceScale(!0),j=e0.vox,H=e0.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,H),!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 y=this.sliceMMShader;if(this.opts.isV1SliceShader&&(y=this.sliceV1Shader),!y)throw new Error("slice shader undefined");y.use(this.gl),this.gl.uniform1f(y.uniforms.overlays,this.overlays.length),this.gl.uniform1f(y.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(y.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,w,D,_){const P=new Uint32Array(27);let L=0;if(!v)return 0;const m=u[this.idx(g,S,v,w)];if(D>=6){const k=this.idx(g,S,v-1,w);m===u[k]&&(P[L++]=h[k])}if(D>=18){if(g){const k=this.idx(g-1,S,v-1,w);m===u[k]&&(P[L++]=h[k])}if(S){const k=this.idx(g,S-1,v-1,w);m===u[k]&&(P[L++]=h[k])}if(g=6){if(k){const B=this.idx(k-1,m,L,h);N===u[B]&&(P[e++]=_[B])}if(m){const B=this.idx(k,m-1,L,h);N===u[B]&&(P[e++]=_[B])}}if(g>=18){if(m&&k){const B=this.idx(k-1,m-1,L,h);N===u[B]&&(P[e++]=_[B])}if(m&&k=w){w+=v;const B=new Uint32Array(w);B.set(D),D=B}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){w=u.cal_min,D=u.cal_max;const H=(g-h)/(D-w);return log.info(" Robust Rescale: min: "+w+" max: "+D+" scale: "+H),console.log("Robust Rescale: min: "+w+" max: "+D+" scale: "+H),[w,H]}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 H=u.img,y=new Float32Array(u.img.length);for(let Y=0;Y=1e-15&&L++;const m=1e3,k=(D-w)/m,e=new Array(m).fill(0);for(let H=0;H=B);)Q++;const e0=w;for(w=Q*k+e0,B=P-Math.floor((1-v)*L),Q=0;Q=B);)Q++;D=Q*k+e0;let j=1;return w!==D&&(j=(g-h)/(D-w)),log.info(" Rescale: min: "+w+" max: "+D+" scale: "+j),[w,j]}conformVox2Vox(u,h,g=256,S=1,v=!1){const w=h.flat(),D=fromValues$3(w[0],w[1],w[2],w[3],w[4],w[5],w[6],w[7],w[8],w[9],w[10],w[11],w[12],w[13],w[14],w[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 N=fromValues$1(g,g,g,1),B=create$3();scale$3(B,e,k);const Q=fromValues$1(N[0],N[1],N[2],1);transformMat4(Q,Q,B),scale$1(Q,Q,.5);const e0=create$2();subtract$1(e0,m,fromValues$2(Q[0],Q[1],Q[2]));const j=create$3();transpose(j,B),j[3]=e0[0],j[7]=e0[1],j[11]=e0[2];const H=create$3();invert(H,j);const y=create$3();mul(y,D,H);const Y=create$3();return invert(Y,y),[j,y,Y]}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),N=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=B||ke>=Q||I0>=e0)continue;const Be=T0-ee,Ge=x0-re,ti=M0-Fe,ai=1-Be,ri=1-Ge,qe=1-ti,be=y(ee,re,Fe);let J0=0;J0+=e[be]*ai*ri*qe,J0+=e[be+j]*ai*ri*ti,J0+=e[be+B]*ai*Ge*qe,J0+=e[be+B+j]*ai*Ge*ti,J0+=e[be+1]*Be*ri*qe,J0+=e[be+1+j]*Be*ri*ti,J0+=e[be+1+B]*Be*Ge*qe,J0+=e[be+1+B+j]*Be*Ge*ti,k[H]=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 T0=Math.round(g0*Y+c0),x0=Math.round(g0*G+d0),M0=Math.round(g0*Z+v0);H++,!(T0<0||x0<0||M0<0)&&(T0>=B||x0>=Q||M0>=e0||(k[H]=e[y(T0,x0,M0)]))}}let i0=0;v&&(i0=NaN);let z=new Uint8Array;if(S){const f0=await this.getScale(u,0,1,i0),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,i0),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){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,w=!0,D=!1){u.length<1&&(w=!1),this.colormapLists.push({name:u,min:h,max:g,alphaThreshold:S,negative:v,visible:w,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 w=0;w0)for(let w=0;wS[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 w=Math.round(v[0]*(this.volumes[0].nFrame4D-1));this.setFrame4D(this.volumes[0].id,w);return}v[0]>.5&&v[1]>1&&this.loadDeferred4DVolumes(this.volumes[0].id).catch(w=>{throw w});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!==w||w>2)continue;const D=this.screenXY2TextureFrac(u,h,v,!1);if(!(D[0]<0)){if(!S){this.scene.crosshairPos[2-w]=g,this.drawScene();return}if(g!==0){let _=1;g<0&&(_=-1);const P=[0,0,0];P[2-w]=_,this.moveCrosshairInVox(P[0],P[1],P[2]),this.drawScene(),this.createOnLocationChange(w);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);if(this.opts.clickToSegment){const P=this.opts.clickToSegmentRadius,L=this.opts.clickToSegmentSteps,m=this.opts.clickToSegmentBright?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;this.drawPenFillPts=[],this.drawPenAxCorSag=w;for(let e=1;e<=L;e++){const N=e/L*2*Math.PI;let B=_[0]+P*Math.cos(N),Q=_[1]+P*Math.sin(N);const e0=_[2];B=Math.round(B),Q=Math.round(Q),this.drawPt(B,Q,e0,this.opts.penValue),this.drawPenFillPts.push([B,Q,_[2]]),e===L&&this.drawFloodFill([B,Q,_[2]],0,m,NaN,NaN,this.opts.floodFillNeighbors)}this.drawScene(),this.createOnLocationChange(w);const k=this.getDescriptives(0,[],!0);this.onClickToSegment({mL:k.volumeML,mm3:k.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=w,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(w);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,w=h[1]+h[3]-2*this.opts.rulerWidth,D=[v,w,v+S,w];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 w=0;w<11;w++){const D=u[0]+w*h,_=[D,g,D,S];w%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&&(w=g),this.screenSlices[w].axCorSag>2)continue;const _=this.screenSlices[w].leftTopWidthHeight;if(u<_[0]||h<_[1]||u>_[0]+_[2]||h>_[1]+_[3]||(S=this.screenXY2TextureFrac(u,h,w,!1),S[0]<0))continue;const P=this.frac2mm(S);return fromValues$1(P[0],P[1],P[2],w)}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 w=this.frac2mm(this.scene.crosshairPos);this.scene.pan2Dxyzmm[0]+=v*w[0],this.scene.pan2Dxyzmm[1]+=v*w[1],this.scene.pan2Dxyzmm[2]+=v*w[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 w=this.canvasPos2frac([u[0],u[1]]),D=this.canvasPos2frac([u[2],u[3]]);if(w[0]>=0&&D[0]>=0){const _=this.frac2mm(w);w=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,w,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(w=>w.type==="connectome").flatMap(w=>w.nodes).map(w=>w.label).filter(w=>w!==void 0);return[...this.document.labels,...S]}getBulletMarginWidth(){let u=0;const h=this.getAllLabels();if(h.length===0)return 0;const g=h.length===1?h[0].style.bulletScale:h.reduce((w,D)=>w.style.bulletScale>D.style.bulletScale?w:D).style.bulletScale,S=h.length===1?h[0]:h.reduce((w,D)=>{const _=this.opts.textHeight*this.gl.canvas.height*w.style.textScale,P=this.opts.textHeight*this.gl.canvas.height*D.style.textScale;return this.textHeight(_,w.text)>this.textHeight(P,D.text)?w: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.getAllLabels();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}),w=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textWidth(w,v.text),_=this.getBulletMarginWidth();return D&&(S=_+D,S+=g*2),S>=this.gl.canvas.width?0:S}getLegendPanelHeight(){const u=this.getAllLabels();let h=0;const S=this.opts.textHeight*this.gl.canvas.height*1;for(const v of u){const w=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textHeight(w,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,w){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&&(N=S,S=0),S===v||D<1)return;const B=Math.abs(v-S);let[Q,e0]=tickSpacing(S,v);e0S.includes(_)).reduce((D,_)=>D.lbwh[3]>_.lbwh[3]?D:_).lbwh[3];return u*w}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],w=-(h*S.lbwh[1]),D=h*S.lbwh[2],_=h*S.lbwh[3],P=u[1]+(w-_)+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 w=v/this.fontMets.size*this.fontMets.distanceRange;w=Math.max(w,1),this.gl.uniform1f(this.fontShader.uniforms.screenPxRange,w);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,w=this.textWidth(v,h);w>this.canvas.width&&(g*=(this.canvas.width-2)/w,v=this.opts.textHeight*this.gl.canvas.height*g,w=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-w-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;g0){this.opts.meshThicknessOn2D!==1/0&&(B=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,this.opts.meshThicknessOn2D,N,P,_,D));const e0=clone$2(B.modelViewProjectionMatrix);multiply(e0,e0,w),this.drawMesh3D(!0,1,e0,B.modelMatrix,B.normalMatrix)}isNaN(g)&&this.drawCrosshairs3D(!1,.15,B.modelViewProjectionMatrix,!0,this.opts.isSliceMM),this.drawSliceOrientationText(u,h),this.readyForSync=!0}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 w=this.furthestFromPivot;const D=this.pivot3D,_=create$3();w=.8*w/this.scene.volScaleMultiplier,v<1?ortho(_,-w,w,-w/v,w/v,w*.01,w*8):ortho(_,-w*v,w*v,-w,w,w*.01,w*8);const P=create$3();P[0]=-1;const L=fromValues$2(0,0,-w*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 w=create$3();invert(w,v);const D=fromValues$1(0,0,-1,1);transformMat4(D,D,w);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,w=this.meshes[0].extentsMax;h=fromValues$2(v[0],v[1],v[2]),g=fromValues$2(w[0],w[1],w[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=w-v;for(let c0=0;c0=w&&(w=v+1),this.drawRect(u.LTWH,u.backColor);const[D,_,P]=tickSpacing(v,w),L=Math.max(0,-1*Math.floor(Math.log(D)/Math.log(10)));v=Math.min(_,v),w=Math.max(P,w);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 N=this.opts.textHeight*this.gl.canvas.height*e;N<16&&(N=0);let B=0,Q=_;if(N>0)for(;Q<=w;){const o0=Q.toFixed(L),c0=this.textWidth(N,o0);B=Math.max(c0,B),Q+=D}const e0=.05,j=Math.abs(u.LTWH[2]),H=Math.abs(u.LTWH[3]),y=[u.LTWH[0]+e0*j+B,u.LTWH[1]+e0*H,u.LTWH[2]-B-2*e0*j,u.LTWH[3]-N-2*e0*H];this.graph.LTWH=u.LTWH,this.graph.plotLTWH=y,this.drawRect(y,this.opts.backColor);const Y=w-v,G=y[3]/Y,Z=y[2]/(u.lines[0].length-1),i0=y[1]+y[3];Q=_+.5*D;const z=u.lineColor.slice();for(z[3]=.25*u.lineColor[3];Q<=w;){const o0=i0-(Q-v)*G;this.drawLine([y[0],o0,y[0]+y[2],o0],.5*u.lineThickness,z),Q+=D}Q=_;const f=.5*u.lineThickness;for(;Q<=w;){const o0=i0-(Q-v)*G;this.drawLine([y[0]-f,o0,y[0]+y[2]+u.lineThickness,o0],u.lineThickness,u.lineColor);const c0=Q.toFixed(L);N>0&&this.drawTextLeft([y[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+y[1]+y[3]],v0,e,u.textColor),this.drawLine([c0,y[1],c0,y[1]+y[3]],d0,u.lineColor)}}for(let o0=0;o0=0&&u.selectedColumnk/255);return}const D=unpackFloatFromVec4i(w);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),w=this.volumeObject3D;if(w){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(w.vao),S.drawElements(w.mode,w.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 w=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(w,w,[1.8*S+u[0],_+1.8*S+u[1],0]),scale$3(w,w,[S,S,S]),rotateX(w,w,deg2rad(270-g)),rotateZ(w,w,deg2rad(-h));const P=create$3();multiply(P,D,w),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 w(k){return Math.max(0,-Math.ceil(Math.log10(Math.abs(k))))}let D=w(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 B=0;B=0&&j=0&&(k+="+"),k+=P(e0,D)),k+=" "}L+=k;const e=this.back.dimsRAS,N=e[1]*e[2]*e[3];if(this.drawBitmap&&this.drawBitmap.length===N){const B=this.frac2vox(this.scene.crosshairPos),Q=B[0]+B[1]*e[1]+B[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),N=k.mm2vox(e),B=k.getValue(N[0],N[1],N[2],k.frame4D);return{name:k.name,value:B,id:k.id,mm:e,vox:N}}),string:L};this.onLocationChange(m)}addLabel(u,h,g){const S={textColor:this.opts.legendTextColor,textScale:1,textAlignment:"left",lineWidth:0,lineColor:this.opts.legendTextColor,lineTerminator:"none",bulletScale:0,bulletColor:this.opts.legendTextColor},v=h?{...S,...h}:{...S},w=new NVLabel3D(u,v,g);return this.document.labels.push(w),w}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){log.debug("screenPoint",u);const h=this.getLegendPanelHeight(),g=this.getLegendPanelWidth(),S=this.gl.canvas.width-g;let v=(this.canvas.height-h)/2;if(log.debug("panelrect",S,v,S+g,v+h),u[0]S+g||u[1]>v+h)return null;const D=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,_=this.getAllLabels();for(const P of _){const L=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,m=this.textHeight(L,P.text);if(u[1]>=v&&u[1]<=v+m+D/2)return P;v+=m,v+=D/2}return null}drawLabelLine(u,h,g,S,v=!1){const w=Array.isArray(u.points)&&Array.isArray(u.points[0])?u.points:[u.points];for(const D of w){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=0,w,D=!1){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 N=u.style.bulletScale*k,B=k-N,Q=L+B/2+N/2,e0=P+(v-N)/2;this.drawCircle([e0,Q,N,N],u.style.bulletColor)}let e=P;if(u.style.textAlignment!=="left"){const N=this.textWidth(u.style.textScale,u.text)*m;if(u.style.textAlignment==="right")e=P+w-m*1.5-N;else{const B=w-(v||m);e+=(B-N)/2}}else e+=v;this.drawText([e,L],_,u.style.textScale,u.style.textColor)}draw3DLabels(u,h,g=!1){const S=this.getAllLabels();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),N=v.getParameter(v.DEPTH_FUNC);g||(v.disable(v.BLEND),v.depthFunc(v.GREATER));for(const B of S){this.draw3DLabel(B,[m,k],u,h,_,L,g);const Q=this.opts.textHeight*this.gl.canvas.height*B.style.textScale,e0=this.textHeight(Q,B.text);k+=e0,k+=D/2}g||(v.depthFunc(N),e&&v.enable(v.BLEND))}draw3D(u=[0,0,0,0],h=null,g=null,S=null,v=null,w=0){const D=v!==null;this.setPivot3D(),D||(v=this.scene.renderAzimuth,w=this.scene.renderElevation);const _=this.gl;h===null&&([h,g,S]=this.calculateMvpMatrix(null,u,v,w));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,w)),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,w);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,w);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 w=this.gl;g||([g,S,v]=this.calculateMvpMatrix(this.volumeObject3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),w.enable(w.DEPTH_TEST),w.blendFunc(w.SRC_ALPHA,w.ONE_MINUS_SRC_ALPHA),w.disable(w.BLEND),w.depthFunc(w.GREATER),w.disable(w.CULL_FACE),u?(w.disable(w.BLEND),w.depthFunc(w.GREATER)):(w.enable(w.BLEND),w.depthFunc(w.ALWAYS),w.enable(w.CULL_FACE)),w.cullFace(w.BACK);let D=this.meshShaders[0].shader,_=!1;for(let P=0;P=3&&this.meshes[P].fiberRadius>0||(w.bindVertexArray(this.meshes[P].vaoFiber),w.drawElements(w.LINE_STRIP,this.meshes[P].indexCount,w.UNSIGNED_INT,0),w.bindVertexArray(this.unusedVAO)));w.enable(w.BLEND),w.depthFunc(w.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 w=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&&(w.deleteBuffer(this.crosshairs3D.indexBuffer),w.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)),w.uniformMatrix4fv(_.uniforms.mvpMtx,!1,g),w.bindBuffer(w.ELEMENT_ARRAY_BUFFER,this.crosshairs3D.indexBuffer),w.enable(w.DEPTH_TEST);const P=[...this.opts.crosshairColor];u?(w.disable(w.BLEND),w.depthFunc(w.GREATER)):(w.enable(w.BLEND),w.blendFunc(w.SRC_ALPHA,w.ONE_MINUS_SRC_ALPHA),w.depthFunc(w.ALWAYS)),P[3]=h,w.uniform4fv(_.uniforms.surfaceColor,P),w.bindVertexArray(this.crosshairs3D.vao),w.drawElements(w.TRIANGLES,this.crosshairs3D.indexCount,w.UNSIGNED_INT,0),w.bindVertexArray(this.unusedVAO)}mm2frac(u,h=0,g=!1){if(this.volumes.length<1){const S=fromValues$2(.1,.5,.5),[v,w,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,w]=this.sceneExtentsMinMax(),D=(_,P,L)=>_*(1-L)+P*L;S[0]=D(v[0],w[0],u[0]),S[1]=D(v[1],w[1],u[1]),S[2]=D(v[2],w[2],u[2])}return S}screenXY2TextureFrac(u,h,g,S=!0){const v=fromValues$2(-1,-1,-1),w=this.screenSlices[g].axCorSag;if(w>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]),w===1&&(m=swizzleVec3(m,[0,2,1])),w===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,w=this.effectiveCanvasHeight()-S;let D=v/u;h*D>w&&(D=w/h);const _=u*D,P=h*D;return[(v-_)*.5,(w-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]),w=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(w/P);w%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,N=1;for(let B=0;B0&&h===1){const e=D,N=2;for(let B=0;B0&&h===2){const e=D,N=2;for(let B=0;B0&&h===0){const e=D,N=0;for(let B=0;B0&&h===1){const e=D,N=0;for(let B=0;B0&&h===2){const e=D,N=1;for(let B=0;B0){const P=w.leftTopWidthHeight.slice();let L=2;h===0&&(L=1);const m=this.frac2mm([.5,.5,.5]);for(let k=0;k0){const P=w.leftTopWidthHeight.slice(),L=w.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 N=this.mm2frac(k);L?this.drawRect([P[0]+(P[2]-N[m]*P[2]),P[1],1,P[3]]):this.drawRect([P[0]+N[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=[],w=[],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,N=!1;e=!1;let B=0,Q=0,e0=0,j=0,H=!1,y=0;for(let Z=0;Z"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],w=this.uiData.dragStart[1]-this.uiData.dragEnd[1],D=this.uiData.dragClipPlaneStartDepthAziElev.slice();if(D[1]-=v,D[1]=D[1]%360,D[2]+=w,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 w=isFinite(this.drawPenLocation[0])&&this.opts.drawingEnabled,{volScale:D}=this.sliceScale();typeof this.opts.multiplanarPadPixels!="number"&&log.debug("multiplanarPadPixels must be numeric");const _=parseFloat(`${this.opts.multiplanarPadPixels}`),P=this.scaleSlice(D[0]+D[1],D[1]+D[2],_*1,_*1),L=Math.max(Math.max(D[1],D[2]),D[0]),m=this.scaleSlice(D[0]+D[0]+D[1],Math.max(D[1],D[2]),_*2),k=this.scaleSlice(D[0]+D[0]+D[1]+L,Math.max(D[1],D[2]),_*3),e=this.scaleSlice(L,D[1]+D[2]+D[2],0,_*2),N=this.scaleSlice(L,D[1]+D[2]+D[2]+L,0,_*3);let B=!w&&(h<2||!g),Q=!1,e0=!1,j=!1;if(this.opts.multiplanarLayout===1?Q=!0:this.opts.multiplanarLayout===2?e0=!0:this.opts.multiplanarLayout===3?j=!0:e[4]>m[4]&&e[4]>P[4]?Q=!0:m[4]>P[4]?j=!0:e0=!0,Q){let H=e;v||this.opts.multiplanarShowRender===2&&N[4]>=e[4]?H=N:B=!1;const y=D[0]*H[4],Y=D[1]*H[4],G=D[2]*H[4],Z=L*H[4];this.draw2D([H[0],H[1],y,Y],0),this.draw2D([H[0],H[1]+Y+_,y,G],1),this.draw2D([H[0],H[1]+Y+_+G+_,Y,G],2),B&&this.draw3D([H[0],H[1]+Y+G+G+_*3,Z,Z])}else if(j){let H=m;v||this.opts.multiplanarShowRender===2&&k[4]>=m[4]?H=k:B=!1;const y=D[0]*H[4],Y=D[1]*H[4],G=D[2]*H[4];this.draw2D([H[0],H[1],y,Y],0),this.draw2D([H[0]+y+_,H[1],y,G],1),this.draw2D([H[0]+y+y+_*2,H[1],Y,G],2),B&&this.draw3D([H[0]+y+y+Y+_*3,H[1],H[3],H[3]])}else if(e0){v||(B=!1),this.opts.multiplanarShowRender===2&&(B=!0);const H=P,y=D[0]*H[4],Y=D[1]*H[4],G=D[2]*H[4];this.draw2D([H[0],H[1]+G+_,y,Y],0),this.draw2D([H[0],H[1],y,G],1),this.draw2D([H[0]+y+_,H[1],Y,G],2),B&&this.draw3D([H[0]+y+_,H[1]+G+_,Y,Y])}}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]),w=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,w])}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(),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}},niimathOperators_default={bptfm:{args:["hp","lp"],help:"Same as bptf but does not remove mean (emulates fslmaths < 5.0.7)"},bwlabel:{args:["conn"],help:"Connected component labelling for non-zero voxels (conn sets neighbors: 6, 18, 26)"},c2h:{args:[],help:"reverse h2c transform"},ceil:{args:[],help:"round voxels upwards to the nearest integer"},conform:{args:[],help:"reslice to 1mm size in coronal slice direction with 256^3 voxels"},crop:{args:["tmin","tsize"],help:"remove volumes, starts with 0 not 1! Inputting -1 for a size will set it to the full range"},dehaze:{args:["mode"],help:"set dark voxels to zero (mode 1..5; higher yields more surviving voxels)"},detrend:{args:[],help:"remove linear trend (and mean) from input"},demean:{args:[],help:"remove average signal across volumes (requires 4D input)"},edt:{args:[],help:"estimate Euler Distance Transform (distance field). Assumes isotropic input"},close:{args:["thr","dx1","dx2"],help:"morphological close that binarizes with `thr`, dilates with `dx1` and erodes with `dx2` (fills bubbles with `thr`)"},floor:{args:[],help:"round voxels downwards to the nearest integer"},h2c:{args:[],help:"convert CT scans from 'Hounsfield' to 'Cormack' units to emphasize soft tissue contrast"},mesh:{args:[],help:"meshify requires 'd'ark, 'm'edium, 'b'right or numeric isosurface ('niimath bet -mesh -i d mesh.gii')",subOperations:{i:{args:["isovalue"],help:"'d'ark, 'm'edium, 'b'right or numeric isosurface"},a:{args:["atlasFile"],help:"roi based atlas to mesh"},b:{args:["fillBubbles"],help:"fill bubbles"},l:{args:["onlyLargest"],help:"only largest"},o:{args:["originalMC"],help:"original marching cubes"},q:{args:["quality"],help:"quality"},s:{args:["postSmooth"],help:"post smooth"},r:{args:["reduceFraction"],help:"reduce fraction"},v:{args:["verbose"],help:"verbose"}}},hollow:{args:["threshold","thickness"],help:"hollow out a mesh"},mod:{args:[],help:"modulus fractional remainder - same as '-rem' but includes fractions"},otsu:{args:["mode"],help:"binarize image using Otsu's method (mode 1..5; higher yields more bright voxels)"},power:{args:["exponent"],help:"raise the current image by following exponent"},qform:{args:["code"],help:"set qform_code"},sform:{args:["code"],help:"set sform_code"},p:{args:["threads"],help:"set maximum number of parallel threads. DISABLED: recompile for OpenMP support"},resize:{args:["X","Y","Z","m"],help:"grow (>1) or shrink (<1) image. Method (0=nearest,1=linear,2=spline,3=Lanczos,4=Mitchell)"},round:{args:[],help:"round voxels to the nearest integer"},sobel:{args:[],help:"fast edge detection"},sobel_binary:{args:[],help:"sobel creating binary edge"},tensor_2lower:{args:[],help:"convert FSL style upper triangle image to NIfTI standard lower triangle order"},tensor_2upper:{args:[],help:"convert NIfTI standard lower triangle image to FSL style upper triangle order"},tensor_decomp_lower:{args:[],help:"as tensor_decomp except input stores lower diagonal (AFNI, ANTS, Camino convention)"},trunc:{args:[],help:"truncates the decimal value from floating point value and returns integer value"},unsharp:{args:["sigma","scl"],help:"edge enhancing unsharp mask (sigma in mm, not voxels; 1.0 is typical)"},dog:{args:["sPos","sNeg"],help:"difference of gaussian with zero-crossing edges (positive and negative sigma mm)"},dogr:{args:["sPos","sNeg"],help:"as dog, without zero-crossing (raw rather than binarized data)"},dogx:{args:["sPos","sNeg"],help:"as dog, zero-crossing for 2D sagittal slices"},dogy:{args:["sPos","sNeg"],help:"as dog, zero-crossing for 2D coronal slices"},dogz:{args:["sPos","sNeg"],help:"as dog, zero-crossing for 2D axial slices"},add:{args:["input"],help:"add following input to current image"},sub:{args:["input"],help:"subtract following input from current image"},mul:{args:["input"],help:"multiply current image by following input"},div:{args:["input"],help:"divide current image by following input"},rem:{args:["number"],help:"modulus remainder - divide current image by following input and take remainder"},mas:{args:["file"],help:"use (following image>0) to mask current image"},thr:{args:["number"],help:"use following number to threshold current image (zero anything below the number)"},thrp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to threshold current image (zero anything below the number)"},thrP:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE of non-zero voxels and threshold below"},uthr:{args:["number"],help:"use following number to upper-threshold current image (zero anything above the number)"},uthrp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to upper-threshold current image (zero anything above the number)"},uthrP:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE of non-zero voxels and threshold above"},clamp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to threshold current image (anything below set to this threshold)"},uclamp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to threshold current image (anything above set to this threshold)"},max:{args:["input"],help:"take maximum of following input and current image"},min:{args:["input"],help:"take minimum of following input and current image"},seed:{args:["number"],help:"seed random number generator with following number"},restart:{args:["file"],help:"replace the current image with input for future processing operations"},save:{args:[],help:"save the current working image to the input filename"},inm:{args:["mean"],help:"(-i i ip.c) intensity normalisation (per 3D volume mean)"},ing:{args:["mean"],help:"(-I i ip.c) intensity normalisation, global 4D mean)"},s:{args:["sigma"],help:"create a gauss kernel of sigma mm and perform mean filtering"},exp:{args:[],help:"exponential"},log:{args:[],help:"natural logarithm"},sin:{args:[],help:"sine function"},cos:{args:[],help:"cosine function"},tan:{args:[],help:"tangent function"},asin:{args:[],help:"arc sine function"},acos:{args:[],help:"arc cosine function"},atan:{args:[],help:"arc tangent function"},sqr:{args:[],help:"square"},sqrt:{args:[],help:"square root"},recip:{args:[],help:"reciprocal (1/current image)"},abs:{args:[],help:"absolute value"},bin:{args:[],help:"use (current image>0) to binarise"},binv:{args:[],help:"binarise and invert (binarisation and logical inversion)"},fillh:{args:[],help:"fill holes in a binary mask (holes are internal - i.e. do not touch the edge of the FOV)"},fillh26:{args:[],help:"fill holes using 26 connectivity"},index:{args:[],help:"replace each nonzero voxel with a unique (subject to wrapping) index number"},grid:{args:["value","spacing"],help:"add a 3D grid of intensity with grid spacing "},edge:{args:[],help:"edge strength"},tfce:{args:["H","E","connectivity"],help:"enhance with TFCE, e.g. -tfce 2 0.5 6 (maybe change 6 to 26 for skeletons)"},tfceS:{args:["H","E","connectivity","X","Y","Z","tfce_thresh"],help:"show support area for voxel (X,Y,Z)"},nan:{args:[],help:"replace NaNs (improper numbers) with 0"},nanm:{args:[],help:"make NaN (improper number) mask with 1 for NaN voxels, 0 otherwise"},rand:{args:[],help:"add uniform noise (range 0:1)"},randn:{args:[],help:"add Gaussian noise (mean=0 sigma=1)"},range:{args:[],help:"set the output calmin/max to full data range"},tensor_decomp:{args:[],help:"convert a 4D (6-timepoint )tensor image into L1,2,3,FA,MD,MO,V1,2,3 (remaining image in pipeline is FA)"},kernel:{subOperations:{"3D":{args:[],help:"3x3x3 box centered on target voxel (set as default kernel)"},"2D":{args:[],help:"3x3x1 box centered on target voxel"},box:{args:["size"],help:"all voxels in a cube of width mm centered on target voxel"},boxv:{args:["size"],help:"all voxels in a cube of width voxels centered on target voxel, CAUTION: size should be an odd number"},boxv3:{args:["X","Y","Z"],help:"all voxels in a cuboid of dimensions X x Y x Z centered on target voxel, CAUTION: size should be an odd number"},gauss:{args:["sigma"],help:"gaussian kernel (sigma in mm, not voxels)"},sphere:{args:["size"],help:"all voxels in a sphere of radius mm centered on target voxel"},file:{args:["filename"],help:"use external file as kernel"}}},dilM:{args:[],help:"Mean Dilation of non-zero voxels"},dilD:{args:[],help:"Maximum Dilation of non-zero voxels (emulating output of fslmaths 6.0.1, max not modal)"},dilF:{args:[],help:"Maximum filtering of all voxels"},dilall:{args:[],help:"Apply -dilM repeatedly until the entire FOV is covered"},ero:{args:[],help:"Erode by zeroing non-zero voxels when zero voxels found in kernel"},eroF:{args:[],help:"Minimum filtering of all voxels"},fmedian:{args:[],help:"Median Filtering"},fmean:{args:[],help:"Mean filtering, kernel weighted (conventionally used with gauss kernel)"},fmeanu:{args:[],help:"Mean filtering, kernel weighted, un-normalized (gives edge effects)"},subsamp2:{args:[],help:"downsamples image by a factor of 2 (keeping new voxels centered on old)"},subsamp2offc:{args:[],help:"downsamples image by a factor of 2 (non-centered)"},Tmean:{args:[],help:"mean across time"},Tstd:{args:[],help:"standard deviation across time"},Tmax:{args:[],help:"max across time"},Tmaxn:{args:[],help:"time index of max across time"},Tmin:{args:[],help:"min across time"},Tmedian:{args:[],help:"median across time"},Tperc:{args:["percentage"],help:"nth percentile (0-100) of FULL RANGE across time"},Tar1:{args:[],help:"temporal AR(1) coefficient (use -odt float and probably demean first)"},pval:{args:[],help:"Nonparametric uncorrected P-value, assuming timepoints are the permutations; first timepoint is actual (unpermuted) stats image"},pval0:{args:[],help:"Same as -pval, but treat zeros as missing data"},cpval:{args:[],help:"Same as -pval, but gives FWE corrected P-values"},ztop:{args:[],help:"Convert Z-stat to (uncorrected) P"},ptoz:{args:[],help:"Convert (uncorrected) P to Z"},ztopc:{args:[],help:"Convert Z-stat to (uncorrected but clamped) P"},ptozc:{args:[],help:"Convert (uncorrected but clamped) P to Z"},rank:{args:[],help:"Convert data to ranks (over T dim)"},ranknorm:{args:[],help:"Transform to Normal dist via ranks"},roi:{args:["xmin","xsize","ymin","ysize","zmin","zsize","tmin","tsize"],help:"zero outside roi (using voxel coordinates). Inputting -1 for a size will set it to the full image extent for that dimension"},bptf:{args:["hp_sigma","lp_sigma"],help:"(-t in ip.c) Bandpass temporal filtering; nonlinear highpass and Gaussian linear lowpass (with sigmas in volumes, not seconds); set either sigma<0 to skip that filter"},roc:{args:["AROC-thresh","outfile","truth"],help:"take (normally binary) truth and test current image in ROC analysis against truth. is usually 0.05 and is limit of Area-under-ROC measure FP axis. is a text file of the ROC curve (triplets of values: FP TP threshold). If the truth image contains negative voxels these get excluded from all calculations. If is positive then the [4Dnoiseonly] option needs to be set, and the FP rate is determined from this noise-only data, and is set to be the fraction of timepoints where any FP (anywhere) is seen, as found in the noise-only 4d-dataset. This is then controlling the FWE rate. If is negative the FP rate is calculated from the zero-value parts of the image, this time averaging voxelwise FP rate over all timepoints. In both cases the TP rate is the average fraction of truth=positive voxels correctly found"}},Niimath=class{constructor(){this.worker=null,this.operators=niimathOperators_default}init(){return this.worker=new Worker(new URL(""+new URL("worker-Jat8hhVx.js",import.meta.url).href,import.meta.url),{type:"module"}),new Promise((T,u)=>{this.worker.onmessage=h=>{h.data&&h.data.type==="ready"&&T(!0)},this.worker.onerror=h=>{u(new Error(`Worker failed to load: ${h.message}`))}})}image(T){return new ImageProcessor({worker:this.worker,file:T,operators:this.operators})}},ImageProcessor=class{constructor({worker:T,file:u,operators:h}){this.worker=T,this.file=u,this.operators=h,this.commands=[],this._generateMethods()}_addCommand(T,...u){return this.commands.push(T,...u.map(String)),this}_generateMethods(){Object.keys(this.operators).forEach(T=>{const u=this.operators[T];T==="kernel"?Object.keys(u.subOperations).forEach(h=>{const g=u.subOperations[h];this[`kernel${h.charAt(0).toUpperCase()+h.slice(1)}`]=(...S)=>{if(S.length!==g.args.length)throw new Error(`Expected ${g.args.length} arguments for kernel ${h}, but got ${S.length}`);return this._addCommand("-kernel",h,...S)}}):T==="mesh"?this.mesh=(h={})=>{const g=[];return Object.keys(h).forEach(S=>{if(u.subOperations[S]){const v=u.subOperations[S],w=h[S];if(v.args.length>0&&w===void 0)throw new Error(`Sub-option -${S} requires a value.`);g.push(`-${S}`),v.args.length>0&&g.push(w)}else throw new Error(`Invalid sub-option -${S} for mesh.`)}),this._addCommand("-mesh",...g)}:this[T]=(...h)=>{if(h.lengthu.args.length)throw new Error(`Expected ${u.args.length} arguments for ${T}, but got ${h.length}`);return this._addCommand(`-${T}`,...h)}})}async run(T="output.nii"){return new Promise((u,h)=>{this.worker.onmessage=S=>{if(S.data.type==="error")h(new Error(S.data.message));else{const{blob:v,exitCode:w}=S.data;w===0?u(v):h(new Error(`niimath processing failed with exit code ${w}`))}};const g=[this.file.name,...this.commands,T];this.worker===null&&h(new Error("Worker not initialized. Did you await the init() method?")),this.worker.postMessage({blob:this.file,cmd:g,outName:T})})}};const brainChopOpts={batchSize:1,numOfChan:1,isColorEnable:!0,isAutoColors:!0,bgLabelValue:0,drawBoundingVolume:!1,isGPU:!0,isBrainCropMaskBased:!0,showPhase1Output:!1,isPostProcessEnable:!0,isContoursViewEnable:!1,browserArrayBufferMaxZDim:30,telemetryFlag:!1,chartXaxisStepPercent:10,uiSampleName:"BC_UI_Sample",atlasSelectedColorTable:"Fire"},inferenceModelsList=[{id:1,type:"Segmentation",path:"/models/model5_gw_ae/model.json",modelName:"⚡ Tissue GWM (light)",labelsPath:"./models/model5_gw_ae/labels.json",colorsPath:"./models/model5_gw_ae/colorLUT.json",colormapPath:"./models/model5_gw_ae/colormap3.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:18,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:null,inferenceDelay:100,description:"Gray and white matter segmentation model. Operates on full T1 image in a single pass, but uses only 5 filters per layer. Can work on integrated graphics cards but is barely large enough to provide good accuracy. Still more accurate than the subvolume model."},{id:2,type:"Segmentation",path:"/models/model20chan3cls/model.json",modelName:"🔪 Tissue GWM (High Acc)",labelsPath:"./models/model20chan3cls/labels.json",colorsPath:"./models/model20chan3cls/colorLUT.json",colormapPath:"./models/model20chan3cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Gray and white matter segmentation model. Operates on full T1 image in a single pass but needs a dedicated graphics card to operate. Provides the best accuracy with hard cropping for better speed"},{id:3,type:"Segmentation",path:"/models/model20chan3cls/model.json",modelName:"🔪 Tissue GWM (High Acc, Low Mem)",labelsPath:"./models/model20chan3cls/labels.json",colorsPath:"./models/model20chan3cls/colorLUT.json",colormapPath:"./models/model20chan3cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Gray and white matter segmentation model. Operates on full T1 image in a single pass but needs a dedicated graphics card to operate. Provides high accuracy and fit low memory available but slower"},{id:4,type:"Atlas",path:"/models/model30chan18cls/model.json",modelName:"🪓 Subcortical + GWM (High Mem, Fast)",labelsPath:"./models/model30chan18cls/labels.json",colorsPath:"./models/model30chan18cls/colorLUT.json",colormapPath:"./models/model30chan18cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is a robust model able to handle range of data quality, including varying saturation, and even clinical scans. It may work on infant brains, but your mileage may vary."},{id:5,type:"Atlas",path:"/models/model30chan18cls/model.json",modelName:"🪓 Subcortical + GWM (Low Mem, Slow)",labelsPath:"./models/model30chan18cls/labels.json",colorsPath:"./models/model30chan18cls/colorLUT.json",colormapPath:"./models/model30chan18cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is a robust model able to handle range of data quality, including varying saturation, and even clinical scans. It may work on infant brains, but your mileage may vary."},{id:6,type:"Atlas",path:"/models/model18cls/model.json",modelName:"🪓 Subcortical + GWM (Low Mem, Faster)",labelsPath:"./models/model18cls/labels.json",colorsPath:"./models/model18cls/colorLUT.json",colormapPath:"./models/model18cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is a robust model able to handle range of data quality, including varying saturation, and even clinical scans. It may work on infant brains, but your mileage may vary."},{id:7,type:"Atlas",path:"/models/model30chan18cls/model.json",modelName:"🔪🪓 Subcortical + GWM (Failsafe, Less Acc)",labelsPath:"./models/model30chan18cls/labels.json",colorsPath:"./models/model30chan18cls/colorLUT.json",colormapPath:"./models/model30chan18cls/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is not a robust model, it may work on low data quality, including varying saturation, and even clinical scans. It may work also on infant brains, but your mileage may vary."},{id:8,type:"Atlas",path:"/models/model30chan50cls/model.json",modelName:"🔪 Aparc+Aseg 50 (High Mem, Fast)",labelsPath:"./models/model30chan50cls/labels.json",colorsPath:"./models/model30chan50cls/colorLUT.json",colormapPath:"./models/model30chan50cls/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"This is a 50-class model, that segments the brain into the Aparc+Aseg Freesurfer Atlas but one where cortical homologues are merged into a single class."},{id:9,type:"Atlas",path:"/models/model30chan50cls/model.json",modelName:"🔪 Aparc+Aseg 50 (Low Mem, Slow)",labelsPath:"./models/model30chan50cls/labels.json",colorsPath:"./models/model30chan50cls/colorLUT.json",colormapPath:"./models/model30chan50cls/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"This is a 50-class model, that segments the brain into the Aparc+Aseg Freesurfer Atlas but one where cortical homologues are merged into a single class. The model use sequential convolution for inference to overcome browser memory limitations but leads to longer computation time."},{id:10,type:"Brain_Extraction",path:"/models/model5_gw_ae/model.json",modelName:"⚡ Extract the Brain (FAST)",labelsPath:null,colorsPath:null,preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:18,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:null,inferenceDelay:100,description:"Extract the brain fast model operates on full T1 image in a single pass, but uses only 5 filters per layer. Can work on integrated graphics cards but is barely large enough to provide good accuracy. Still more accurate than the failsafe version."},{id:11,type:"Brain_Extraction",path:"/models/model11_gw_ae/model.json",modelName:"🔪 Extract the Brain (High Acc, Slow)",labelsPath:null,colorsPath:null,preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Extract the brain high accuracy model operates on full T1 image in a single pass, but uses only 11 filters per layer. Can work on dedicated graphics cards. Still more accurate than the fast version."},{id:12,type:"Brain_Masking",path:"/models/model5_gw_ae/model.json",modelName:"⚡ Brain Mask (FAST)",labelsPath:null,colorsPath:null,colormapPath:"./models/model5_gw_ae/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:17,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:null,inferenceDelay:100,description:"This fast masking model operates on full T1 image in a single pass, but uses only 5 filters per layer. Can work on integrated graphics cards but is barely large enough to provide good accuracy. Still more accurate than failsafe version."},{id:13,type:"Brain_Masking",path:"/models/model11_gw_ae/model.json",modelName:"🔪 Brain Mask (High Acc, Low Mem)",labelsPath:null,colorsPath:null,preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"This masking model operates on full T1 image in a single pass, but uses 11 filters per layer. Can work on dedicated graphics cards. Still more accurate than fast version."},{id:14,type:"Atlas",path:"/models/model21_104class/model.json",modelName:"🔪 Aparc+Aseg 104 (High Mem, Fast)",labelsPath:"./models/model21_104class/labels.json",colorsPath:"./models/model21_104class/colorLUT.json",colormapPath:"./models/model21_104class/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"FreeSurfer aparc+aseg atlas 104 parcellate brain areas into 104 regions. It contains a combination of the Desikan-Killiany atlas for cortical area and also segmentation of subcortical regions."},{id:15,type:"Atlas",path:"/models/model21_104class/model.json",modelName:"🔪 Aparc+Aseg 104 (Low Mem, Slow)",labelsPath:"./models/model21_104class/labels.json",colorsPath:"./models/model21_104class/colorLUT.json",colormapPath:"./models/model21_104class/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"FreeSurfer aparc+aseg atlas 104 parcellate brain areas into 104 regions. It contains a combination of the Desikan-Killiany atlas for cortical area and also segmentation of subcortical regions. The model use sequential convolution for inference to overcome browser memory limitations but leads to longer computation time. "}];async function detectBrowser(){return navigator.userAgent.indexOf("OPR/")>-1?"Opera":navigator.userAgent.indexOf("Edg/")>-1?"Edge":navigator.userAgent.indexOf("Falkon/")>-1?"Falkon":navigator.userAgent.indexOf("Chrome/")>-1?"Chrome":navigator.userAgent.indexOf("Firefox/")>-1?"Firefox":navigator.userAgent.indexOf("Safari/")>-1?"Safari":navigator.userAgent.indexOf("MSIE/")>-1||navigator.userAgent.indexOf("rv:")>-1?"IExplorer":"Unknown"}async function detectBrowserVersion(){return navigator.userAgent.indexOf("OPR/")>-1?parseInt(navigator.userAgent.split("OPR/")[1]):navigator.userAgent.indexOf("Edg/")>-1?parseInt(navigator.userAgent.split("Edg/")[1]):navigator.userAgent.indexOf("Falkon/")>-1?parseInt(navigator.userAgent.split("Falkon/")[1]):navigator.userAgent.indexOf("Chrome/")>-1?parseInt(navigator.userAgent.split("Chrome/")[1]):navigator.userAgent.indexOf("Firefox/")>-1?parseInt(navigator.userAgent.split("Firefox/")[1]):navigator.userAgent.indexOf("Safari/")>-1?parseInt(navigator.userAgent.split("Safari/")[1]):navigator.userAgent.indexOf("MSIE/")>-1||navigator.userAgent.indexOf("rv:")>-1?parseInt(navigator.userAgent.split("MSIE/")[1]):1/0}async function detectOperatingSys(){return navigator.userAgent.indexOf("Win")>-1?"Windows":navigator.userAgent.indexOf("Mac")>-1?"MacOS":navigator.userAgent.indexOf("Linux")>-1?"Linux":navigator.userAgent.indexOf("UNIX")>-1?"UNIX":"Unknown"}async function checkWebGl2(T){return T?(console.log("WebGl2 is enabled"),!0):(typeof WebGL2RenderingContext<"u"||console.log("WebGL2 is not supported"),!1)}async function detectGPUVendor(T){let u;if(T&&(u=T.getExtension("WEBGL_debug_renderer_info"),u)){const h=T.getParameter(u.UNMASKED_VENDOR_WEBGL);return h.indexOf("(")>-1&&h.indexOf(")")>-1?h.substring(h.indexOf("(")+1,h.indexOf(")")):h}return null}async function detectGPUVendor_v0(T){if(T){const u=T.getExtension("WEBGL_debug_renderer_info");return u?T.getParameter(u.UNMASKED_VENDOR_WEBGL):null}else return null}async function detectGPUCardType_v0(T){if(T){if(detectBrowser()==="Firefox")return T.getParameter(T.RENDERER);const u=T.getExtension("WEBGL_debug_renderer_info");return u?T.getParameter(u.UNMASKED_RENDERER_WEBGL):null}else return null}async function detectGPUCardType(T){let u;if(T){if(detectBrowser()==="Firefox")return T.getParameter(T.RENDERER);if(u=T.getExtension("WEBGL_debug_renderer_info"),u){let h=T.getParameter(u.UNMASKED_RENDERER_WEBGL);return h.indexOf("(")>-1&&h.indexOf(")")>-1&&h.indexOf("(R)")===-1&&(h=h.substring(h.indexOf("(")+1,h.indexOf(")")),h.split(",").length===3)?h.split(",")[1].trim():h}}return null}async function getCPUNumCores(){return navigator.hardwareConcurrency}async function isChrome(){return/Chrome/.test(navigator.userAgent)&&/Google Inc/.test(navigator.vendor)}async function localSystemDetails(T,u=null){const h=new Date;if(T.isModelFullVol?T.Brainchop_Ver="FullVolume":T.Brainchop_Ver="SubVolumes",T.Total_t=(Date.now()-T.startTime)/1e3,delete T.startTime,T.Date=parseInt(h.getMonth()+1)+"/"+h.getDate()+"/"+h.getFullYear(),T.Browser=await detectBrowser(),T.Browser_Ver=await detectBrowserVersion(),T.OS=await detectOperatingSys(),T.WebGL2=await checkWebGl2(u),T.GPU_Vendor=await detectGPUVendor(u),T.GPU_Card=await detectGPUCardType(u),T.GPU_Vendor_Full=await detectGPUVendor_v0(u),T.GPU_Card_Full=await detectGPUCardType_v0(u),T.CPU_Cores=await getCPUNumCores(),T.Which_Brainchop="latest",await isChrome()&&(T.Heap_Size_MB=window.performance.memory.totalJSHeapSize/(1024*1024).toFixed(2),T.Used_Heap_MB=window.performance.memory.usedJSHeapSize/(1024*1024).toFixed(2),T.Heap_Limit_MB=window.performance.memory.jsHeapSizeLimit/(1024*1024).toFixed(2)),u){console.log("MAX_TEXTURE_SIZE :",u.getParameter(u.MAX_TEXTURE_SIZE)),console.log("MAX_RENDERBUFFER_SIZE :",u.getParameter(u.MAX_RENDERBUFFER_SIZE));const g=u.getExtension("WEBGL_debug_renderer_info");console.log("VENDOR WEBGL:",u.getParameter(g.UNMASKED_VENDOR_WEBGL)),T.Texture_Size=u.getParameter(u.MAX_TEXTURE_SIZE)}else T.Texture_Size=null;return T}function WorkerWrapper(T){return new Worker(""+new URL("brainchop-webworker-C3YgFmeN.js",import.meta.url).href,{name:T==null?void 0:T.name})}async function main(){const T=new Niimath;await T.init(),aboutBtn.onclick=function(){window.open("https://github.com/neurolabusc/niivue-brainchop","_blank")},opacitySlider0.oninput=function(){k.setOpacity(0,opacitySlider0.value/255),k.updateGLVolume()},opacitySlider1.oninput=function(){k.setOpacity(1,opacitySlider1.value/255)};async function u(){let N=k.volumes[0],B=N.dims[1]===256&&N.dims[2]===256&&N.dims[3]===256;if((N.permRAS[0]!==-1||N.permRAS[1]!==3||N.permRAS[2]!==-2)&&(B=!1),B)return;let Q=await k.conform(N,!1);await k.removeVolume(k.volumes[0]),await k.addVolume(Q)}async function h(){for(;k.volumes.length>1;)await k.removeVolume(k.volumes[1])}modelSelect.onchange=async function(){this.selectedIndex<0&&(modelSelect.selectedIndex=11),await h(),await u();let N=inferenceModelsList[this.selectedIndex];N.isNvidia=!1;const B=k.gl.getExtension("WEBGL_debug_renderer_info");B&&(N.isNvidia=k.gl.getParameter(B.UNMASKED_RENDERER_WEBGL).includes("NVIDIA"));let Q=brainChopOpts;if(Q.rootURL=location.href,!!(window.location.hostname==="localhost"||window.location.hostname==="[::1]"||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/))&&(Q.rootURL=location.protocol+"//"+location.host),workerCheck.checked){if(typeof m<"u"){console.log("Unable to start new segmentation: previous call has not completed");return}m=await new WorkerWrapper({type:"module"});let j={datatypeCode:k.volumes[0].hdr.datatypeCode,dims:k.volumes[0].hdr.dims},H={opts:Q,modelEntry:N,niftiHeader:j,niftiImage:k.volumes[0].img};m.postMessage(H),m.onmessage=function(y){let Y=y.data.cmd;Y==="ui"&&(y.data.modalMessage!==""&&(m.terminate(),m=void 0),D(y.data.message,y.data.progressFrac,y.data.modalMessage,y.data.statData)),Y==="img"&&(m.terminate(),m=void 0,v(y.data.img,y.data.opts,y.data.modelEntry))}}else console.log("Only provided with webworker code, see main brainchop github repository for main thread code")},saveBtn.onclick=function(){k.volumes[1].saveToDisk("Custom.nii")},workerCheck.onchange=function(){modelSelect.onchange()},clipCheck.onchange=function(){clipCheck.checked?k.setClipPlane([0,0,90]):k.setClipPlane([2,0,90])};function g(){opacitySlider0.oninput()}async function S(N){return await(await fetch(N)).json()}async function v(N,B,Q){h();let e0=await k.volumes[0].clone();if(e0.zeroImage(),e0.hdr.scl_inter=0,e0.hdr.scl_slope=1,e0.img=new Uint8Array(N),Q.colormapPath){let j=await S(Q.colormapPath);e0.setColormapLabel(j),e0.hdr.intent_code=1002}else{let j=B.atlasSelectedColorTable.toLowerCase();k.colormaps().includes(j)||(j="actc"),e0.colormap=j}e0.opacity=opacitySlider1.value/255,await k.addVolume(e0)}async function w(N){(typeof N=="string"||N instanceof String)&&(N=function(e0){const j=JSON.parse(e0),H=[];for(const y in j)H[y]=j[y];return H}(N)),N=await localSystemDetails(N,k.gl),L=`:: Diagnostics can help resolve issues https://github.com/neuroneural/brainchop/issues :: +Ooh no!!`);break}v[_]=P,D=Math.min(D,P)}for(let _=0;_u.cal_min){w=u.cal_min,D=u.cal_max;const H=(g-h)/(D-w);return log.info(" Robust Rescale: min: "+w+" max: "+D+" scale: "+H),console.log("Robust Rescale: min: "+w+" max: "+D+" scale: "+H),[w,H]}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 H=u.img,y=new Float32Array(u.img.length);for(let Y=0;Y=1e-15&&L++;const m=1e3,k=(D-w)/m,e=new Array(m).fill(0);for(let H=0;H=B);)Q++;const e0=w;for(w=Q*k+e0,B=P-Math.floor((1-v)*L),Q=0;Q=B);)Q++;D=Q*k+e0;let j=1;return w!==D&&(j=(g-h)/(D-w)),log.info(" Rescale: min: "+w+" max: "+D+" scale: "+j),[w,j]}conformVox2Vox(u,h,g=256,S=1,v=!1){const w=h.flat(),D=fromValues$3(w[0],w[1],w[2],w[3],w[4],w[5],w[6],w[7],w[8],w[9],w[10],w[11],w[12],w[13],w[14],w[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 N=fromValues$1(g,g,g,1),B=create$3();scale$3(B,e,k);const Q=fromValues$1(N[0],N[1],N[2],1);transformMat4(Q,Q,B),scale$1(Q,Q,.5);const e0=create$2();subtract$1(e0,m,fromValues$2(Q[0],Q[1],Q[2]));const j=create$3();transpose(j,B),j[3]=e0[0],j[7]=e0[1],j[11]=e0[2];const H=create$3();invert(H,j);const y=create$3();mul(y,D,H);const Y=create$3();return invert(Y,y),[j,y,Y]}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),N=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=B||ke>=Q||I0>=e0)continue;const Be=T0-ee,Ge=x0-re,ti=M0-Fe,ai=1-Be,ri=1-Ge,qe=1-ti,be=y(ee,re,Fe);let J0=0;J0+=e[be]*ai*ri*qe,J0+=e[be+j]*ai*ri*ti,J0+=e[be+B]*ai*Ge*qe,J0+=e[be+B+j]*ai*Ge*ti,J0+=e[be+1]*Be*ri*qe,J0+=e[be+1+j]*Be*ri*ti,J0+=e[be+1+B]*Be*Ge*qe,J0+=e[be+1+B+j]*Be*Ge*ti,k[H]=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 T0=Math.round(g0*Y+c0),x0=Math.round(g0*G+d0),M0=Math.round(g0*Z+v0);H++,!(T0<0||x0<0||M0<0)&&(T0>=B||x0>=Q||M0>=e0||(k[H]=e[y(T0,x0,M0)]))}}let i0=0;v&&(i0=NaN);let z=new Uint8Array;if(S){const f0=await this.getScale(u,0,1,i0),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,i0),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){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,w=!0,D=!1){u.length<1&&(w=!1),this.colormapLists.push({name:u,min:h,max:g,alphaThreshold:S,negative:v,visible:w,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 w=0;w0)for(let w=0;wS[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 w=Math.round(v[0]*(this.volumes[0].nFrame4D-1));this.setFrame4D(this.volumes[0].id,w);return}v[0]>.5&&v[1]>1&&this.loadDeferred4DVolumes(this.volumes[0].id).catch(w=>{throw w});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!==w||w>2)continue;const D=this.screenXY2TextureFrac(u,h,v,!1);if(!(D[0]<0)){if(!S){this.scene.crosshairPos[2-w]=g,this.drawScene();return}if(g!==0){let _=1;g<0&&(_=-1);const P=[0,0,0];P[2-w]=_,this.moveCrosshairInVox(P[0],P[1],P[2]),this.drawScene(),this.createOnLocationChange(w);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);if(this.opts.clickToSegment){const P=this.opts.clickToSegmentRadius,L=this.opts.clickToSegmentSteps,m=this.opts.clickToSegmentBright?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;this.drawPenFillPts=[],this.drawPenAxCorSag=w;for(let e=1;e<=L;e++){const N=e/L*2*Math.PI;let B=_[0]+P*Math.cos(N),Q=_[1]+P*Math.sin(N);const e0=_[2];B=Math.round(B),Q=Math.round(Q),this.drawPt(B,Q,e0,this.opts.penValue),this.drawPenFillPts.push([B,Q,_[2]]),e===L&&this.drawFloodFill([B,Q,_[2]],0,m,NaN,NaN,this.opts.floodFillNeighbors)}this.drawScene(),this.createOnLocationChange(w);const k=this.getDescriptives(0,[],!0);this.onClickToSegment({mL:k.volumeML,mm3:k.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=w,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(w);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,w=h[1]+h[3]-2*this.opts.rulerWidth,D=[v,w,v+S,w];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 w=0;w<11;w++){const D=u[0]+w*h,_=[D,g,D,S];w%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&&(w=g),this.screenSlices[w].axCorSag>2)continue;const _=this.screenSlices[w].leftTopWidthHeight;if(u<_[0]||h<_[1]||u>_[0]+_[2]||h>_[1]+_[3]||(S=this.screenXY2TextureFrac(u,h,w,!1),S[0]<0))continue;const P=this.frac2mm(S);return fromValues$1(P[0],P[1],P[2],w)}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 w=this.frac2mm(this.scene.crosshairPos);this.scene.pan2Dxyzmm[0]+=v*w[0],this.scene.pan2Dxyzmm[1]+=v*w[1],this.scene.pan2Dxyzmm[2]+=v*w[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 w=this.canvasPos2frac([u[0],u[1]]),D=this.canvasPos2frac([u[2],u[3]]);if(w[0]>=0&&D[0]>=0){const _=this.frac2mm(w);w=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,w,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(w=>w.type==="connectome").flatMap(w=>w.nodes).map(w=>w.label).filter(w=>w!==void 0);return[...this.document.labels,...S]}getBulletMarginWidth(){let u=0;const h=this.getAllLabels();if(h.length===0)return 0;const g=h.length===1?h[0].style.bulletScale:h.reduce((w,D)=>w.style.bulletScale>D.style.bulletScale?w:D).style.bulletScale,S=h.length===1?h[0]:h.reduce((w,D)=>{const _=this.opts.textHeight*this.gl.canvas.height*w.style.textScale,P=this.opts.textHeight*this.gl.canvas.height*D.style.textScale;return this.textHeight(_,w.text)>this.textHeight(P,D.text)?w: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.getAllLabels();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}),w=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textWidth(w,v.text),_=this.getBulletMarginWidth();return D&&(S=_+D,S+=g*2),S>=this.gl.canvas.width?0:S}getLegendPanelHeight(){const u=this.getAllLabels();let h=0;const S=this.opts.textHeight*this.gl.canvas.height*1;for(const v of u){const w=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textHeight(w,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,w){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&&(N=S,S=0),S===v||D<1)return;const B=Math.abs(v-S);let[Q,e0]=tickSpacing(S,v);e0S.includes(_)).reduce((D,_)=>D.lbwh[3]>_.lbwh[3]?D:_).lbwh[3];return u*w}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],w=-(h*S.lbwh[1]),D=h*S.lbwh[2],_=h*S.lbwh[3],P=u[1]+(w-_)+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 w=v/this.fontMets.size*this.fontMets.distanceRange;w=Math.max(w,1),this.gl.uniform1f(this.fontShader.uniforms.screenPxRange,w);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,w=this.textWidth(v,h);w>this.canvas.width&&(g*=(this.canvas.width-2)/w,v=this.opts.textHeight*this.gl.canvas.height*g,w=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-w-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;g0){this.opts.meshThicknessOn2D!==1/0&&(B=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,this.opts.meshThicknessOn2D,N,P,_,D));const e0=clone$2(B.modelViewProjectionMatrix);multiply(e0,e0,w),this.drawMesh3D(!0,1,e0,B.modelMatrix,B.normalMatrix)}isNaN(g)&&this.drawCrosshairs3D(!1,.15,B.modelViewProjectionMatrix,!0,this.opts.isSliceMM),this.drawSliceOrientationText(u,h),this.readyForSync=!0}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 w=this.furthestFromPivot;const D=this.pivot3D,_=create$3();w=.8*w/this.scene.volScaleMultiplier,v<1?ortho(_,-w,w,-w/v,w/v,w*.01,w*8):ortho(_,-w*v,w*v,-w,w,w*.01,w*8);const P=create$3();P[0]=-1;const L=fromValues$2(0,0,-w*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 w=create$3();invert(w,v);const D=fromValues$1(0,0,-1,1);transformMat4(D,D,w);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,w=this.meshes[0].extentsMax;h=fromValues$2(v[0],v[1],v[2]),g=fromValues$2(w[0],w[1],w[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=w-v;for(let c0=0;c0=w&&(w=v+1),this.drawRect(u.LTWH,u.backColor);const[D,_,P]=tickSpacing(v,w),L=Math.max(0,-1*Math.floor(Math.log(D)/Math.log(10)));v=Math.min(_,v),w=Math.max(P,w);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 N=this.opts.textHeight*this.gl.canvas.height*e;N<16&&(N=0);let B=0,Q=_;if(N>0)for(;Q<=w;){const o0=Q.toFixed(L),c0=this.textWidth(N,o0);B=Math.max(c0,B),Q+=D}const e0=.05,j=Math.abs(u.LTWH[2]),H=Math.abs(u.LTWH[3]),y=[u.LTWH[0]+e0*j+B,u.LTWH[1]+e0*H,u.LTWH[2]-B-2*e0*j,u.LTWH[3]-N-2*e0*H];this.graph.LTWH=u.LTWH,this.graph.plotLTWH=y,this.drawRect(y,this.opts.backColor);const Y=w-v,G=y[3]/Y,Z=y[2]/(u.lines[0].length-1),i0=y[1]+y[3];Q=_+.5*D;const z=u.lineColor.slice();for(z[3]=.25*u.lineColor[3];Q<=w;){const o0=i0-(Q-v)*G;this.drawLine([y[0],o0,y[0]+y[2],o0],.5*u.lineThickness,z),Q+=D}Q=_;const f=.5*u.lineThickness;for(;Q<=w;){const o0=i0-(Q-v)*G;this.drawLine([y[0]-f,o0,y[0]+y[2]+u.lineThickness,o0],u.lineThickness,u.lineColor);const c0=Q.toFixed(L);N>0&&this.drawTextLeft([y[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+y[1]+y[3]],v0,e,u.textColor),this.drawLine([c0,y[1],c0,y[1]+y[3]],d0,u.lineColor)}}for(let o0=0;o0=0&&u.selectedColumnk/255);return}const D=unpackFloatFromVec4i(w);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),w=this.volumeObject3D;if(w){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(w.vao),S.drawElements(w.mode,w.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 w=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(w,w,[1.8*S+u[0],_+1.8*S+u[1],0]),scale$3(w,w,[S,S,S]),rotateX(w,w,deg2rad(270-g)),rotateZ(w,w,deg2rad(-h));const P=create$3();multiply(P,D,w),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 w(k){return Math.max(0,-Math.ceil(Math.log10(Math.abs(k))))}let D=w(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 B=0;B=0&&j=0&&(k+="+"),k+=P(e0,D)),k+=" "}L+=k;const e=this.back.dimsRAS,N=e[1]*e[2]*e[3];if(this.drawBitmap&&this.drawBitmap.length===N){const B=this.frac2vox(this.scene.crosshairPos),Q=B[0]+B[1]*e[1]+B[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),N=k.mm2vox(e),B=k.getValue(N[0],N[1],N[2],k.frame4D);return{name:k.name,value:B,id:k.id,mm:e,vox:N}}),string:L};this.onLocationChange(m)}addLabel(u,h,g){const S={textColor:this.opts.legendTextColor,textScale:1,textAlignment:"left",lineWidth:0,lineColor:this.opts.legendTextColor,lineTerminator:"none",bulletScale:0,bulletColor:this.opts.legendTextColor},v=h?{...S,...h}:{...S},w=new NVLabel3D(u,v,g);return this.document.labels.push(w),w}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){log.debug("screenPoint",u);const h=this.getLegendPanelHeight(),g=this.getLegendPanelWidth(),S=this.gl.canvas.width-g;let v=(this.canvas.height-h)/2;if(log.debug("panelrect",S,v,S+g,v+h),u[0]S+g||u[1]>v+h)return null;const D=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,_=this.getAllLabels();for(const P of _){const L=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,m=this.textHeight(L,P.text);if(u[1]>=v&&u[1]<=v+m+D/2)return P;v+=m,v+=D/2}return null}drawLabelLine(u,h,g,S,v=!1){const w=Array.isArray(u.points)&&Array.isArray(u.points[0])?u.points:[u.points];for(const D of w){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=0,w,D=!1){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 N=u.style.bulletScale*k,B=k-N,Q=L+B/2+N/2,e0=P+(v-N)/2;this.drawCircle([e0,Q,N,N],u.style.bulletColor)}let e=P;if(u.style.textAlignment!=="left"){const N=this.textWidth(u.style.textScale,u.text)*m;if(u.style.textAlignment==="right")e=P+w-m*1.5-N;else{const B=w-(v||m);e+=(B-N)/2}}else e+=v;this.drawText([e,L],_,u.style.textScale,u.style.textColor)}draw3DLabels(u,h,g=!1){const S=this.getAllLabels();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),N=v.getParameter(v.DEPTH_FUNC);g||(v.disable(v.BLEND),v.depthFunc(v.GREATER));for(const B of S){this.draw3DLabel(B,[m,k],u,h,_,L,g);const Q=this.opts.textHeight*this.gl.canvas.height*B.style.textScale,e0=this.textHeight(Q,B.text);k+=e0,k+=D/2}g||(v.depthFunc(N),e&&v.enable(v.BLEND))}draw3D(u=[0,0,0,0],h=null,g=null,S=null,v=null,w=0){const D=v!==null;this.setPivot3D(),D||(v=this.scene.renderAzimuth,w=this.scene.renderElevation);const _=this.gl;h===null&&([h,g,S]=this.calculateMvpMatrix(null,u,v,w));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,w)),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,w);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,w);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 w=this.gl;g||([g,S,v]=this.calculateMvpMatrix(this.volumeObject3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),w.enable(w.DEPTH_TEST),w.blendFunc(w.SRC_ALPHA,w.ONE_MINUS_SRC_ALPHA),w.disable(w.BLEND),w.depthFunc(w.GREATER),w.disable(w.CULL_FACE),u?(w.disable(w.BLEND),w.depthFunc(w.GREATER)):(w.enable(w.BLEND),w.depthFunc(w.ALWAYS),w.enable(w.CULL_FACE)),w.cullFace(w.BACK);let D=this.meshShaders[0].shader,_=!1;for(let P=0;P=3&&this.meshes[P].fiberRadius>0||(w.bindVertexArray(this.meshes[P].vaoFiber),w.drawElements(w.LINE_STRIP,this.meshes[P].indexCount,w.UNSIGNED_INT,0),w.bindVertexArray(this.unusedVAO)));w.enable(w.BLEND),w.depthFunc(w.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 w=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&&(w.deleteBuffer(this.crosshairs3D.indexBuffer),w.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)),w.uniformMatrix4fv(_.uniforms.mvpMtx,!1,g),w.bindBuffer(w.ELEMENT_ARRAY_BUFFER,this.crosshairs3D.indexBuffer),w.enable(w.DEPTH_TEST);const P=[...this.opts.crosshairColor];u?(w.disable(w.BLEND),w.depthFunc(w.GREATER)):(w.enable(w.BLEND),w.blendFunc(w.SRC_ALPHA,w.ONE_MINUS_SRC_ALPHA),w.depthFunc(w.ALWAYS)),P[3]=h,w.uniform4fv(_.uniforms.surfaceColor,P),w.bindVertexArray(this.crosshairs3D.vao),w.drawElements(w.TRIANGLES,this.crosshairs3D.indexCount,w.UNSIGNED_INT,0),w.bindVertexArray(this.unusedVAO)}mm2frac(u,h=0,g=!1){if(this.volumes.length<1){const S=fromValues$2(.1,.5,.5),[v,w,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,w]=this.sceneExtentsMinMax(),D=(_,P,L)=>_*(1-L)+P*L;S[0]=D(v[0],w[0],u[0]),S[1]=D(v[1],w[1],u[1]),S[2]=D(v[2],w[2],u[2])}return S}screenXY2TextureFrac(u,h,g,S=!0){const v=fromValues$2(-1,-1,-1),w=this.screenSlices[g].axCorSag;if(w>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]),w===1&&(m=swizzleVec3(m,[0,2,1])),w===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,w=this.effectiveCanvasHeight()-S;let D=v/u;h*D>w&&(D=w/h);const _=u*D,P=h*D;return[(v-_)*.5,(w-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]),w=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(w/P);w%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,N=1;for(let B=0;B0&&h===1){const e=D,N=2;for(let B=0;B0&&h===2){const e=D,N=2;for(let B=0;B0&&h===0){const e=D,N=0;for(let B=0;B0&&h===1){const e=D,N=0;for(let B=0;B0&&h===2){const e=D,N=1;for(let B=0;B0){const P=w.leftTopWidthHeight.slice();let L=2;h===0&&(L=1);const m=this.frac2mm([.5,.5,.5]);for(let k=0;k0){const P=w.leftTopWidthHeight.slice(),L=w.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 N=this.mm2frac(k);L?this.drawRect([P[0]+(P[2]-N[m]*P[2]),P[1],1,P[3]]):this.drawRect([P[0]+N[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=[],w=[],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,N=!1;e=!1;let B=0,Q=0,e0=0,j=0,H=!1,y=0;for(let Z=0;Z"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],w=this.uiData.dragStart[1]-this.uiData.dragEnd[1],D=this.uiData.dragClipPlaneStartDepthAziElev.slice();if(D[1]-=v,D[1]=D[1]%360,D[2]+=w,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 w=isFinite(this.drawPenLocation[0])&&this.opts.drawingEnabled,{volScale:D}=this.sliceScale();typeof this.opts.multiplanarPadPixels!="number"&&log.debug("multiplanarPadPixels must be numeric");const _=parseFloat(`${this.opts.multiplanarPadPixels}`),P=this.scaleSlice(D[0]+D[1],D[1]+D[2],_*1,_*1),L=Math.max(Math.max(D[1],D[2]),D[0]),m=this.scaleSlice(D[0]+D[0]+D[1],Math.max(D[1],D[2]),_*2),k=this.scaleSlice(D[0]+D[0]+D[1]+L,Math.max(D[1],D[2]),_*3),e=this.scaleSlice(L,D[1]+D[2]+D[2],0,_*2),N=this.scaleSlice(L,D[1]+D[2]+D[2]+L,0,_*3);let B=!w&&(h<2||!g),Q=!1,e0=!1,j=!1;if(this.opts.multiplanarLayout===1?Q=!0:this.opts.multiplanarLayout===2?e0=!0:this.opts.multiplanarLayout===3?j=!0:e[4]>m[4]&&e[4]>P[4]?Q=!0:m[4]>P[4]?j=!0:e0=!0,Q){let H=e;v||this.opts.multiplanarShowRender===2&&N[4]>=e[4]?H=N:B=!1;const y=D[0]*H[4],Y=D[1]*H[4],G=D[2]*H[4],Z=L*H[4];this.draw2D([H[0],H[1],y,Y],0),this.draw2D([H[0],H[1]+Y+_,y,G],1),this.draw2D([H[0],H[1]+Y+_+G+_,Y,G],2),B&&this.draw3D([H[0],H[1]+Y+G+G+_*3,Z,Z])}else if(j){let H=m;v||this.opts.multiplanarShowRender===2&&k[4]>=m[4]?H=k:B=!1;const y=D[0]*H[4],Y=D[1]*H[4],G=D[2]*H[4];this.draw2D([H[0],H[1],y,Y],0),this.draw2D([H[0]+y+_,H[1],y,G],1),this.draw2D([H[0]+y+y+_*2,H[1],Y,G],2),B&&this.draw3D([H[0]+y+y+Y+_*3,H[1],H[3],H[3]])}else if(e0){v||(B=!1),this.opts.multiplanarShowRender===2&&(B=!0);const H=P,y=D[0]*H[4],Y=D[1]*H[4],G=D[2]*H[4];this.draw2D([H[0],H[1]+G+_,y,Y],0),this.draw2D([H[0],H[1],y,G],1),this.draw2D([H[0]+y+_,H[1],Y,G],2),B&&this.draw3D([H[0]+y+_,H[1]+G+_,Y,Y])}}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]),w=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,w])}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(),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}},niimathOperators_default={bptfm:{args:["hp","lp"],help:"Same as bptf but does not remove mean (emulates fslmaths < 5.0.7)"},bwlabel:{args:["conn"],help:"Connected component labelling for non-zero voxels (conn sets neighbors: 6, 18, 26)"},c2h:{args:[],help:"reverse h2c transform"},ceil:{args:[],help:"round voxels upwards to the nearest integer"},conform:{args:[],help:"reslice to 1mm size in coronal slice direction with 256^3 voxels"},crop:{args:["tmin","tsize"],help:"remove volumes, starts with 0 not 1! Inputting -1 for a size will set it to the full range"},dehaze:{args:["mode"],help:"set dark voxels to zero (mode 1..5; higher yields more surviving voxels)"},detrend:{args:[],help:"remove linear trend (and mean) from input"},demean:{args:[],help:"remove average signal across volumes (requires 4D input)"},edt:{args:[],help:"estimate Euler Distance Transform (distance field). Assumes isotropic input"},close:{args:["thr","dx1","dx2"],help:"morphological close that binarizes with `thr`, dilates with `dx1` and erodes with `dx2` (fills bubbles with `thr`)"},floor:{args:[],help:"round voxels downwards to the nearest integer"},h2c:{args:[],help:"convert CT scans from 'Hounsfield' to 'Cormack' units to emphasize soft tissue contrast"},mesh:{args:[],help:"meshify requires 'd'ark, 'm'edium, 'b'right or numeric isosurface ('niimath bet -mesh -i d mesh.gii')",subOperations:{i:{args:["isovalue"],help:"'d'ark, 'm'edium, 'b'right or numeric isosurface"},a:{args:["atlasFile"],help:"roi based atlas to mesh"},b:{args:["fillBubbles"],help:"fill bubbles"},l:{args:["onlyLargest"],help:"only largest"},o:{args:["originalMC"],help:"original marching cubes"},q:{args:["quality"],help:"quality"},s:{args:["postSmooth"],help:"post smooth"},r:{args:["reduceFraction"],help:"reduce fraction"},v:{args:["verbose"],help:"verbose"}}},hollow:{args:["threshold","thickness"],help:"hollow out a mesh"},mod:{args:[],help:"modulus fractional remainder - same as '-rem' but includes fractions"},otsu:{args:["mode"],help:"binarize image using Otsu's method (mode 1..5; higher yields more bright voxels)"},power:{args:["exponent"],help:"raise the current image by following exponent"},qform:{args:["code"],help:"set qform_code"},sform:{args:["code"],help:"set sform_code"},p:{args:["threads"],help:"set maximum number of parallel threads. DISABLED: recompile for OpenMP support"},resize:{args:["X","Y","Z","m"],help:"grow (>1) or shrink (<1) image. Method (0=nearest,1=linear,2=spline,3=Lanczos,4=Mitchell)"},round:{args:[],help:"round voxels to the nearest integer"},sobel:{args:[],help:"fast edge detection"},sobel_binary:{args:[],help:"sobel creating binary edge"},tensor_2lower:{args:[],help:"convert FSL style upper triangle image to NIfTI standard lower triangle order"},tensor_2upper:{args:[],help:"convert NIfTI standard lower triangle image to FSL style upper triangle order"},tensor_decomp_lower:{args:[],help:"as tensor_decomp except input stores lower diagonal (AFNI, ANTS, Camino convention)"},trunc:{args:[],help:"truncates the decimal value from floating point value and returns integer value"},unsharp:{args:["sigma","scl"],help:"edge enhancing unsharp mask (sigma in mm, not voxels; 1.0 is typical)"},dog:{args:["sPos","sNeg"],help:"difference of gaussian with zero-crossing edges (positive and negative sigma mm)"},dogr:{args:["sPos","sNeg"],help:"as dog, without zero-crossing (raw rather than binarized data)"},dogx:{args:["sPos","sNeg"],help:"as dog, zero-crossing for 2D sagittal slices"},dogy:{args:["sPos","sNeg"],help:"as dog, zero-crossing for 2D coronal slices"},dogz:{args:["sPos","sNeg"],help:"as dog, zero-crossing for 2D axial slices"},add:{args:["input"],help:"add following input to current image"},sub:{args:["input"],help:"subtract following input from current image"},mul:{args:["input"],help:"multiply current image by following input"},div:{args:["input"],help:"divide current image by following input"},rem:{args:["number"],help:"modulus remainder - divide current image by following input and take remainder"},mas:{args:["file"],help:"use (following image>0) to mask current image"},thr:{args:["number"],help:"use following number to threshold current image (zero anything below the number)"},thrp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to threshold current image (zero anything below the number)"},thrP:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE of non-zero voxels and threshold below"},uthr:{args:["number"],help:"use following number to upper-threshold current image (zero anything above the number)"},uthrp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to upper-threshold current image (zero anything above the number)"},uthrP:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE of non-zero voxels and threshold above"},clamp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to threshold current image (anything below set to this threshold)"},uclamp:{args:["input"],help:"use following percentage (0-100) of ROBUST RANGE to threshold current image (anything above set to this threshold)"},max:{args:["input"],help:"take maximum of following input and current image"},min:{args:["input"],help:"take minimum of following input and current image"},seed:{args:["number"],help:"seed random number generator with following number"},restart:{args:["file"],help:"replace the current image with input for future processing operations"},save:{args:[],help:"save the current working image to the input filename"},inm:{args:["mean"],help:"(-i i ip.c) intensity normalisation (per 3D volume mean)"},ing:{args:["mean"],help:"(-I i ip.c) intensity normalisation, global 4D mean)"},s:{args:["sigma"],help:"create a gauss kernel of sigma mm and perform mean filtering"},exp:{args:[],help:"exponential"},log:{args:[],help:"natural logarithm"},sin:{args:[],help:"sine function"},cos:{args:[],help:"cosine function"},tan:{args:[],help:"tangent function"},asin:{args:[],help:"arc sine function"},acos:{args:[],help:"arc cosine function"},atan:{args:[],help:"arc tangent function"},sqr:{args:[],help:"square"},sqrt:{args:[],help:"square root"},recip:{args:[],help:"reciprocal (1/current image)"},abs:{args:[],help:"absolute value"},bin:{args:[],help:"use (current image>0) to binarise"},binv:{args:[],help:"binarise and invert (binarisation and logical inversion)"},fillh:{args:[],help:"fill holes in a binary mask (holes are internal - i.e. do not touch the edge of the FOV)"},fillh26:{args:[],help:"fill holes using 26 connectivity"},index:{args:[],help:"replace each nonzero voxel with a unique (subject to wrapping) index number"},grid:{args:["value","spacing"],help:"add a 3D grid of intensity with grid spacing "},edge:{args:[],help:"edge strength"},tfce:{args:["H","E","connectivity"],help:"enhance with TFCE, e.g. -tfce 2 0.5 6 (maybe change 6 to 26 for skeletons)"},tfceS:{args:["H","E","connectivity","X","Y","Z","tfce_thresh"],help:"show support area for voxel (X,Y,Z)"},nan:{args:[],help:"replace NaNs (improper numbers) with 0"},nanm:{args:[],help:"make NaN (improper number) mask with 1 for NaN voxels, 0 otherwise"},rand:{args:[],help:"add uniform noise (range 0:1)"},randn:{args:[],help:"add Gaussian noise (mean=0 sigma=1)"},range:{args:[],help:"set the output calmin/max to full data range"},tensor_decomp:{args:[],help:"convert a 4D (6-timepoint )tensor image into L1,2,3,FA,MD,MO,V1,2,3 (remaining image in pipeline is FA)"},kernel:{subOperations:{"3D":{args:[],help:"3x3x3 box centered on target voxel (set as default kernel)"},"2D":{args:[],help:"3x3x1 box centered on target voxel"},box:{args:["size"],help:"all voxels in a cube of width mm centered on target voxel"},boxv:{args:["size"],help:"all voxels in a cube of width voxels centered on target voxel, CAUTION: size should be an odd number"},boxv3:{args:["X","Y","Z"],help:"all voxels in a cuboid of dimensions X x Y x Z centered on target voxel, CAUTION: size should be an odd number"},gauss:{args:["sigma"],help:"gaussian kernel (sigma in mm, not voxels)"},sphere:{args:["size"],help:"all voxels in a sphere of radius mm centered on target voxel"},file:{args:["filename"],help:"use external file as kernel"}}},dilM:{args:[],help:"Mean Dilation of non-zero voxels"},dilD:{args:[],help:"Maximum Dilation of non-zero voxels (emulating output of fslmaths 6.0.1, max not modal)"},dilF:{args:[],help:"Maximum filtering of all voxels"},dilall:{args:[],help:"Apply -dilM repeatedly until the entire FOV is covered"},ero:{args:[],help:"Erode by zeroing non-zero voxels when zero voxels found in kernel"},eroF:{args:[],help:"Minimum filtering of all voxels"},fmedian:{args:[],help:"Median Filtering"},fmean:{args:[],help:"Mean filtering, kernel weighted (conventionally used with gauss kernel)"},fmeanu:{args:[],help:"Mean filtering, kernel weighted, un-normalized (gives edge effects)"},subsamp2:{args:[],help:"downsamples image by a factor of 2 (keeping new voxels centered on old)"},subsamp2offc:{args:[],help:"downsamples image by a factor of 2 (non-centered)"},Tmean:{args:[],help:"mean across time"},Tstd:{args:[],help:"standard deviation across time"},Tmax:{args:[],help:"max across time"},Tmaxn:{args:[],help:"time index of max across time"},Tmin:{args:[],help:"min across time"},Tmedian:{args:[],help:"median across time"},Tperc:{args:["percentage"],help:"nth percentile (0-100) of FULL RANGE across time"},Tar1:{args:[],help:"temporal AR(1) coefficient (use -odt float and probably demean first)"},pval:{args:[],help:"Nonparametric uncorrected P-value, assuming timepoints are the permutations; first timepoint is actual (unpermuted) stats image"},pval0:{args:[],help:"Same as -pval, but treat zeros as missing data"},cpval:{args:[],help:"Same as -pval, but gives FWE corrected P-values"},ztop:{args:[],help:"Convert Z-stat to (uncorrected) P"},ptoz:{args:[],help:"Convert (uncorrected) P to Z"},ztopc:{args:[],help:"Convert Z-stat to (uncorrected but clamped) P"},ptozc:{args:[],help:"Convert (uncorrected but clamped) P to Z"},rank:{args:[],help:"Convert data to ranks (over T dim)"},ranknorm:{args:[],help:"Transform to Normal dist via ranks"},roi:{args:["xmin","xsize","ymin","ysize","zmin","zsize","tmin","tsize"],help:"zero outside roi (using voxel coordinates). Inputting -1 for a size will set it to the full image extent for that dimension"},bptf:{args:["hp_sigma","lp_sigma"],help:"(-t in ip.c) Bandpass temporal filtering; nonlinear highpass and Gaussian linear lowpass (with sigmas in volumes, not seconds); set either sigma<0 to skip that filter"},roc:{args:["AROC-thresh","outfile","truth"],help:"take (normally binary) truth and test current image in ROC analysis against truth. is usually 0.05 and is limit of Area-under-ROC measure FP axis. is a text file of the ROC curve (triplets of values: FP TP threshold). If the truth image contains negative voxels these get excluded from all calculations. If is positive then the [4Dnoiseonly] option needs to be set, and the FP rate is determined from this noise-only data, and is set to be the fraction of timepoints where any FP (anywhere) is seen, as found in the noise-only 4d-dataset. This is then controlling the FWE rate. If is negative the FP rate is calculated from the zero-value parts of the image, this time averaging voxelwise FP rate over all timepoints. In both cases the TP rate is the average fraction of truth=positive voxels correctly found"}},Niimath=class{constructor(){this.worker=null,this.operators=niimathOperators_default}init(){return this.worker=new Worker(new URL(""+new URL("worker-Jat8hhVx.js",import.meta.url).href,import.meta.url),{type:"module"}),new Promise((T,u)=>{this.worker.onmessage=h=>{h.data&&h.data.type==="ready"&&T(!0)},this.worker.onerror=h=>{u(new Error(`Worker failed to load: ${h.message}`))}})}image(T){return new ImageProcessor({worker:this.worker,file:T,operators:this.operators})}},ImageProcessor=class{constructor({worker:T,file:u,operators:h}){this.worker=T,this.file=u,this.operators=h,this.commands=[],this._generateMethods()}_addCommand(T,...u){return this.commands.push(T,...u.map(String)),this}_generateMethods(){Object.keys(this.operators).forEach(T=>{const u=this.operators[T];T==="kernel"?Object.keys(u.subOperations).forEach(h=>{const g=u.subOperations[h];this[`kernel${h.charAt(0).toUpperCase()+h.slice(1)}`]=(...S)=>{if(S.length!==g.args.length)throw new Error(`Expected ${g.args.length} arguments for kernel ${h}, but got ${S.length}`);return this._addCommand("-kernel",h,...S)}}):T==="mesh"?this.mesh=(h={})=>{const g=[];return Object.keys(h).forEach(S=>{if(u.subOperations[S]){const v=u.subOperations[S],w=h[S];if(v.args.length>0&&w===void 0)throw new Error(`Sub-option -${S} requires a value.`);g.push(`-${S}`),v.args.length>0&&g.push(w)}else throw new Error(`Invalid sub-option -${S} for mesh.`)}),this._addCommand("-mesh",...g)}:this[T]=(...h)=>{if(h.lengthu.args.length)throw new Error(`Expected ${u.args.length} arguments for ${T}, but got ${h.length}`);return this._addCommand(`-${T}`,...h)}})}async run(T="output.nii"){return new Promise((u,h)=>{this.worker.onmessage=S=>{if(S.data.type==="error")h(new Error(S.data.message));else{const{blob:v,exitCode:w}=S.data;w===0?u(v):h(new Error(`niimath processing failed with exit code ${w}`))}};const g=[this.file.name,...this.commands,T];this.worker===null&&h(new Error("Worker not initialized. Did you await the init() method?")),this.worker.postMessage({blob:this.file,cmd:g,outName:T})})}};const brainChopOpts={batchSize:1,numOfChan:1,isColorEnable:!0,isAutoColors:!0,bgLabelValue:0,drawBoundingVolume:!1,isGPU:!0,isBrainCropMaskBased:!0,showPhase1Output:!1,isPostProcessEnable:!0,isContoursViewEnable:!1,browserArrayBufferMaxZDim:30,telemetryFlag:!1,chartXaxisStepPercent:10,uiSampleName:"BC_UI_Sample",atlasSelectedColorTable:"Fire"},inferenceModelsList=[{id:1,type:"Segmentation",path:"/models/model5_gw_ae/model.json",modelName:"⚡ Tissue GWM (light)",labelsPath:"./models/model5_gw_ae/labels.json",colorsPath:"./models/model5_gw_ae/colorLUT.json",colormapPath:"./models/model5_gw_ae/colormap3.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:18,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:null,inferenceDelay:100,description:"Gray and white matter segmentation model. Operates on full T1 image in a single pass, but uses only 5 filters per layer. Can work on integrated graphics cards but is barely large enough to provide good accuracy. Still more accurate than the subvolume model."},{id:2,type:"Segmentation",path:"/models/model20chan3cls/model.json",modelName:"🔪 Tissue GWM (High Acc)",labelsPath:"./models/model20chan3cls/labels.json",colorsPath:"./models/model20chan3cls/colorLUT.json",colormapPath:"./models/model20chan3cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Gray and white matter segmentation model. Operates on full T1 image in a single pass but needs a dedicated graphics card to operate. Provides the best accuracy with hard cropping for better speed"},{id:3,type:"Segmentation",path:"/models/model20chan3cls/model.json",modelName:"🔪 Tissue GWM (High Acc, Low Mem)",labelsPath:"./models/model20chan3cls/labels.json",colorsPath:"./models/model20chan3cls/colorLUT.json",colormapPath:"./models/model20chan3cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Gray and white matter segmentation model. Operates on full T1 image in a single pass but needs a dedicated graphics card to operate. Provides high accuracy and fit low memory available but slower"},{id:4,type:"Atlas",path:"/models/model30chan18cls/model.json",modelName:"🪓 Subcortical + GWM (High Mem, Fast)",labelsPath:"./models/model30chan18cls/labels.json",colorsPath:"./models/model30chan18cls/colorLUT.json",colormapPath:"./models/model30chan18cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is a robust model able to handle range of data quality, including varying saturation, and even clinical scans. It may work on infant brains, but your mileage may vary."},{id:5,type:"Atlas",path:"/models/model30chan18cls/model.json",modelName:"🪓 Subcortical + GWM (Low Mem, Slow)",labelsPath:"./models/model30chan18cls/labels.json",colorsPath:"./models/model30chan18cls/colorLUT.json",colormapPath:"./models/model30chan18cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is a robust model able to handle range of data quality, including varying saturation, and even clinical scans. It may work on infant brains, but your mileage may vary."},{id:6,type:"Atlas",path:"/models/model18cls/model.json",modelName:"🪓 Subcortical + GWM (Low Mem, Faster)",labelsPath:"./models/model18cls/labels.json",colorsPath:"./models/model18cls/colorLUT.json",colormapPath:"./models/model18cls/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:.2,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is a robust model able to handle range of data quality, including varying saturation, and even clinical scans. It may work on infant brains, but your mileage may vary."},{id:7,type:"Atlas",path:"/models/model30chan18cls/model.json",modelName:"🔪🪓 Subcortical + GWM (Failsafe, Less Acc)",labelsPath:"./models/model30chan18cls/labels.json",colorsPath:"./models/model30chan18cls/colorLUT.json",colormapPath:"./models/model30chan18cls/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Parcellation of the brain into 17 regions: gray and white matter plus subcortical areas. This is not a robust model, it may work on low data quality, including varying saturation, and even clinical scans. It may work also on infant brains, but your mileage may vary."},{id:8,type:"Atlas",path:"/models/model30chan50cls/model.json",modelName:"🔪 Aparc+Aseg 50 (High Mem, Fast)",labelsPath:"./models/model30chan50cls/labels.json",colorsPath:"./models/model30chan50cls/colorLUT.json",colormapPath:"./models/model30chan50cls/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"This is a 50-class model, that segments the brain into the Aparc+Aseg Freesurfer Atlas but one where cortical homologues are merged into a single class."},{id:9,type:"Atlas",path:"/models/model30chan50cls/model.json",modelName:"🔪 Aparc+Aseg 50 (Low Mem, Slow)",labelsPath:"./models/model30chan50cls/labels.json",colorsPath:"./models/model30chan50cls/colorLUT.json",colormapPath:"./models/model30chan50cls/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"This is a 50-class model, that segments the brain into the Aparc+Aseg Freesurfer Atlas but one where cortical homologues are merged into a single class. The model use sequential convolution for inference to overcome browser memory limitations but leads to longer computation time."},{id:10,type:"Brain_Extraction",path:"/models/model5_gw_ae/model.json",modelName:"⚡ Extract the Brain (FAST)",labelsPath:null,colorsPath:null,preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:18,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:null,inferenceDelay:100,description:"Extract the brain fast model operates on full T1 image in a single pass, but uses only 5 filters per layer. Can work on integrated graphics cards but is barely large enough to provide good accuracy. Still more accurate than the failsafe version."},{id:11,type:"Brain_Extraction",path:"/models/model11_gw_ae/model.json",modelName:"🔪 Extract the Brain (High Acc, Slow)",labelsPath:null,colorsPath:null,preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"Extract the brain high accuracy model operates on full T1 image in a single pass, but uses only 11 filters per layer. Can work on dedicated graphics cards. Still more accurate than the fast version."},{id:12,type:"Brain_Masking",path:"/models/model5_gw_ae/model.json",modelName:"⚡ Brain Mask (FAST)",labelsPath:null,colorsPath:null,colormapPath:"./models/model5_gw_ae/colormap.json",preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:17,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:null,inferenceDelay:100,description:"This fast masking model operates on full T1 image in a single pass, but uses only 5 filters per layer. Can work on integrated graphics cards but is barely large enough to provide good accuracy. Still more accurate than failsafe version."},{id:13,type:"Brain_Masking",path:"/models/model11_gw_ae/model.json",modelName:"🔪 Brain Mask (High Acc, Low Mem)",labelsPath:null,colorsPath:null,preModelId:null,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:0,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!0,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"This masking model operates on full T1 image in a single pass, but uses 11 filters per layer. Can work on dedicated graphics cards. Still more accurate than fast version."},{id:14,type:"Atlas",path:"/models/model21_104class/model.json",modelName:"🔪 Aparc+Aseg 104 (High Mem, Fast)",labelsPath:"./models/model21_104class/labels.json",colorsPath:"./models/model21_104class/colorLUT.json",colormapPath:"./models/model21_104class/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!1,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"FreeSurfer aparc+aseg atlas 104 parcellate brain areas into 104 regions. It contains a combination of the Desikan-Killiany atlas for cortical area and also segmentation of subcortical regions."},{id:15,type:"Atlas",path:"/models/model21_104class/model.json",modelName:"🔪 Aparc+Aseg 104 (Low Mem, Slow)",labelsPath:"./models/model21_104class/labels.json",colorsPath:"./models/model21_104class/colorLUT.json",colormapPath:"./models/model21_104class/colormap.json",preModelId:1,preModelPostProcess:!1,isBatchOverlapEnable:!1,numOverlapBatches:200,enableTranspose:!0,enableCrop:!0,cropPadding:0,autoThreshold:0,enableQuantileNorm:!1,filterOutWithPreMask:!1,enableSeqConv:!0,textureSize:0,warning:"This model may need dedicated graphics card. For more info please check with Browser Resources .",inferenceDelay:100,description:"FreeSurfer aparc+aseg atlas 104 parcellate brain areas into 104 regions. It contains a combination of the Desikan-Killiany atlas for cortical area and also segmentation of subcortical regions. The model use sequential convolution for inference to overcome browser memory limitations but leads to longer computation time. "}];async function detectBrowser(){return navigator.userAgent.indexOf("OPR/")>-1?"Opera":navigator.userAgent.indexOf("Edg/")>-1?"Edge":navigator.userAgent.indexOf("Falkon/")>-1?"Falkon":navigator.userAgent.indexOf("Chrome/")>-1?"Chrome":navigator.userAgent.indexOf("Firefox/")>-1?"Firefox":navigator.userAgent.indexOf("Safari/")>-1?"Safari":navigator.userAgent.indexOf("MSIE/")>-1||navigator.userAgent.indexOf("rv:")>-1?"IExplorer":"Unknown"}async function detectBrowserVersion(){return navigator.userAgent.indexOf("OPR/")>-1?parseInt(navigator.userAgent.split("OPR/")[1]):navigator.userAgent.indexOf("Edg/")>-1?parseInt(navigator.userAgent.split("Edg/")[1]):navigator.userAgent.indexOf("Falkon/")>-1?parseInt(navigator.userAgent.split("Falkon/")[1]):navigator.userAgent.indexOf("Chrome/")>-1?parseInt(navigator.userAgent.split("Chrome/")[1]):navigator.userAgent.indexOf("Firefox/")>-1?parseInt(navigator.userAgent.split("Firefox/")[1]):navigator.userAgent.indexOf("Safari/")>-1?parseInt(navigator.userAgent.split("Safari/")[1]):navigator.userAgent.indexOf("MSIE/")>-1||navigator.userAgent.indexOf("rv:")>-1?parseInt(navigator.userAgent.split("MSIE/")[1]):1/0}async function detectOperatingSys(){return navigator.userAgent.indexOf("Win")>-1?"Windows":navigator.userAgent.indexOf("Mac")>-1?"MacOS":navigator.userAgent.indexOf("Linux")>-1?"Linux":navigator.userAgent.indexOf("UNIX")>-1?"UNIX":"Unknown"}async function checkWebGl2(T){return T?(console.log("WebGl2 is enabled"),!0):(typeof WebGL2RenderingContext<"u"||console.log("WebGL2 is not supported"),!1)}async function detectGPUVendor(T){let u;if(T&&(u=T.getExtension("WEBGL_debug_renderer_info"),u)){const h=T.getParameter(u.UNMASKED_VENDOR_WEBGL);return h.indexOf("(")>-1&&h.indexOf(")")>-1?h.substring(h.indexOf("(")+1,h.indexOf(")")):h}return null}async function detectGPUVendor_v0(T){if(T){const u=T.getExtension("WEBGL_debug_renderer_info");return u?T.getParameter(u.UNMASKED_VENDOR_WEBGL):null}else return null}async function detectGPUCardType_v0(T){if(T){if(detectBrowser()==="Firefox")return T.getParameter(T.RENDERER);const u=T.getExtension("WEBGL_debug_renderer_info");return u?T.getParameter(u.UNMASKED_RENDERER_WEBGL):null}else return null}async function detectGPUCardType(T){let u;if(T){if(detectBrowser()==="Firefox")return T.getParameter(T.RENDERER);if(u=T.getExtension("WEBGL_debug_renderer_info"),u){let h=T.getParameter(u.UNMASKED_RENDERER_WEBGL);return h.indexOf("(")>-1&&h.indexOf(")")>-1&&h.indexOf("(R)")===-1&&(h=h.substring(h.indexOf("(")+1,h.indexOf(")")),h.split(",").length===3)?h.split(",")[1].trim():h}}return null}async function getCPUNumCores(){return navigator.hardwareConcurrency}async function isChrome(){return/Chrome/.test(navigator.userAgent)&&/Google Inc/.test(navigator.vendor)}async function localSystemDetails(T,u=null){const h=new Date;if(T.isModelFullVol?T.Brainchop_Ver="FullVolume":T.Brainchop_Ver="SubVolumes",T.Total_t=(Date.now()-T.startTime)/1e3,delete T.startTime,T.Date=parseInt(h.getMonth()+1)+"/"+h.getDate()+"/"+h.getFullYear(),T.Browser=await detectBrowser(),T.Browser_Ver=await detectBrowserVersion(),T.OS=await detectOperatingSys(),T.WebGL2=await checkWebGl2(u),T.GPU_Vendor=await detectGPUVendor(u),T.GPU_Card=await detectGPUCardType(u),T.GPU_Vendor_Full=await detectGPUVendor_v0(u),T.GPU_Card_Full=await detectGPUCardType_v0(u),T.CPU_Cores=await getCPUNumCores(),T.Which_Brainchop="latest",await isChrome()&&(T.Heap_Size_MB=window.performance.memory.totalJSHeapSize/(1024*1024).toFixed(2),T.Used_Heap_MB=window.performance.memory.usedJSHeapSize/(1024*1024).toFixed(2),T.Heap_Limit_MB=window.performance.memory.jsHeapSizeLimit/(1024*1024).toFixed(2)),u){console.log("MAX_TEXTURE_SIZE :",u.getParameter(u.MAX_TEXTURE_SIZE)),console.log("MAX_RENDERBUFFER_SIZE :",u.getParameter(u.MAX_RENDERBUFFER_SIZE));const g=u.getExtension("WEBGL_debug_renderer_info");console.log("VENDOR WEBGL:",u.getParameter(g.UNMASKED_VENDOR_WEBGL)),T.Texture_Size=u.getParameter(u.MAX_TEXTURE_SIZE)}else T.Texture_Size=null;return T}function WorkerWrapper(T){return new Worker(""+new URL("brainchop-webworker-C3YgFmeN.js",import.meta.url).href,{name:T==null?void 0:T.name})}async function main(){const T=new Niimath;await T.init(),aboutBtn.onclick=function(){window.open("https://github.com/niivue/brain2print","_blank")},opacitySlider0.oninput=function(){k.setOpacity(0,opacitySlider0.value/255),k.updateGLVolume()},opacitySlider1.oninput=function(){k.setOpacity(1,opacitySlider1.value/255)};async function u(){let N=k.volumes[0],B=N.dims[1]===256&&N.dims[2]===256&&N.dims[3]===256;if((N.permRAS[0]!==-1||N.permRAS[1]!==3||N.permRAS[2]!==-2)&&(B=!1),B)return;let Q=await k.conform(N,!1);await k.removeVolume(k.volumes[0]),await k.addVolume(Q)}async function h(){for(;k.volumes.length>1;)await k.removeVolume(k.volumes[1])}modelSelect.onchange=async function(){this.selectedIndex<0&&(modelSelect.selectedIndex=11),await h(),await u();let N=inferenceModelsList[this.selectedIndex];N.isNvidia=!1;const B=k.gl.getExtension("WEBGL_debug_renderer_info");B&&(N.isNvidia=k.gl.getParameter(B.UNMASKED_RENDERER_WEBGL).includes("NVIDIA"));let Q=brainChopOpts;if(Q.rootURL=location.href,!!(window.location.hostname==="localhost"||window.location.hostname==="[::1]"||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/))&&(Q.rootURL=location.protocol+"//"+location.host),workerCheck.checked){if(typeof m<"u"){console.log("Unable to start new segmentation: previous call has not completed");return}m=await new WorkerWrapper({type:"module"});let j={datatypeCode:k.volumes[0].hdr.datatypeCode,dims:k.volumes[0].hdr.dims},H={opts:Q,modelEntry:N,niftiHeader:j,niftiImage:k.volumes[0].img};m.postMessage(H),m.onmessage=function(y){let Y=y.data.cmd;Y==="ui"&&(y.data.modalMessage!==""&&(m.terminate(),m=void 0),D(y.data.message,y.data.progressFrac,y.data.modalMessage,y.data.statData)),Y==="img"&&(m.terminate(),m=void 0,v(y.data.img,y.data.opts,y.data.modelEntry))}}else console.log("Only provided with webworker code, see main brainchop github repository for main thread code")},saveBtn.onclick=function(){k.volumes[1].saveToDisk("Custom.nii")},workerCheck.onchange=function(){modelSelect.onchange()},clipCheck.onchange=function(){clipCheck.checked?k.setClipPlane([0,0,90]):k.setClipPlane([2,0,90])};function g(){opacitySlider0.oninput()}async function S(N){return await(await fetch(N)).json()}async function v(N,B,Q){h();let e0=await k.volumes[0].clone();if(e0.zeroImage(),e0.hdr.scl_inter=0,e0.hdr.scl_slope=1,e0.img=new Uint8Array(N),Q.colormapPath){let j=await S(Q.colormapPath);e0.setColormapLabel(j),e0.hdr.intent_code=1002}else{let j=B.atlasSelectedColorTable.toLowerCase();k.colormaps().includes(j)||(j="actc"),e0.colormap=j}e0.opacity=opacitySlider1.value/255,await k.addVolume(e0)}async function w(N){(typeof N=="string"||N instanceof String)&&(N=function(e0){const j=JSON.parse(e0),H=[];for(const y in j)H[y]=j[y];return H}(N)),N=await localSystemDetails(N,k.gl),L=`:: Diagnostics can help resolve issues https://github.com/neuroneural/brainchop/issues :: `;for(var B in N)L+=B+": "+N[B]+` `}function D(N="",B=-1,Q="",e0=[]){N!==""&&(console.log(N),document.getElementById("location").innerHTML=N),isNaN(B)?(memstatus.style.color="red",memstatus.innerHTML="Memory Issue"):B>=0&&(modelProgress.value=B*modelProgress.max),Q!==""&&window.alert(Q),Object.keys(e0).length>0&&w(e0)}function _(N){document.getElementById("location").innerHTML="  "+N.string}let P={backColor:[.4,.4,.4,1],show3Dcrosshair:!0,onLocationChange:_};createMeshBtn.onclick=function(){k.meshes.length>0&&k.removeMesh(k.meshes[0]),k.volumes.length<1?window.alert("Image not loaded. Drag and drop an image."):remeshDialog.show()},applyBtn.onclick=async function(){const N=await k.saveImage({volumeByIndex:k.volumes.length-1}).buffer,B=new Blob([N],{type:"application/octet-stream"}),Q=new File([B],"input.nii");let e0=T.image(Q);loadingCircle.classList.remove("hidden");let j={i:.5};largestCheck.checked&&(j.l=1);let H=Math.min(Math.max(Number(shrinkPct.value)/100,.01),1);j.r=H,bubbleCheck.checked&&(j.b=1);let y=Number(hollowSelect.value);y<0&&(e0=e0.hollow(.5,y));let Y=Number(closeMM.value);isFinite(Y)&&Y>0&&(e0=e0.close(.5,Y,2*Y)),e0=e0.mesh(j),console.log("niimath mesh operation",e0.commands);const Z=await(await e0.run("output.mz3")).arrayBuffer();loadingCircle.classList.add("hidden"),k.meshes.length>0&&k.removeMesh(k.meshes[0]),await k.loadFromArrayBuffer(Z,"output.mz3"),k.reverseFaces(0)},saveMeshBtn.onclick=function(){k.meshes.length<1?window.alert("No mesh open for saving. Use 'Create Mesh'."):saveDialog.show()},applySaveBtn.onclick=function(){if(k.meshes.length<1)return;let N="obj";formatSelect.selectedIndex===0&&(N="mz3"),formatSelect.selectedIndex===2&&(N="stl");const B=1/Number(scaleSelect.value),Q=k.meshes[0].pts.slice();for(let e0=0;e0 Niivue brain chop - +