diff --git a/assets/index-DgVXdzB4.js b/assets/index-jh3uDHfm.js similarity index 99% rename from assets/index-DgVXdzB4.js rename to assets/index-jh3uDHfm.js index 915027a..aba57c6 100644 --- a/assets/index-DgVXdzB4.js +++ b/assets/index-jh3uDHfm.js @@ -1700,4 +1700,4 @@ void main(void) { <\/script> `}saveHTML(u="untitled.html",m="gl1",A){const S=this.generateHTML(m,A);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"){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)}async loadVolumes(u){if(this.loadingText="loading...",this.drawScene(),this.thumbnailVisible)return this.deferredVolumes=u,this;this.volumes=[],this.gl.clearColor(0,0,0,1),this.gl.clear(this.gl.COLOR_BUFFER_BIT);for(let m=0;m0&&(v[Y]=P);const L=this.r16Tex(null,TEXTURE12_GC_STRENGTH0,this.back.dims,v),d=this.r16Tex(null,TEXTURE13_GC_STRENGTH1,this.back.dims,v);m.bindVertexArray(this.genericVAO);const E=this.growCutShader;E.use(m);const e=128;m.uniform1i(E.uniforms.finalPass,0),m.uniform1i(E.uniforms.backTex,11);for(let Y=0;Yu[0]&&(D=1),m[1]>u[1]&&(R=1),m[2]>u[2]&&(P=1);let L=u[0],d=u[1],E=u[2];const e=m[0],N=m[1],q=m[2];if(S>=v&&S>=w){let z=2*v-S,e0=2*w-S;for(;L!==e;)L+=D,z>=0&&(d+=R,z-=2*S),e0>=0&&(E+=P,e0-=2*S),z+=2*v,e0+=2*w,this.drawPt(L,d,E,A)}else if(v>=S&&v>=w){let z=2*S-v,e0=2*w-v;for(;d!==N;)d+=R,z>=0&&(L+=D,z-=2*v),e0>=0&&(E+=P,e0-=2*v),z+=2*S,e0+=2*w,this.drawPt(L,d,E,A)}else{let z=2*v-w,e0=2*S-w;for(;E!==q;)E+=P,z>=0&&(d+=R,z-=2*w),e0>=0&&(L+=D,e0-=2*w),z+=2*v,e0+=2*S,this.drawPt(L,d,E,A)}}drawFloodFillCore(u,m,A=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(d){return d[0]+d[1]*v+d[2]*w}function R(d){const E=Math.floor(d/w),e=Math.floor((d-E*w)/v);return[Math.floor(d%v),e,E]}const P=[];for(P.push(m),u[m]=2;P.length>0;){let d=function(N){const q=e.slice();if(q[0]+=N[0],q[1]+=N[1],q[2]+=N[2],q[0]<0||q[1]<0||q[2]<0||q[0]>=S[0]||q[1]>=S[1]||q[2]>=S[2])return;const z=D(q);u[z]===1&&(u[z]=2,P.push(z))};const E=P[0];P.shift();const e=R(E);d([0,0,-1]),d([0,0,1]),d([0,-1,0]),d([0,1,0]),d([-1,0,0]),d([1,0,0]),!(A<=6)&&(d([-1,-1,0]),d([1,1,0]),d([-1,1,0]),d([1,1,0]),d([0,-1,-1]),d([0,1,-1]),d([-1,0,-1]),d([1,0,-1]),d([0,-1,1]),d([0,1,1]),d([-1,0,1]),d([1,0,1]),!(A<=18)&&(d([-1,-1,-1]),d([1,-1,-1]),d([-1,1,-1]),d([1,1,-1]),d([-1,-1,1]),d([1,-1,1]),d([-1,1,1]),d([1,1,1])))}}drawFloodFill(u,m=0,A=0,S=NaN,v=NaN,w=6){var q;if(!this.drawBitmap)throw new Error("drawBitmap undefined");if(!((q=this.back)!=null&&q.dims))throw new Error("back.dims undefined");m=Math.abs(m);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 R=D[0],P=R*D[1],L=P*D[2],d=this.drawBitmap.slice();if(d.length!==P*D[2])return;function E(z){return z[0]+z[1]*R+z[2]*P}const e=E(u),N=d[e];if(N===m){A!==0?log.debug("drawFloodFill selected voxel is not part of a drawing"):log.debug("drawFloodFill selected voxel is already desired color");return}for(let z=1;z=H&&z[j]<=e0&&(d[j]=1);this.drawFloodFillCore(d,e,w),m=N}for(let z=1;zq[0]&&(j=1),z[1]>q[1]&&(y=1);let Y=q[0],G=q[1];const J=z[0],i0=z[1];if(e0>=H){let Q=2*H-e0;for(;Y!==J;)Y+=j,Q>=0&&(G+=y,Q-=2*e0),Q+=2*H,w[Y+G*v[0]]=D}else{let Q=2*e0-H;for(;G!==i0;)G+=y,Q>=0&&(Y+=j,Q-=2*H),Q+=2*e0,w[Y+G*v[0]]=D}}const P=[this.drawPenFillPts[0][A],this.drawPenFillPts[0][S]];let L=P;for(let q=1;q=v[0]||q[1]>=v[1])return;const z=q[0]+q[1]*v[0];w[z]===0&&(d.push(q),w[z]=2)}for(let q=0;q0;){const q=d.shift();E([q[0]-1,q[1]]),E([q[0]+1,q[1]]),E([q[0],q[1]-1]),E([q[0],q[1]+1])}D=this.opts.penValue;const e=this.drawPenFillPts[0][3-(A+S)];if(!this.drawBitmap)throw new Error("drawBitmap undefined");if(m===0){const q=e*v[0]*v[1];for(let z=0;z0){const q=this.drawBitmap.length,z=decodeRLE(this.drawUndoBitmaps[this.currentDrawUndoBitmap],q);for(let e0=0;e0{const v=new Image;v.onload=()=>{if(!this.bmpShader)return;let w;m===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)):m===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),A(w),m!==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 A=0;A<256;A++)this.fontMets.mets[A]={xadv:0,uv_lbwh:[0,0,0,0],lbwh:[0,0,0,0]};const u=this.fontMetrics.atlas.width,m=this.fontMetrics.atlas.height;for(let A=0;A=this.meshes.length){log.debug("Unable to change shader until mesh is loaded (maybe you need async)");return}this.meshes[S].meshShaderIndex=A,this.updateGLVolume(),this.onMeshShaderChanged(S,A)}createCustomMeshShader(u,m="Custom"){if(!u)throw new Error("Need fragment shader");const A=this.meshShaderNameToNumber(m);A>=0&&(this.gl.deleteProgram(this.meshShaders[A].shader.program),this.meshShaders.splice(A,1));const S=new Shader(this.gl,vertMeshShader,u);return S.use(this.gl),{Name:m,Frag:u,shader:S}}setCustomMeshShader(u="",m="Custom"){const A=this.createCustomMeshShader(u,m);return this.meshShaders.push(A),this.onCustomMeshShaderAdded(u,m),this.meshShaders.length-1}meshShaderNames(u=!0){const m=[];for(let A=0;A0&&(await this.loadBmpTexture(this.opts.thumbnail),this.thumbnailVisible=!0),this.updateGLVolume(),this.initialized=!0,this.resizeListener(),this.drawScene(),this}gradientGL(u){const m=this.gl,A=[0,0,0,0,1,0,1,0,0,1,1,0],S=m.createVertexArray();m.bindVertexArray(S);const v=m.createBuffer();m.bindBuffer(m.ARRAY_BUFFER,v),m.bufferData(m.ARRAY_BUFFER,new Float32Array(A),m.STATIC_DRAW),m.enableVertexAttribArray(0),m.vertexAttribPointer(0,3,m.FLOAT,!1,0,0);const w=m.createFramebuffer();m.bindFramebuffer(m.FRAMEBUFFER,w),m.disable(m.CULL_FACE),m.viewport(0,0,u.dims[1],u.dims[2]),m.disable(m.BLEND);const D=this.rgbaTex(null,TEXTURE8_GRADIENT_TEMP,u.dims),R=this.blurShader;R.use(m),m.activeTexture(TEXTURE0_BACK_VOL),m.bindTexture(m.TEXTURE_3D,this.volumeTexture);const P=.7;m.uniform1i(R.uniforms.intensityVol,0),m.uniform1f(R.uniforms.dX,P/u.dims[1]),m.uniform1f(R.uniforms.dY,P/u.dims[2]),m.uniform1f(R.uniforms.dZ,P/u.dims[3]),m.bindVertexArray(S);for(let E=0;E0&&(this.furthestVertexFromOrigin=this.volumeObject3D.furthestVertexFromOrigin),this.meshes)for(let A=0;A0)for(let J=0;J0&&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(m===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:J}=this.sliceScale(!0);if(this.volScale=G,this.vox=J,this.volumeObject3D.scale=G,!this.renderShader)throw new Error("renderShader undefined");this.renderShader.use(this.gl),this.gl.uniform3fv(this.renderShader.uniforms.texVox,J),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 J=this.mm2frac(u.mm100,0,!0),i0=this.mm2frac(u.mm010,0,!0),Q=this.mm2frac(u.mm001,0,!0);J=subtract$1(J,J,G),i0=subtract$1(i0,i0,G),Q=subtract$1(Q,Q,G),D=fromValues$3(J[0],i0[0],Q[0],G[0],J[1],i0[1],Q[1],G[1],J[2],i0[2],Q[2],G[2],0,0,0,1),invert(D,D),m===1?(w=this.rgbaTex(this.overlayTexture,TEXTURE2_OVERLAY_VOL,this.back.dims),this.overlayTexture=w,this.overlayTextureID=w):w=this.overlayTextureID}const R=this.gl.createFramebuffer();this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,R),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(!A)throw new Error("hdr undefined");if(!S)throw new Error("img undefined");if(A.datatypeCode===2)A.intent_code===1002&&(L=this.orientShaderAtlasU),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R8UI,A.dims[1],A.dims[2],A.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,A.dims[1],A.dims[2],A.dims[3],this.gl.RED_INTEGER,this.gl.UNSIGNED_BYTE,S);else if(A.datatypeCode===4)L=this.orientShaderI,A.intent_code===1002&&(L=this.orientShaderAtlasI),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R16I,A.dims[1],A.dims[2],A.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,A.dims[1],A.dims[2],A.dims[3],this.gl.RED_INTEGER,this.gl.SHORT,S);else if(A.datatypeCode===16)this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R32F,A.dims[1],A.dims[2],A.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,A.dims[1],A.dims[2],A.dims[3],this.gl.RED,this.gl.FLOAT,S),L=this.orientShaderF;else if(A.datatypeCode===64){let G=new Float32Array;G=Float32Array.from(S),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R32F,A.dims[1],A.dims[2],A.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,A.dims[1],A.dims[2],A.dims[3],this.gl.RED,this.gl.FLOAT,G),L=this.orientShaderF}else A.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,A.dims[1],A.dims[2],A.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,A.dims[1],A.dims[2],A.dims[3],this.gl.RGB_INTEGER,this.gl.UNSIGNED_BYTE,S)):A.datatypeCode===512?(A.intent_code===1002&&(L=this.orientShaderAtlasU),this.gl.texStorage3D(this.gl.TEXTURE_3D,1,this.gl.R16UI,A.dims[1],A.dims[2],A.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,A.dims[1],A.dims[2],A.dims[3],this.gl.RED_INTEGER,this.gl.UNSIGNED_SHORT,S)):A.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,A.dims[1],A.dims[2],A.dims[3]),this.gl.texSubImage3D(this.gl.TEXTURE_3D,0,0,0,0,A.dims[1],A.dims[2],A.dims[3],this.gl.RGBA_INTEGER,this.gl.UNSIGNED_BYTE,S));u.global_min===void 0&&u.calMinMax();let d=null;if(this.gl.bindVertexArray(this.genericVAO),m>1){if(!this.back.dims)throw new Error("back.dims undefined");d=this.rgbaTex(d,TEXTURE10_BLEND,this.back.dims),this.gl.bindTexture(this.gl.TEXTURE_3D,d);const G=this.passThroughShader;G.use(this.gl),this.gl.uniform1i(G.uniforms.in3D,2);for(let J=0;J7){const G=u.colormapLabel.max-u.colormapLabel.min+1;E=this.createColormapTexture(E,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,E)}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,m),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,A.scl_inter),this.gl.uniform1f(L.uniforms.scl_slope??null,A.scl_slope),this.gl.uniform1f(L.uniforms.opacity??null,v),this.gl.uniform1i(L.uniforms.modulationVol??null,7);let q=null;if(u.modulationImage!==null&&u.modulationImage>=0&&u.modulationImage0;let d0=this.volumes[u.modulationImage].cal_min,S0=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,S0=this.volumes[u.modulationImage].cal_minNeg),d0=Math.abs(d0),S0=Math.abs(S0),d0>S0&&([d0,S0]=[S0,d0]);const p0=1/(S0-d0);let T0=Math.abs(u.modulateAlpha);T0=Math.max(T0,1);const x0=this.volumes[u.modulationImage].frame4D*J;for(let M0=0;M00?this.gradientGL(A):(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),H=e0.vox,j=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.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,H),this.gl.uniform3fv(this.renderShader.uniforms.volScale,j),!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,H);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),E!==null&&(this.gl.deleteTexture(E),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(m)}colormaps(){return cmapper.colormaps()}addColormap(u,m){cmapper.addColormap(u,m)}setColormap(u,m){const A=this.getVolumeIndexByID(u);this.volumes[A].colormap=m,this.updateGLVolume()}idx(u,m,A,S){return A*S[0]*S[1]+m*S[0]+u}check_previous_slice(u,m,A,S,v,w,D,R){const P=new Uint32Array(27);let L=0;if(!v)return 0;const d=u[this.idx(A,S,v,w)];if(D>=6){const E=this.idx(A,S,v-1,w);d===u[E]&&(P[L++]=m[E])}if(D>=18){if(A){const E=this.idx(A-1,S,v-1,w);d===u[E]&&(P[L++]=m[E])}if(S){const E=this.idx(A,S-1,v-1,w);d===u[E]&&(P[L++]=m[E])}if(A=6){if(E){const q=this.idx(E-1,d,L,m);N===u[q]&&(P[e++]=R[q])}if(d){const q=this.idx(E,d-1,L,m);N===u[q]&&(P[e++]=R[q])}}if(A>=18){if(d&&E){const q=this.idx(E-1,d-1,L,m);N===u[q]&&(P[e++]=R[q])}if(d&&E=w){w+=v;const q=new Uint32Array(w);q.set(D),D=q}D[S-1]=S,S++}}}for(let L=0;L100){log.info(` -Ooh no!!`);break}v[R]=P,D=Math.min(D,P)}for(let R=0;Ru.cal_min){w=u.cal_min,D=u.cal_max;const j=(A-m)/(D-w);return log.info(" Robust Rescale: min: "+w+" max: "+D+" scale: "+j),console.log("Robust Rescale: min: "+w+" max: "+D+" scale: "+j),[w,j]}const R=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 j=u.img,y=new Float32Array(u.img.length);for(let Y=0;Y=1e-15&&L++;const d=1e3,E=(D-w)/d,e=new Array(d).fill(0);for(let j=0;j=q);)z++;const e0=w;for(w=z*E+e0,q=P-Math.floor((1-v)*L),z=0;z=q);)z++;D=z*E+e0;let H=1;return w!==D&&(H=(A-m)/(D-w)),log.info(" Rescale: min: "+w+" max: "+D+" scale: "+H),[w,H]}conformVox2Vox(u,m,A=256,S=1,v=!1){const w=m.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]),R=fromValues$1(u[1]/2,u[2]/2,u[3]/2,1),P=create$1(),L=create$3();transpose(L,D),transformMat4(P,R,L);const d=fromValues$2(P[0],P[1],P[2]),E=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(A,A,A,1),q=create$3();scale$3(q,e,E);const z=fromValues$1(N[0],N[1],N[2],1);transformMat4(z,z,q),scale$1(z,z,.5);const e0=create$2();subtract$1(e0,d,fromValues$2(z[0],z[1],z[2]));const H=create$3();transpose(H,q),H[3]=e0[0],H[7]=e0[1],H[11]=e0[2];const j=create$3();invert(j,H);const y=create$3();mul(y,D,j);const Y=create$3();return invert(Y,y),[H,y,Y]}async createNiftiArray(u=[256,256,256],m=[1,1,1],A=[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,m,A,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,m=!1,A=!0,S=!1,v=!1){const R=this.conformVox2Vox(u.hdr.dims,u.hdr.affine.flat(),256,1,m),P=R[0],L=R[2],d=256*256*256,E=new Float32Array(d),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 o0=0;o0=q||Ee>=z||I0>=e0)continue;const Be=T0-ee,Ge=x0-ie,ti=M0-Fe,ai=1-Be,ri=1-Ge,qe=1-ti,be=y(ee,ie,Fe);let J0=0;J0+=e[be]*ai*ri*qe,J0+=e[be+H]*ai*ri*ti,J0+=e[be+q]*ai*Ge*qe,J0+=e[be+q+H]*ai*Ge*ti,J0+=e[be+1]*Be*ri*qe,J0+=e[be+1+H]*Be*ri*ti,J0+=e[be+1+q]*Be*Ge*qe,J0+=e[be+1+q+H]*Be*Ge*ti,E[j]=J0}}else for(let o0=0;o0<256;o0++)for(let l0=0;l0<256;l0++){const c0=l0*L[1]+o0*L[2]+L[3],d0=l0*L[5]+o0*L[6]+L[7],S0=l0*L[9]+o0*L[10]+L[11];for(let p0=0;p0<256;p0++){const T0=Math.round(p0*Y+c0),x0=Math.round(p0*G+d0),M0=Math.round(p0*J+S0);j++,!(T0<0||x0<0||M0<0)&&(T0>=q||x0>=z||M0>=e0||(E[j]=e[y(T0,x0,M0)]))}}let i0=0;v&&(i0=NaN);let Q=new Uint8Array;if(S){const o0=await this.getScale(u,0,1,i0),l0=await this.scalecropFloat32(E,0,1,o0[0],o0[1]);Q=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),16,new Uint8Array(l0.buffer))}else{const o0=await this.getScale(u,0,255,i0),l0=await this.scalecropUint8(E,0,255,o0[0],o0[1]);Q=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),2,l0)}return await this.niftiArray2NVImage(Q)}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,m){this.setColormap(u,m)}setColormapNegative(u,m){const A=this.getVolumeIndexByID(u);this.volumes[A].colormapNegative=m,this.updateGLVolume()}setModulationImage(u,m,A=0){const S=this.getVolumeIndexByID(u);let v=null;m.length>0&&(v=this.getVolumeIndexByID(m)),this.volumes[S].modulationImage=v,this.volumes[S].modulateAlpha=A,this.updateGLVolume()}setGamma(u=1){cmapper.gamma=u,this.updateGLVolume()}async loadDeferred4DVolumes(u){const m=this.getVolumeIndexByID(u),A=this.volumes[m];if(A.nTotalFrame4D<=A.nFrame4D)return;let S;A.fileObject?S=await NVImage.loadFromFile({file:A.fileObject}):S=await NVImage.loadFromUrl({url:A.url}),S&&(A.img=S.img.slice(),A.nTotalFrame4D=S.nTotalFrame4D,A.nFrame4D=S.nFrame4D,this.updateGLVolume())}setFrame4D(u,m){const A=this.getVolumeIndexByID(u),S=this.volumes[A];m>S.nFrame4D-1&&(m=S.nFrame4D-1),m<0&&(m=0),m!==S.frame4D&&(S.frame4D=m,this.updateGLVolume(),this.onFrameChange(S,m),this.createOnLocationChange())}getFrame4D(u){const m=this.getVolumeIndexByID(u);return this.volumes[m].frame4D}colormapFromKey(u){return cmapper.colormapFromKey(u)}colormap(u="",m=!1){return cmapper.colormap(u,m)}createColormapTexture(u=null,m=0,A=256){return u!==null&&this.gl.deleteTexture(u),m<1||A<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,A,m),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="",m=NaN,A=NaN,S=!1,v=!1,w=!0,D=!1){u.length<1&&(w=!1),this.colormapLists.push({name:u,min:m,max:A,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]&&m>S[1]&&u=0&&this.screenSlices[A].axCorSag===4?A:-1}sliceScroll3D(u=0){if(u!==0){if(this.volumes.length>0&&this.scene.clipPlaneDepthAziElev[0]<1.8){const m=this.scene.clipPlaneDepthAziElev.slice();return u>0&&(m[0]=Math.min(1.5,m[0]+.025)),u<0&&(m[0]=Math.max(-1.5,m[0]-.025)),m[0]!==this.scene.clipPlaneDepthAziElev[0]?(this.scene.clipPlaneDepthAziElev=m,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,m){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 A=[(u-this.graph.LTWH[0])/this.graph.LTWH[2],(m-this.graph.LTWH[1])/this.graph.LTWH[3]];return A[0]>0&&A[1]>0&&A[0]<=1&&A[1]<=1}mouseClick(u,m,A=0,S=!0){if(u*=this.uiData.dpr,m*=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,m)){if(!this.graph.plotLTWH)throw new Error("plotLTWH undefined");const v=[(u-this.graph.plotLTWH[0])/this.graph.plotLTWH[2],(m-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,m)>=0){this.sliceScroll3D(A),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,m,v,!1);if(!(D[0]<0)){if(!S){this.scene.crosshairPos[2-w]=A,this.drawScene();return}if(A!==0){let R=1;A<0&&(R=-1);const P=[0,0,0];P[2-w]=R,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 R=this.frac2vox(this.scene.crosshairPos);if(!isFinite(this.opts.penValue)||this.opts.penValue<0||Object.is(this.opts.penValue,-0)){isFinite(this.opts.penValue)?this.drawFloodFill(R,Math.abs(this.opts.penValue),this.opts.floodFillNeighbors):this.drawFloodFill(R,0,this.opts.penValue,this.opts.floodFillNeighbors);return}if(isNaN(this.drawPenLocation[0]))this.drawPenAxCorSag=w,this.drawPenFillPts=[],this.drawPt(...R,this.opts.penValue);else{if(R[0]===this.drawPenLocation[0]&&R[1]===this.drawPenLocation[1]&&R[2]===this.drawPenLocation[2])return;this.drawPenLine(R,this.drawPenLocation,this.opts.penValue)}this.drawPenLocation=R,this.opts.isFilledPen&&this.drawPenFillPts.push(R),this.refreshDrawing(!1)}this.drawScene(),this.createOnLocationChange(w);return}}}drawRuler(){let u=[],m=[];for(let R=0;R1){m=this.screenSlices[R].leftTopWidthHeight,u=this.screenSlices[R].fovMM;break}if(m.length<4)return;const S=100/u[0]*m[2],v=m[0]+.5*m[2]-.5*S,w=m[1]+m[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 m=-.1*(u[0]-u[2]),A=u[1],S=A-2*this.opts.rulerWidth,v=A-4*this.opts.rulerWidth;for(let w=0;w<11;w++){const D=u[0]+w*m,R=[D,A,D,S];w%5===0&&(R[3]=v),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,R),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}this.gl.bindVertexArray(this.unusedVAO)}screenXY2mm(u,m,A=-1){let S;for(let v=0;v=0&&(w=A),this.screenSlices[w].axCorSag>2)continue;const R=this.screenSlices[w].leftTopWidthHeight;if(uR[0]+R[2]||m>R[1]+R[3]||(S=this.screenXY2TextureFrac(u,m,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 m=this.screenXY2mm(u[2],u[3]);if(isNaN(m[0]))return;const A=this.screenXY2mm(u[0],u[1],m[3]);if(isNaN(A[0])||isNaN(m[0])||isNaN(m[3]))return;const S=create$1(),v=this.uiData.pan2DxyzmmAtMouseDown[3];sub(S,m,A),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]}dragForCenterButton(u){this.dragForPanZoom(u)}dragForSlicer3D(u){let m=this.uiData.pan2DxyzmmAtMouseDown[3];const A=u[3]-u[1];m+=A*.01,m=Math.max(m,.1),m=Math.min(m,10);const v=this.scene.pan2Dxyzmm[3]-m;this.opts.yoke3Dto2DZoom&&(this.scene.volScaleMultiplier=m),this.scene.pan2Dxyzmm[3]=m;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 m=this.gl;if(m.bindVertexArray(this.genericVAO),m.depthFunc(m.ALWAYS),m.enable(m.BLEND),m.blendFunc(m.SRC_ALPHA,m.ONE_MINUS_SRC_ALPHA),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),m.uniform4fv(this.lineShader.uniforms.lineColor,this.opts.rulerColor),m.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[m.canvas.width,m.canvas.height]),m.uniform1f(this.lineShader.uniforms.thickness,this.opts.rulerWidth),m.uniform4fv(this.lineShader.uniforms.startXYendXY,u),m.drawArrays(m.TRIANGLE_STRIP,0,4);const A=this.opts.rulerColor;A[3]=1,m.uniform4fv(this.lineShader.uniforms.lineColor,A);const S=this.opts.rulerWidth;m.uniform1f(this.lineShader.uniforms.thickness,S*2);let v=[u[0],u[1]-S,u[0],u[1]+S];m.uniform4fv(this.lineShader.uniforms.startXYendXY,v),m.drawArrays(m.TRIANGLE_STRIP,0,4),v=[u[2],u[3]-S,u[2],u[3]+S],m.uniform4fv(this.lineShader.uniforms.startXYendXY,v),m.drawArrays(m.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 R=this.frac2mm(w);w=fromValues$2(R[0],R[1],R[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 d=len(L);let E=2;d>9&&(E=1),d>99&&(E=0);const e=d.toFixed(E);this.drawTextBetween(u,e,1,A)}m.bindVertexArray(this.unusedVAO)}drawRect(u,m=[1,0,0,-1]){if(m[3]<0&&(m=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,m),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,m=this.opts.fontColor,A=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,m),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,A),this.gl.uniform4fv(this.circleShader.uniforms.circleColor,m),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 m=this.getAllLabels();if(m.length===0)return 0;const A=m.length===1?m[0].style.bulletScale:m.reduce((w,D)=>w.style.bulletScale>D.style.bulletScale?w:D).style.bulletScale,S=m.length===1?m[0]:m.reduce((w,D)=>{const R=this.opts.textHeight*this.gl.canvas.height*w.style.textScale,P=this.opts.textHeight*this.gl.canvas.height*D.style.textScale;return this.textHeight(R,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)*A,u+=v,u}getLegendPanelWidth(){const u=this.getAllLabels();if(!this.opts.showLegend||u.length===0)return 0;const A=this.opts.textHeight*this.gl.canvas.height*1;let S=0;const v=u.reduce((P,L)=>{const d=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,E=this.opts.textHeight*this.gl.canvas.height*L.style.textScale;return this.textWidth(d,P.text)>this.textWidth(E,L.text)?P:L}),w=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textWidth(w,v.text),R=this.getBulletMarginWidth();return D&&(S=R+D,S+=A*2),S}getLegendPanelHeight(){const u=this.getAllLabels();let m=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);m+=D}return m&&(m+=S/2*(u.length+1)),m}reserveColorbarPanel(){let u=Math.max(this.opts.textHeight,.01);u=u*Math.min(this.gl.canvas.height,this.gl.canvas.width);const m=3*u,A=[0,this.gl.canvas.height-m,this.gl.canvas.width,m];return this.colorbarHeight=A[3]+1,A}drawColorbarCore(u=0,m=[0,0,0,0],A=!1,S=0,v=1,w){if(m[2]<=0||m[3]<=0)return;let D=Math.max(this.opts.textHeight,.01);D=D*Math.min(this.gl.canvas.height,this.gl.canvas.width);let R=D;const P=3*D;let L=D;if(m[3]0&&(N=S,S=0),S===v||D<1)return;const q=Math.abs(v-S);let[z,e0]=tickSpacing(S,v);e0S.includes(R)).reduce((D,R)=>D.lbwh[3]>R.lbwh[3]?D:R).lbwh[3];return u*w}drawChar(u,m,A){if(!this.fontShader)throw new Error("fontShader undefined");const S=this.fontMets.mets[A],v=u[0]+m*S.lbwh[0],w=-(m*S.lbwh[1]),D=m*S.lbwh[2],R=m*S.lbwh[3],P=u[1]+(w-R)+m;return this.gl.uniform4f(this.fontShader.uniforms.leftTopWidthHeight,v,P,D,R),this.gl.uniform4fv(this.fontShader.uniforms.uvLeftTopWidthHeight,S.uv_lbwh),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),m*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,m,A=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)*A;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(m);this.gl.bindVertexArray(this.genericVAO);for(let R=0;R.8?P=[0,0,0,.5]:P=[1,1,1,.5],this.drawRect(R,P),this.drawText(v,m,A,S)}drawTextBelow(u,m,A=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*A,w=this.textWidth(v,m);w>this.canvas.width&&(A*=(this.canvas.width-2)/w,v=this.opts.textHeight*this.gl.canvas.height*A,w=this.textWidth(v,m)),u[0]-=.5*this.textWidth(v,m),u[0]=Math.max(u[0],1),u[0]=Math.min(u[0],this.canvas.width-w-1),this.drawText(u,m,A,S)}updateInterpolation(u,m=!1){let A=this.gl.LINEAR;!m&&this.opts.isNearestInterpolation&&(A=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,A),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MAG_FILTER,A)}setAtlasOutline(u){this.opts.atlasOutline=u,this.updateGLVolume(),this.drawScene()}setInterpolation(u){this.opts.isNearestInterpolation=u;const m=this.volumes.length;if(!(m<1)){for(let A=0;A0){this.opts.meshThicknessOn2D!==1/0&&(q=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,this.opts.meshThicknessOn2D,N,P,R,D));const e0=clone$2(q.modelViewProjectionMatrix);multiply(e0,e0,w),this.drawMesh3D(!0,1,e0,q.modelMatrix,q.normalMatrix)}isNaN(A)&&this.drawCrosshairs3D(!1,.15,q.modelViewProjectionMatrix,!0,this.opts.isSliceMM),this.drawSliceOrientationText(u,m),this.readyForSync=!0}calculateMvpMatrix(u,m=[0,0,0,0],A,S){(m[2]===0||m[3]===0)&&(m=[0,0,this.gl.canvas.width,this.gl.canvas.height]);const v=m[2]/m[3];let w=this.furthestFromPivot;const D=this.pivot3D,R=create$3();w=.8*w/this.scene.volScaleMultiplier,v<1?ortho(R,-w,w,-w/v,w/v,w*.01,w*8):ortho(R,-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(A-180)),translate(P,P,[-D[0],-D[1],-D[2]]);const d=create$3();invert(d,P);const E=create$3();transpose(E,d);const e=create$3();return multiply(e,R,P),[e,P,E]}calculateModelMatrix(u,m){if(!this.back)throw new Error("back undefined");const A=create$3();if(A[0]=-1,rotateX(A,A,deg2rad(270-m)),rotateZ(A,A,deg2rad(u-180)),this.back.obliqueRAS){const S=clone$2(this.back.obliqueRAS);multiply(A,A,S)}return A}calculateRayDirection(u,m){const A=this.calculateModelMatrix(u,m),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,A);const w=create$3();invert(w,v);const D=fromValues$1(0,0,-1,1);transformMat4(D,D,w);const R=fromValues$2(D[0],D[1],D[2]);normalize$1(R,R);const P=5e-5;return Math.abs(R[0])0){if(!this.volumeObject3D)throw new Error("volumeObject3D undefined");m=fromValues$2(this.volumeObject3D.extentsMin[0],this.volumeObject3D.extentsMin[1],this.volumeObject3D.extentsMin[2]),A=fromValues$2(this.volumeObject3D.extentsMax[0],this.volumeObject3D.extentsMax[1],this.volumeObject3D.extentsMax[2]),u||(m=fromValues$2(this.volumes[0].extentsMinOrtho[0],this.volumes[0].extentsMinOrtho[1],this.volumes[0].extentsMinOrtho[2]),A=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;m=fromValues$2(v[0],v[1],v[2]),A=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 A=[];if(u.vols.length<1)this.volumes[0]!=null&&A.push(0);else for(let l0=0;l0v){const l0=w-v;for(let c0=0;c0=w&&(w=v+1),this.drawRect(u.LTWH,u.backColor);const[D,R,P]=tickSpacing(v,w),L=Math.max(0,-1*Math.floor(Math.log(D)/Math.log(10)));v=Math.min(R,v),w=Math.max(P,w);function d(l0){return l0.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 q=0,z=R;if(N>0)for(;z<=w;){const l0=z.toFixed(L),c0=this.textWidth(N,l0);q=Math.max(c0,q),z+=D}const e0=.05,H=Math.abs(u.LTWH[2]),j=Math.abs(u.LTWH[3]),y=[u.LTWH[0]+e0*H+q,u.LTWH[1]+e0*j,u.LTWH[2]-q-2*e0*H,u.LTWH[3]-N-2*e0*j];this.graph.LTWH=u.LTWH,this.graph.plotLTWH=y,this.drawRect(y,this.opts.backColor);const Y=w-v,G=y[3]/Y,J=y[2]/(u.lines[0].length-1),i0=y[1]+y[3];z=R+.5*D;const Q=u.lineColor.slice();for(Q[3]=.25*u.lineColor[3];z<=w;){const l0=i0-(z-v)*G;this.drawLine([y[0],l0,y[0]+y[2],l0],.5*u.lineThickness,Q),z+=D}z=R;const f=.5*u.lineThickness;for(;z<=w;){const l0=i0-(z-v)*G;this.drawLine([y[0]-f,l0,y[0]+y[2]+u.lineThickness,l0],u.lineThickness,u.lineColor);const c0=z.toFixed(L);N>0&&this.drawTextLeft([y[0]-6,l0],c0,e,u.textColor),z+=D}let o0=1;for(;u.lines[0].length/o0>20;)o0*=5;for(let l0=0;l00&&this.drawTextBelow([c0,2+y[1]+y[3]],S0,e,u.textColor),this.drawLine([c0,y[1],c0,y[1]+y[3]],d0,u.lineColor)}}for(let l0=0;l0=0&&u.selectedColumnE/255);return}const D=unpackFloatFromVec4i(w);if(D>1)return;const R=(this.mousePos[0]-u[0])/u[2],P=(A.canvas.height-this.mousePos[1]-u[1])/u[3],L=unProject(R,P,D,m),d=this.mm2frac(L,0,!0);d[0]<0||d[0]>1||d[1]<0||d[1]>1||d[2]<0||d[2]>1||(this.scene.crosshairPos=this.mm2frac(L,0,!0))}drawImage3D(u,m,A){if(this.volumes.length===0)return;const S=this.gl,v=this.calculateRayDirection(m,A),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 R=this.calculateModelMatrix(m,A),P=create$3();invert(P,R);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,m=0,A=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 R=0;u[1]===0&&(R=v.canvas.height-this.effectiveCanvasHeight()),translate(w,w,[1.8*S+u[0],R+1.8*S+u[1],0]),scale$3(w,w,[S,S,S]),rotateX(w,w,deg2rad(270-A)),rotateZ(w,w,deg2rad(-m));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[m,A,S]=this.sceneExtentsMinMax(!0),v=Math.max(Math.max(S[0],S[1]),S[2]);function w(E){return Math.max(0,-Math.ceil(Math.log10(Math.abs(E))))}let D=w(v*.001);const R=this.frac2mm(this.scene.crosshairPos,0,!0);function P(E,e=0){return parseFloat(E.toFixed(e))}let L=P(R[0],D)+"×"+P(R[1],D)+"×"+P(R[2],D);if(this.volumes.length>0&&this.volumes[0].nFrame4D>0&&(L+="×"+P(this.volumes[0].frame4D)),this.volumes.length>0){let E=" = ";for(let q=0;q=0&&H=0&&(E+="+"),E+=P(e0,D)),E+=" "}L+=E;const e=this.back.dimsRAS,N=e[1]*e[2]*e[3];if(this.drawBitmap&&this.drawBitmap.length===N){const q=this.frac2vox(this.scene.crosshairPos),z=q[0]+q[1]*e[1]+q[2]*e[1]*e[2];L+=" "+this.drawLut.labels[this.drawBitmap[z]]}}const d={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(E=>{const e=this.frac2mm(this.scene.crosshairPos,0,!0),N=E.mm2vox(e),q=E.getValue(N[0],N[1],N[2],E.frame4D);return{name:E.name,value:q,id:E.id,mm:e,vox:N}}),string:L};this.onLocationChange(d)}addLabel(u,m,A){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=m?{...S,...m}:{...S},w=new NVLabel3D(u,v,A);return this.document.labels.push(w),w}calculateScreenPoint(u,m,A){const S=create$1();return transformMat4(S,[...u,1],m),S[3]!==0&&(S[0]=(S[0]/S[3]+1)*.5*A[2],S[1]=(1-S[1]/S[3])*.5*A[3],S[2]/=S[3],S[0]+=A[0],S[1]+=A[1]),S}getLabelAtPoint(u){log.debug("screenPoint",u);const m=this.getLegendPanelHeight(),A=this.getLegendPanelWidth(),S=this.gl.canvas.width-A;let v=(this.canvas.height-m)/2;if(log.debug("panelrect",S,v,S+A,v+m),u[0]S+A||u[1]>v+m)return null;const D=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,R=this.getAllLabels();for(const P of R){const L=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,d=this.textHeight(L,P.text);if(u[1]>=v&&u[1]<=v+d+D/2)return P;v+=d,v+=D/2}return null}drawLabelLine(u,m,A,S,v=!1){const w=Array.isArray(u.points)&&Array.isArray(u.points[0])?u.points:[u.points];for(const D of w){const R=this.calculateScreenPoint(D,A,S);v?this.drawDottedLine([...m,R[0],R[1]],u.style.lineWidth,u.style.lineColor):this.draw3DLine(m,[R[0],R[1],R[2]],u.style.lineWidth,u.style.lineColor)}}draw3DLabel(u,m,A,S,v=0,w,D=!1){const R=u.text,P=m[0],L=m[1],d=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,E=this.textHeight(u.style.textScale,R)*d;if(u.style.lineWidth>0&&Array.isArray(u.points)&&this.drawLabelLine(u,[P,L+E],A,S,D),u.style.bulletScale){const N=u.style.bulletScale*E,q=E-N,z=L+q/2+N/2,e0=P+(v-N)/2;this.drawCircle([e0,z,N,N],u.style.bulletColor)}let e=P;if(u.style.textAlignment!=="left"){const N=this.textWidth(u.style.textScale,u.text)*d;if(u.style.textAlignment==="right")e=P+w-d*1.5-N;else{const q=w-(v||d);e+=(q-N)/2}}else e+=v;this.drawText([e,L],R,u.style.textScale,u.style.textColor)}draw3DLabels(u,m,A=!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,R=this.getBulletMarginWidth(),P=this.getLegendPanelHeight(),L=this.getLegendPanelWidth(),d=v.canvas.width-L;let E=(this.canvas.height-P)/2;this.drawRect([v.canvas.width-L,E,L-D,P],this.opts.legendBackgroundColor);const e=v.getParameter(v.BLEND),N=v.getParameter(v.DEPTH_FUNC);A||(v.disable(v.BLEND),v.depthFunc(v.GREATER));for(const q of S){this.draw3DLabel(q,[d,E],u,m,R,L,A);const z=this.opts.textHeight*this.gl.canvas.height*q.style.textScale,e0=this.textHeight(z,q.text);E+=e0,E+=D/2}A||(v.depthFunc(N),e&&v.enable(v.BLEND))}draw3D(u=[0,0,0,0],m=null,A=null,S=null,v=null,w=0){const D=v!==null;this.setPivot3D(),D||(v=this.scene.renderAzimuth,w=this.scene.renderElevation);const R=this.gl;m===null&&([m,A,S]=this.calculateMvpMatrix(null,u,v,w));let P=[...u];if(u[2]===0||u[3]===0?(u=[0,0,R.canvas.width,R.canvas.height],P=[...u],this.screenSlices.push({leftTopWidthHeight:u,axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(A),0]})):(this.screenSlices.push({leftTopWidthHeight:u.slice(),axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(A),0]}),u[1]=R.canvas.height-u[3]-u[1]),R.enable(R.DEPTH_TEST),R.depthFunc(R.ALWAYS),R.depthMask(!0),R.clearDepth(0),this.draw3DLabels(m,P,!1),R.viewport(u[0],u[1],u[2],u[3]),this.volumes.length>0&&(this.updateInterpolation(0,!0),this.updateInterpolation(1,!0),this.drawImage3D(m,v,w)),this.updateInterpolation(0),this.updateInterpolation(1),D||this.drawCrosshairs3D(!0,1,m),this.drawMesh3D(!0,1,m,A,S),this.uiData.mouseDepthPicker){this.depthPicker(u,m),this.createOnLocationChange(),this.draw3D(u,m,A,S,v,w);return}this.opts.meshXRay>0&&this.drawMesh3D(!1,this.opts.meshXRay,m,A,S),this.draw3DLabels(m,P,!1),R.viewport(u[0],u[1],u[2],u[3]),D||this.drawCrosshairs3D(!1,.15,m),R.viewport(0,0,R.canvas.width,R.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(m,P,!0),L}drawMesh3D(u=!0,m=1,A,S,v){if(this.meshes.length<1)return;const w=this.gl;A||([A,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,R=!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,m=1,A=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,d,E]=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(E[0]<50||E[0]>1e3)&&(e=E[0]*.02);e*=this.opts.crosshairWidth,this.crosshairs3D=NiivueObject3D.generateCrosshairs(this.gl,1,D,L,d,e,20,this.opts.crosshairGap),this.crosshairs3D.mm=D}if(!this.surfaceShader)throw new Error("surfaceShader undefined");const R=this.surfaceShader;R.use(this.gl),A==null&&([A]=this.calculateMvpMatrix(this.crosshairs3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),w.uniformMatrix4fv(R.uniforms.mvpMtx,!1,A),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]=m,w.uniform4fv(R.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,m=0,A=!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[m].convertMM2Frac(u,A||this.opts.isSliceMM)}vox2frac(u,m=0){return this.volumes[m].convertVox2Frac(u)}frac2vox(u,m=0){return this.volumes.length<=m?[0,0,0]:this.volumes[m].convertFrac2Vox(u)}moveCrosshairInVox(u,m,A){const S=this.frac2vox(this.scene.crosshairPos);S[0]+=u,S[1]+=m,S[2]+=A,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,m=0,A=!1){const S=fromValues$1(u[0],u[1],u[2],1);if(this.volumes.length>0)return this.volumes[m].convertFrac2MM(u,A||this.opts.isSliceMM);{const[v,w]=this.sceneExtentsMinMax(),D=(R,P,L)=>R*(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,m,A,S=!0){const v=fromValues$2(-1,-1,-1),w=this.screenSlices[A].axCorSag;if(w>2)return v;const D=this.screenSlices[A].leftTopWidthHeight.slice();let R=!1;D[2]<0&&(R=!0,D[0]+=D[2],D[2]=-D[2]);let P=(u-D[0])/D[2];R&&(P=1-P);const L=1-(m-D[1])/D[3];if(P<0||P>1||L<0||L>1||this.screenSlices[A].AxyzMxy.length<4)return v;let d=fromValues$2(0,0,0);d[0]=this.screenSlices[A].leftTopMM[0]+P*this.screenSlices[A].fovMM[0],d[1]=this.screenSlices[A].leftTopMM[1]+L*this.screenSlices[A].fovMM[1];const E=this.screenSlices[A].AxyzMxy;d[2]=E[2]+E[4]*(d[1]-E[1])-E[3]*(d[0]-E[0]),w===1&&(d=swizzleVec3(d,[0,2,1])),w===2&&(d=swizzleVec3(d,[2,0,1]));const e=this.mm2frac(d);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 m=0;m=0)return A}return[-1,-1,-1]}scaleSlice(u,m,A=0,S=0){const v=this.effectiveCanvasWidth()-A,w=this.effectiveCanvasHeight()-S;let D=v/u;m*D>w&&(D=w/m);const R=u*D,P=m*D;return[(v-R)*.5,(w-P)*.5,R,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,m=this.gl.canvas.height*this.bmpTextureWH;m>this.gl.canvas.width&&(u=this.gl.canvas.width/this.bmpTextureWH,m=this.gl.canvas.width),this.gl.uniform4f(this.bmpShader.uniforms.leftTopWidthHeight,0,0,m,u),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawLine(u,m=1,A=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),A[3]<0&&(A=this.opts.crosshairColor),this.gl.uniform4fv(this.lineShader.uniforms.lineColor,A),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,m),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,u),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}draw3DLine(u,m,A=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,A),this.gl.uniform2fv(this.line3DShader.uniforms.startXY,u),this.gl.uniform3fv(this.line3DShader.uniforms.endXYZ,m),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawDottedLine(u,m=1,A=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl);const S=A[3]<0?[...this.opts.crosshairColor]:[...A];S[3]=.3;const v=fromValues(u[2]-u[0],u[3]-u[1]),w=length(v);normalize(v,v);const R=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1;scale(v,v,R/2);const P=length(v);let L=Math.floor(w/P);w%P&&L++;const d=[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,m);for(let E=0;E0&&m===0){const e=D,N=1;for(let q=0;q0&&m===1){const e=D,N=2;for(let q=0;q0&&m===2){const e=D,N=2;for(let q=0;q0&&m===0){const e=D,N=0;for(let q=0;q0&&m===1){const e=D,N=0;for(let q=0;q0&&m===2){const e=D,N=1;for(let q=0;q0){const P=w.leftTopWidthHeight.slice();let L=2;m===0&&(L=1);const d=this.frac2mm([.5,.5,.5]);for(let E=0;E0){const P=w.leftTopWidthHeight.slice(),L=w.fovMM[0]<0;let d=0;m===2&&(d=1);const E=this.frac2mm([.5,.5,.5]);for(let e=0;e"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 m=this.getMaxVols(),A=this.opts.sliceType===3&&m>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{const v=isFinite(this.drawPenLocation[0])&&this.opts.drawingEnabled,{volScale:w}=this.sliceScale();typeof this.opts.multiplanarPadPixels!="number"&&log.debug("multiplanarPadPixels must be numeric");const D=parseFloat(`${this.opts.multiplanarPadPixels}`),R=this.scaleSlice(w[0]+w[1],w[1]+w[2],D*1,D*1),P=Math.max(Math.max(w[1],w[2]),w[0]),L=this.scaleSlice(w[0]+w[0]+w[1],Math.max(w[1],w[2]),D*2),d=this.scaleSlice(w[0]+w[0]+w[1]+P,Math.max(w[1],w[2]),D*3),E=this.scaleSlice(P,w[1]+w[2]+w[2],0,D*2),e=this.scaleSlice(P,w[1]+w[2]+w[2]+P,0,D*3);let N=!v&&(m<2||!A),q=!1,z=!1,e0=!1;if(this.opts.multiplanarLayout===1?q=!0:this.opts.multiplanarLayout===2?z=!0:this.opts.multiplanarLayout===3?e0=!0:E[4]>L[4]&&E[4]>R[4]?q=!0:L[4]>R[4]?e0=!0:z=!0,q){let H=E;this.opts.multiplanarForceRender||e[4]>=E[4]?H=e:N=!1;const j=w[0]*H[4],y=w[1]*H[4],Y=w[2]*H[4],G=P*H[4];this.draw2D([H[0],H[1],j,y],0),this.draw2D([H[0],H[1]+y+D,j,Y],1),this.draw2D([H[0],H[1]+y+D+Y+D,y,Y],2),N&&this.draw3D([H[0],H[1]+y+Y+Y+D*3,G,G])}else if(e0){let H=L;this.opts.multiplanarForceRender||d[4]>=L[4]?H=d:N=!1;const j=w[0]*H[4],y=w[1]*H[4],Y=w[2]*H[4];this.draw2D([H[0],H[1],j,y],0),this.draw2D([H[0]+j+D,H[1],j,Y],1),this.draw2D([H[0]+j+j+D*2,H[1],y,Y],2),N&&this.draw3D([H[0]+j+j+y+D*3,H[1],H[3],H[3]])}else if(z){const H=R,j=w[0]*H[4],y=w[1]*H[4],Y=w[2]*H[4];this.draw2D([H[0],H[1]+Y+D,j,y],0),this.draw2D([H[0],H[1],j,Y],1),this.draw2D([H[0]+j+D,H[1],y,Y],2),N&&this.draw3D([H[0]+j+D,H[1]+Y+D,y,y])}}if(this.opts.isRuler&&this.drawRuler(),this.opts.isColorbar&&this.drawColorbar(),A&&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}};async function main(){async function T(S){await A.loadFromFile(S[0]),A.setColormap(A.volumes[2].id,"red"),lesionSlider.oninput()}openBtn.onclick=async function(){let S=document.createElement("input");S.style.display="none",S.type="file",document.body.appendChild(S),await A.removeVolume(A.volumes[2]),S.onchange=async function(v){await T(v.target.files)},S.click()},predictBtn.onclick=function(){window.alert("Outcome prediction: 0.70289")},aboutBtn.onclick=function(){window.alert("Drag and drop NIfTI images. Use pulldown menu to choose brainchop model")};function u(S){document.getElementById("intensity").innerHTML=S.string}const m={backColor:[.4,.4,.4,1],show3Dcrosshair:!0,onLocationChange:u};maskSlider.oninput=function(){A.setOpacity(1,this.value/255)},lesionSlider.oninput=function(){A.setOpacity(2,this.value/255)};const A=new Niivue(m);A.attachToCanvas(gl1),A.opts.dragMode=A.dragModes.pan,A.opts.multiplanarForceRender=!0,A.opts.yoke3Dto2DZoom=!0,A.opts.crosshairGap=11,A.setInterpolation(!0),await A.loadVolumes([{url:"./betsct1_unsmooth.nii.gz"},{url:"./mask_vox.nii.gz",colormap:"blue"},{url:"./M2095_lesion.nii.gz",colormap:"red"}]),maskSlider.oninput(),lesionSlider.oninput()}main(); +Ooh no!!`);break}v[R]=P,D=Math.min(D,P)}for(let R=0;Ru.cal_min){w=u.cal_min,D=u.cal_max;const j=(A-m)/(D-w);return log.info(" Robust Rescale: min: "+w+" max: "+D+" scale: "+j),console.log("Robust Rescale: min: "+w+" max: "+D+" scale: "+j),[w,j]}const R=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 j=u.img,y=new Float32Array(u.img.length);for(let Y=0;Y=1e-15&&L++;const d=1e3,E=(D-w)/d,e=new Array(d).fill(0);for(let j=0;j=q);)z++;const e0=w;for(w=z*E+e0,q=P-Math.floor((1-v)*L),z=0;z=q);)z++;D=z*E+e0;let H=1;return w!==D&&(H=(A-m)/(D-w)),log.info(" Rescale: min: "+w+" max: "+D+" scale: "+H),[w,H]}conformVox2Vox(u,m,A=256,S=1,v=!1){const w=m.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]),R=fromValues$1(u[1]/2,u[2]/2,u[3]/2,1),P=create$1(),L=create$3();transpose(L,D),transformMat4(P,R,L);const d=fromValues$2(P[0],P[1],P[2]),E=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(A,A,A,1),q=create$3();scale$3(q,e,E);const z=fromValues$1(N[0],N[1],N[2],1);transformMat4(z,z,q),scale$1(z,z,.5);const e0=create$2();subtract$1(e0,d,fromValues$2(z[0],z[1],z[2]));const H=create$3();transpose(H,q),H[3]=e0[0],H[7]=e0[1],H[11]=e0[2];const j=create$3();invert(j,H);const y=create$3();mul(y,D,j);const Y=create$3();return invert(Y,y),[H,y,Y]}async createNiftiArray(u=[256,256,256],m=[1,1,1],A=[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,m,A,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,m=!1,A=!0,S=!1,v=!1){const R=this.conformVox2Vox(u.hdr.dims,u.hdr.affine.flat(),256,1,m),P=R[0],L=R[2],d=256*256*256,E=new Float32Array(d),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 o0=0;o0=q||Ee>=z||I0>=e0)continue;const Be=T0-ee,Ge=x0-ie,ti=M0-Fe,ai=1-Be,ri=1-Ge,qe=1-ti,be=y(ee,ie,Fe);let J0=0;J0+=e[be]*ai*ri*qe,J0+=e[be+H]*ai*ri*ti,J0+=e[be+q]*ai*Ge*qe,J0+=e[be+q+H]*ai*Ge*ti,J0+=e[be+1]*Be*ri*qe,J0+=e[be+1+H]*Be*ri*ti,J0+=e[be+1+q]*Be*Ge*qe,J0+=e[be+1+q+H]*Be*Ge*ti,E[j]=J0}}else for(let o0=0;o0<256;o0++)for(let l0=0;l0<256;l0++){const c0=l0*L[1]+o0*L[2]+L[3],d0=l0*L[5]+o0*L[6]+L[7],S0=l0*L[9]+o0*L[10]+L[11];for(let p0=0;p0<256;p0++){const T0=Math.round(p0*Y+c0),x0=Math.round(p0*G+d0),M0=Math.round(p0*J+S0);j++,!(T0<0||x0<0||M0<0)&&(T0>=q||x0>=z||M0>=e0||(E[j]=e[y(T0,x0,M0)]))}}let i0=0;v&&(i0=NaN);let Q=new Uint8Array;if(S){const o0=await this.getScale(u,0,1,i0),l0=await this.scalecropFloat32(E,0,1,o0[0],o0[1]);Q=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),16,new Uint8Array(l0.buffer))}else{const o0=await this.getScale(u,0,255,i0),l0=await this.scalecropUint8(E,0,255,o0[0],o0[1]);Q=await this.createNiftiArray([256,256,256],[1,1,1],Array.from(P),2,l0)}return await this.niftiArray2NVImage(Q)}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,m){this.setColormap(u,m)}setColormapNegative(u,m){const A=this.getVolumeIndexByID(u);this.volumes[A].colormapNegative=m,this.updateGLVolume()}setModulationImage(u,m,A=0){const S=this.getVolumeIndexByID(u);let v=null;m.length>0&&(v=this.getVolumeIndexByID(m)),this.volumes[S].modulationImage=v,this.volumes[S].modulateAlpha=A,this.updateGLVolume()}setGamma(u=1){cmapper.gamma=u,this.updateGLVolume()}async loadDeferred4DVolumes(u){const m=this.getVolumeIndexByID(u),A=this.volumes[m];if(A.nTotalFrame4D<=A.nFrame4D)return;let S;A.fileObject?S=await NVImage.loadFromFile({file:A.fileObject}):S=await NVImage.loadFromUrl({url:A.url}),S&&(A.img=S.img.slice(),A.nTotalFrame4D=S.nTotalFrame4D,A.nFrame4D=S.nFrame4D,this.updateGLVolume())}setFrame4D(u,m){const A=this.getVolumeIndexByID(u),S=this.volumes[A];m>S.nFrame4D-1&&(m=S.nFrame4D-1),m<0&&(m=0),m!==S.frame4D&&(S.frame4D=m,this.updateGLVolume(),this.onFrameChange(S,m),this.createOnLocationChange())}getFrame4D(u){const m=this.getVolumeIndexByID(u);return this.volumes[m].frame4D}colormapFromKey(u){return cmapper.colormapFromKey(u)}colormap(u="",m=!1){return cmapper.colormap(u,m)}createColormapTexture(u=null,m=0,A=256){return u!==null&&this.gl.deleteTexture(u),m<1||A<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,A,m),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="",m=NaN,A=NaN,S=!1,v=!1,w=!0,D=!1){u.length<1&&(w=!1),this.colormapLists.push({name:u,min:m,max:A,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]&&m>S[1]&&u=0&&this.screenSlices[A].axCorSag===4?A:-1}sliceScroll3D(u=0){if(u!==0){if(this.volumes.length>0&&this.scene.clipPlaneDepthAziElev[0]<1.8){const m=this.scene.clipPlaneDepthAziElev.slice();return u>0&&(m[0]=Math.min(1.5,m[0]+.025)),u<0&&(m[0]=Math.max(-1.5,m[0]-.025)),m[0]!==this.scene.clipPlaneDepthAziElev[0]?(this.scene.clipPlaneDepthAziElev=m,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,m){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 A=[(u-this.graph.LTWH[0])/this.graph.LTWH[2],(m-this.graph.LTWH[1])/this.graph.LTWH[3]];return A[0]>0&&A[1]>0&&A[0]<=1&&A[1]<=1}mouseClick(u,m,A=0,S=!0){if(u*=this.uiData.dpr,m*=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,m)){if(!this.graph.plotLTWH)throw new Error("plotLTWH undefined");const v=[(u-this.graph.plotLTWH[0])/this.graph.plotLTWH[2],(m-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,m)>=0){this.sliceScroll3D(A),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,m,v,!1);if(!(D[0]<0)){if(!S){this.scene.crosshairPos[2-w]=A,this.drawScene();return}if(A!==0){let R=1;A<0&&(R=-1);const P=[0,0,0];P[2-w]=R,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 R=this.frac2vox(this.scene.crosshairPos);if(!isFinite(this.opts.penValue)||this.opts.penValue<0||Object.is(this.opts.penValue,-0)){isFinite(this.opts.penValue)?this.drawFloodFill(R,Math.abs(this.opts.penValue),this.opts.floodFillNeighbors):this.drawFloodFill(R,0,this.opts.penValue,this.opts.floodFillNeighbors);return}if(isNaN(this.drawPenLocation[0]))this.drawPenAxCorSag=w,this.drawPenFillPts=[],this.drawPt(...R,this.opts.penValue);else{if(R[0]===this.drawPenLocation[0]&&R[1]===this.drawPenLocation[1]&&R[2]===this.drawPenLocation[2])return;this.drawPenLine(R,this.drawPenLocation,this.opts.penValue)}this.drawPenLocation=R,this.opts.isFilledPen&&this.drawPenFillPts.push(R),this.refreshDrawing(!1)}this.drawScene(),this.createOnLocationChange(w);return}}}drawRuler(){let u=[],m=[];for(let R=0;R1){m=this.screenSlices[R].leftTopWidthHeight,u=this.screenSlices[R].fovMM;break}if(m.length<4)return;const S=100/u[0]*m[2],v=m[0]+.5*m[2]-.5*S,w=m[1]+m[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 m=-.1*(u[0]-u[2]),A=u[1],S=A-2*this.opts.rulerWidth,v=A-4*this.opts.rulerWidth;for(let w=0;w<11;w++){const D=u[0]+w*m,R=[D,A,D,S];w%5===0&&(R[3]=v),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,R),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}this.gl.bindVertexArray(this.unusedVAO)}screenXY2mm(u,m,A=-1){let S;for(let v=0;v=0&&(w=A),this.screenSlices[w].axCorSag>2)continue;const R=this.screenSlices[w].leftTopWidthHeight;if(uR[0]+R[2]||m>R[1]+R[3]||(S=this.screenXY2TextureFrac(u,m,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 m=this.screenXY2mm(u[2],u[3]);if(isNaN(m[0]))return;const A=this.screenXY2mm(u[0],u[1],m[3]);if(isNaN(A[0])||isNaN(m[0])||isNaN(m[3]))return;const S=create$1(),v=this.uiData.pan2DxyzmmAtMouseDown[3];sub(S,m,A),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]}dragForCenterButton(u){this.dragForPanZoom(u)}dragForSlicer3D(u){let m=this.uiData.pan2DxyzmmAtMouseDown[3];const A=u[3]-u[1];m+=A*.01,m=Math.max(m,.1),m=Math.min(m,10);const v=this.scene.pan2Dxyzmm[3]-m;this.opts.yoke3Dto2DZoom&&(this.scene.volScaleMultiplier=m),this.scene.pan2Dxyzmm[3]=m;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 m=this.gl;if(m.bindVertexArray(this.genericVAO),m.depthFunc(m.ALWAYS),m.enable(m.BLEND),m.blendFunc(m.SRC_ALPHA,m.ONE_MINUS_SRC_ALPHA),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),m.uniform4fv(this.lineShader.uniforms.lineColor,this.opts.rulerColor),m.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[m.canvas.width,m.canvas.height]),m.uniform1f(this.lineShader.uniforms.thickness,this.opts.rulerWidth),m.uniform4fv(this.lineShader.uniforms.startXYendXY,u),m.drawArrays(m.TRIANGLE_STRIP,0,4);const A=this.opts.rulerColor;A[3]=1,m.uniform4fv(this.lineShader.uniforms.lineColor,A);const S=this.opts.rulerWidth;m.uniform1f(this.lineShader.uniforms.thickness,S*2);let v=[u[0],u[1]-S,u[0],u[1]+S];m.uniform4fv(this.lineShader.uniforms.startXYendXY,v),m.drawArrays(m.TRIANGLE_STRIP,0,4),v=[u[2],u[3]-S,u[2],u[3]+S],m.uniform4fv(this.lineShader.uniforms.startXYendXY,v),m.drawArrays(m.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 R=this.frac2mm(w);w=fromValues$2(R[0],R[1],R[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 d=len(L);let E=2;d>9&&(E=1),d>99&&(E=0);const e=d.toFixed(E);this.drawTextBetween(u,e,1,A)}m.bindVertexArray(this.unusedVAO)}drawRect(u,m=[1,0,0,-1]){if(m[3]<0&&(m=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,m),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,m=this.opts.fontColor,A=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,m),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,A),this.gl.uniform4fv(this.circleShader.uniforms.circleColor,m),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 m=this.getAllLabels();if(m.length===0)return 0;const A=m.length===1?m[0].style.bulletScale:m.reduce((w,D)=>w.style.bulletScale>D.style.bulletScale?w:D).style.bulletScale,S=m.length===1?m[0]:m.reduce((w,D)=>{const R=this.opts.textHeight*this.gl.canvas.height*w.style.textScale,P=this.opts.textHeight*this.gl.canvas.height*D.style.textScale;return this.textHeight(R,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)*A,u+=v,u}getLegendPanelWidth(){const u=this.getAllLabels();if(!this.opts.showLegend||u.length===0)return 0;const A=this.opts.textHeight*this.gl.canvas.height*1;let S=0;const v=u.reduce((P,L)=>{const d=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,E=this.opts.textHeight*this.gl.canvas.height*L.style.textScale;return this.textWidth(d,P.text)>this.textWidth(E,L.text)?P:L}),w=this.opts.textHeight*this.gl.canvas.height*v.style.textScale,D=this.textWidth(w,v.text),R=this.getBulletMarginWidth();return D&&(S=R+D,S+=A*2),S}getLegendPanelHeight(){const u=this.getAllLabels();let m=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);m+=D}return m&&(m+=S/2*(u.length+1)),m}reserveColorbarPanel(){let u=Math.max(this.opts.textHeight,.01);u=u*Math.min(this.gl.canvas.height,this.gl.canvas.width);const m=3*u,A=[0,this.gl.canvas.height-m,this.gl.canvas.width,m];return this.colorbarHeight=A[3]+1,A}drawColorbarCore(u=0,m=[0,0,0,0],A=!1,S=0,v=1,w){if(m[2]<=0||m[3]<=0)return;let D=Math.max(this.opts.textHeight,.01);D=D*Math.min(this.gl.canvas.height,this.gl.canvas.width);let R=D;const P=3*D;let L=D;if(m[3]0&&(N=S,S=0),S===v||D<1)return;const q=Math.abs(v-S);let[z,e0]=tickSpacing(S,v);e0S.includes(R)).reduce((D,R)=>D.lbwh[3]>R.lbwh[3]?D:R).lbwh[3];return u*w}drawChar(u,m,A){if(!this.fontShader)throw new Error("fontShader undefined");const S=this.fontMets.mets[A],v=u[0]+m*S.lbwh[0],w=-(m*S.lbwh[1]),D=m*S.lbwh[2],R=m*S.lbwh[3],P=u[1]+(w-R)+m;return this.gl.uniform4f(this.fontShader.uniforms.leftTopWidthHeight,v,P,D,R),this.gl.uniform4fv(this.fontShader.uniforms.uvLeftTopWidthHeight,S.uv_lbwh),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),m*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,m,A=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)*A;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(m);this.gl.bindVertexArray(this.genericVAO);for(let R=0;R.8?P=[0,0,0,.5]:P=[1,1,1,.5],this.drawRect(R,P),this.drawText(v,m,A,S)}drawTextBelow(u,m,A=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*A,w=this.textWidth(v,m);w>this.canvas.width&&(A*=(this.canvas.width-2)/w,v=this.opts.textHeight*this.gl.canvas.height*A,w=this.textWidth(v,m)),u[0]-=.5*this.textWidth(v,m),u[0]=Math.max(u[0],1),u[0]=Math.min(u[0],this.canvas.width-w-1),this.drawText(u,m,A,S)}updateInterpolation(u,m=!1){let A=this.gl.LINEAR;!m&&this.opts.isNearestInterpolation&&(A=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,A),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MAG_FILTER,A)}setAtlasOutline(u){this.opts.atlasOutline=u,this.updateGLVolume(),this.drawScene()}setInterpolation(u){this.opts.isNearestInterpolation=u;const m=this.volumes.length;if(!(m<1)){for(let A=0;A0){this.opts.meshThicknessOn2D!==1/0&&(q=this.calculateMvpMatrix2D(u,v.mnMM,v.mxMM,this.opts.meshThicknessOn2D,N,P,R,D));const e0=clone$2(q.modelViewProjectionMatrix);multiply(e0,e0,w),this.drawMesh3D(!0,1,e0,q.modelMatrix,q.normalMatrix)}isNaN(A)&&this.drawCrosshairs3D(!1,.15,q.modelViewProjectionMatrix,!0,this.opts.isSliceMM),this.drawSliceOrientationText(u,m),this.readyForSync=!0}calculateMvpMatrix(u,m=[0,0,0,0],A,S){(m[2]===0||m[3]===0)&&(m=[0,0,this.gl.canvas.width,this.gl.canvas.height]);const v=m[2]/m[3];let w=this.furthestFromPivot;const D=this.pivot3D,R=create$3();w=.8*w/this.scene.volScaleMultiplier,v<1?ortho(R,-w,w,-w/v,w/v,w*.01,w*8):ortho(R,-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(A-180)),translate(P,P,[-D[0],-D[1],-D[2]]);const d=create$3();invert(d,P);const E=create$3();transpose(E,d);const e=create$3();return multiply(e,R,P),[e,P,E]}calculateModelMatrix(u,m){if(!this.back)throw new Error("back undefined");const A=create$3();if(A[0]=-1,rotateX(A,A,deg2rad(270-m)),rotateZ(A,A,deg2rad(u-180)),this.back.obliqueRAS){const S=clone$2(this.back.obliqueRAS);multiply(A,A,S)}return A}calculateRayDirection(u,m){const A=this.calculateModelMatrix(u,m),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,A);const w=create$3();invert(w,v);const D=fromValues$1(0,0,-1,1);transformMat4(D,D,w);const R=fromValues$2(D[0],D[1],D[2]);normalize$1(R,R);const P=5e-5;return Math.abs(R[0])0){if(!this.volumeObject3D)throw new Error("volumeObject3D undefined");m=fromValues$2(this.volumeObject3D.extentsMin[0],this.volumeObject3D.extentsMin[1],this.volumeObject3D.extentsMin[2]),A=fromValues$2(this.volumeObject3D.extentsMax[0],this.volumeObject3D.extentsMax[1],this.volumeObject3D.extentsMax[2]),u||(m=fromValues$2(this.volumes[0].extentsMinOrtho[0],this.volumes[0].extentsMinOrtho[1],this.volumes[0].extentsMinOrtho[2]),A=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;m=fromValues$2(v[0],v[1],v[2]),A=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 A=[];if(u.vols.length<1)this.volumes[0]!=null&&A.push(0);else for(let l0=0;l0v){const l0=w-v;for(let c0=0;c0=w&&(w=v+1),this.drawRect(u.LTWH,u.backColor);const[D,R,P]=tickSpacing(v,w),L=Math.max(0,-1*Math.floor(Math.log(D)/Math.log(10)));v=Math.min(R,v),w=Math.max(P,w);function d(l0){return l0.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 q=0,z=R;if(N>0)for(;z<=w;){const l0=z.toFixed(L),c0=this.textWidth(N,l0);q=Math.max(c0,q),z+=D}const e0=.05,H=Math.abs(u.LTWH[2]),j=Math.abs(u.LTWH[3]),y=[u.LTWH[0]+e0*H+q,u.LTWH[1]+e0*j,u.LTWH[2]-q-2*e0*H,u.LTWH[3]-N-2*e0*j];this.graph.LTWH=u.LTWH,this.graph.plotLTWH=y,this.drawRect(y,this.opts.backColor);const Y=w-v,G=y[3]/Y,J=y[2]/(u.lines[0].length-1),i0=y[1]+y[3];z=R+.5*D;const Q=u.lineColor.slice();for(Q[3]=.25*u.lineColor[3];z<=w;){const l0=i0-(z-v)*G;this.drawLine([y[0],l0,y[0]+y[2],l0],.5*u.lineThickness,Q),z+=D}z=R;const f=.5*u.lineThickness;for(;z<=w;){const l0=i0-(z-v)*G;this.drawLine([y[0]-f,l0,y[0]+y[2]+u.lineThickness,l0],u.lineThickness,u.lineColor);const c0=z.toFixed(L);N>0&&this.drawTextLeft([y[0]-6,l0],c0,e,u.textColor),z+=D}let o0=1;for(;u.lines[0].length/o0>20;)o0*=5;for(let l0=0;l00&&this.drawTextBelow([c0,2+y[1]+y[3]],S0,e,u.textColor),this.drawLine([c0,y[1],c0,y[1]+y[3]],d0,u.lineColor)}}for(let l0=0;l0=0&&u.selectedColumnE/255);return}const D=unpackFloatFromVec4i(w);if(D>1)return;const R=(this.mousePos[0]-u[0])/u[2],P=(A.canvas.height-this.mousePos[1]-u[1])/u[3],L=unProject(R,P,D,m),d=this.mm2frac(L,0,!0);d[0]<0||d[0]>1||d[1]<0||d[1]>1||d[2]<0||d[2]>1||(this.scene.crosshairPos=this.mm2frac(L,0,!0))}drawImage3D(u,m,A){if(this.volumes.length===0)return;const S=this.gl,v=this.calculateRayDirection(m,A),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 R=this.calculateModelMatrix(m,A),P=create$3();invert(P,R);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,m=0,A=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 R=0;u[1]===0&&(R=v.canvas.height-this.effectiveCanvasHeight()),translate(w,w,[1.8*S+u[0],R+1.8*S+u[1],0]),scale$3(w,w,[S,S,S]),rotateX(w,w,deg2rad(270-A)),rotateZ(w,w,deg2rad(-m));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[m,A,S]=this.sceneExtentsMinMax(!0),v=Math.max(Math.max(S[0],S[1]),S[2]);function w(E){return Math.max(0,-Math.ceil(Math.log10(Math.abs(E))))}let D=w(v*.001);const R=this.frac2mm(this.scene.crosshairPos,0,!0);function P(E,e=0){return parseFloat(E.toFixed(e))}let L=P(R[0],D)+"×"+P(R[1],D)+"×"+P(R[2],D);if(this.volumes.length>0&&this.volumes[0].nFrame4D>0&&(L+="×"+P(this.volumes[0].frame4D)),this.volumes.length>0){let E=" = ";for(let q=0;q=0&&H=0&&(E+="+"),E+=P(e0,D)),E+=" "}L+=E;const e=this.back.dimsRAS,N=e[1]*e[2]*e[3];if(this.drawBitmap&&this.drawBitmap.length===N){const q=this.frac2vox(this.scene.crosshairPos),z=q[0]+q[1]*e[1]+q[2]*e[1]*e[2];L+=" "+this.drawLut.labels[this.drawBitmap[z]]}}const d={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(E=>{const e=this.frac2mm(this.scene.crosshairPos,0,!0),N=E.mm2vox(e),q=E.getValue(N[0],N[1],N[2],E.frame4D);return{name:E.name,value:q,id:E.id,mm:e,vox:N}}),string:L};this.onLocationChange(d)}addLabel(u,m,A){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=m?{...S,...m}:{...S},w=new NVLabel3D(u,v,A);return this.document.labels.push(w),w}calculateScreenPoint(u,m,A){const S=create$1();return transformMat4(S,[...u,1],m),S[3]!==0&&(S[0]=(S[0]/S[3]+1)*.5*A[2],S[1]=(1-S[1]/S[3])*.5*A[3],S[2]/=S[3],S[0]+=A[0],S[1]+=A[1]),S}getLabelAtPoint(u){log.debug("screenPoint",u);const m=this.getLegendPanelHeight(),A=this.getLegendPanelWidth(),S=this.gl.canvas.width-A;let v=(this.canvas.height-m)/2;if(log.debug("panelrect",S,v,S+A,v+m),u[0]S+A||u[1]>v+m)return null;const D=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,R=this.getAllLabels();for(const P of R){const L=this.opts.textHeight*this.gl.canvas.height*P.style.textScale,d=this.textHeight(L,P.text);if(u[1]>=v&&u[1]<=v+d+D/2)return P;v+=d,v+=D/2}return null}drawLabelLine(u,m,A,S,v=!1){const w=Array.isArray(u.points)&&Array.isArray(u.points[0])?u.points:[u.points];for(const D of w){const R=this.calculateScreenPoint(D,A,S);v?this.drawDottedLine([...m,R[0],R[1]],u.style.lineWidth,u.style.lineColor):this.draw3DLine(m,[R[0],R[1],R[2]],u.style.lineWidth,u.style.lineColor)}}draw3DLabel(u,m,A,S,v=0,w,D=!1){const R=u.text,P=m[0],L=m[1],d=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1,E=this.textHeight(u.style.textScale,R)*d;if(u.style.lineWidth>0&&Array.isArray(u.points)&&this.drawLabelLine(u,[P,L+E],A,S,D),u.style.bulletScale){const N=u.style.bulletScale*E,q=E-N,z=L+q/2+N/2,e0=P+(v-N)/2;this.drawCircle([e0,z,N,N],u.style.bulletColor)}let e=P;if(u.style.textAlignment!=="left"){const N=this.textWidth(u.style.textScale,u.text)*d;if(u.style.textAlignment==="right")e=P+w-d*1.5-N;else{const q=w-(v||d);e+=(q-N)/2}}else e+=v;this.drawText([e,L],R,u.style.textScale,u.style.textColor)}draw3DLabels(u,m,A=!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,R=this.getBulletMarginWidth(),P=this.getLegendPanelHeight(),L=this.getLegendPanelWidth(),d=v.canvas.width-L;let E=(this.canvas.height-P)/2;this.drawRect([v.canvas.width-L,E,L-D,P],this.opts.legendBackgroundColor);const e=v.getParameter(v.BLEND),N=v.getParameter(v.DEPTH_FUNC);A||(v.disable(v.BLEND),v.depthFunc(v.GREATER));for(const q of S){this.draw3DLabel(q,[d,E],u,m,R,L,A);const z=this.opts.textHeight*this.gl.canvas.height*q.style.textScale,e0=this.textHeight(z,q.text);E+=e0,E+=D/2}A||(v.depthFunc(N),e&&v.enable(v.BLEND))}draw3D(u=[0,0,0,0],m=null,A=null,S=null,v=null,w=0){const D=v!==null;this.setPivot3D(),D||(v=this.scene.renderAzimuth,w=this.scene.renderElevation);const R=this.gl;m===null&&([m,A,S]=this.calculateMvpMatrix(null,u,v,w));let P=[...u];if(u[2]===0||u[3]===0?(u=[0,0,R.canvas.width,R.canvas.height],P=[...u],this.screenSlices.push({leftTopWidthHeight:u,axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(A),0]})):(this.screenSlices.push({leftTopWidthHeight:u.slice(),axCorSag:4,sliceFrac:0,AxyzMxy:[],leftTopMM:[],fovMM:[isRadiological(A),0]}),u[1]=R.canvas.height-u[3]-u[1]),R.enable(R.DEPTH_TEST),R.depthFunc(R.ALWAYS),R.depthMask(!0),R.clearDepth(0),this.draw3DLabels(m,P,!1),R.viewport(u[0],u[1],u[2],u[3]),this.volumes.length>0&&(this.updateInterpolation(0,!0),this.updateInterpolation(1,!0),this.drawImage3D(m,v,w)),this.updateInterpolation(0),this.updateInterpolation(1),D||this.drawCrosshairs3D(!0,1,m),this.drawMesh3D(!0,1,m,A,S),this.uiData.mouseDepthPicker){this.depthPicker(u,m),this.createOnLocationChange(),this.draw3D(u,m,A,S,v,w);return}this.opts.meshXRay>0&&this.drawMesh3D(!1,this.opts.meshXRay,m,A,S),this.draw3DLabels(m,P,!1),R.viewport(u[0],u[1],u[2],u[3]),D||this.drawCrosshairs3D(!1,.15,m),R.viewport(0,0,R.canvas.width,R.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(m,P,!0),L}drawMesh3D(u=!0,m=1,A,S,v){if(this.meshes.length<1)return;const w=this.gl;A||([A,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,R=!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,m=1,A=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,d,E]=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(E[0]<50||E[0]>1e3)&&(e=E[0]*.02);e*=this.opts.crosshairWidth,this.crosshairs3D=NiivueObject3D.generateCrosshairs(this.gl,1,D,L,d,e,20,this.opts.crosshairGap),this.crosshairs3D.mm=D}if(!this.surfaceShader)throw new Error("surfaceShader undefined");const R=this.surfaceShader;R.use(this.gl),A==null&&([A]=this.calculateMvpMatrix(this.crosshairs3D,void 0,this.scene.renderAzimuth,this.scene.renderElevation)),w.uniformMatrix4fv(R.uniforms.mvpMtx,!1,A),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]=m,w.uniform4fv(R.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,m=0,A=!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[m].convertMM2Frac(u,A||this.opts.isSliceMM)}vox2frac(u,m=0){return this.volumes[m].convertVox2Frac(u)}frac2vox(u,m=0){return this.volumes.length<=m?[0,0,0]:this.volumes[m].convertFrac2Vox(u)}moveCrosshairInVox(u,m,A){const S=this.frac2vox(this.scene.crosshairPos);S[0]+=u,S[1]+=m,S[2]+=A,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,m=0,A=!1){const S=fromValues$1(u[0],u[1],u[2],1);if(this.volumes.length>0)return this.volumes[m].convertFrac2MM(u,A||this.opts.isSliceMM);{const[v,w]=this.sceneExtentsMinMax(),D=(R,P,L)=>R*(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,m,A,S=!0){const v=fromValues$2(-1,-1,-1),w=this.screenSlices[A].axCorSag;if(w>2)return v;const D=this.screenSlices[A].leftTopWidthHeight.slice();let R=!1;D[2]<0&&(R=!0,D[0]+=D[2],D[2]=-D[2]);let P=(u-D[0])/D[2];R&&(P=1-P);const L=1-(m-D[1])/D[3];if(P<0||P>1||L<0||L>1||this.screenSlices[A].AxyzMxy.length<4)return v;let d=fromValues$2(0,0,0);d[0]=this.screenSlices[A].leftTopMM[0]+P*this.screenSlices[A].fovMM[0],d[1]=this.screenSlices[A].leftTopMM[1]+L*this.screenSlices[A].fovMM[1];const E=this.screenSlices[A].AxyzMxy;d[2]=E[2]+E[4]*(d[1]-E[1])-E[3]*(d[0]-E[0]),w===1&&(d=swizzleVec3(d,[0,2,1])),w===2&&(d=swizzleVec3(d,[2,0,1]));const e=this.mm2frac(d);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 m=0;m=0)return A}return[-1,-1,-1]}scaleSlice(u,m,A=0,S=0){const v=this.effectiveCanvasWidth()-A,w=this.effectiveCanvasHeight()-S;let D=v/u;m*D>w&&(D=w/m);const R=u*D,P=m*D;return[(v-R)*.5,(w-P)*.5,R,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,m=this.gl.canvas.height*this.bmpTextureWH;m>this.gl.canvas.width&&(u=this.gl.canvas.width/this.bmpTextureWH,m=this.gl.canvas.width),this.gl.uniform4f(this.bmpShader.uniforms.leftTopWidthHeight,0,0,m,u),this.gl.bindVertexArray(this.genericVAO),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawLine(u,m=1,A=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl),A[3]<0&&(A=this.opts.crosshairColor),this.gl.uniform4fv(this.lineShader.uniforms.lineColor,A),this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight,[this.gl.canvas.width,this.gl.canvas.height]),this.gl.uniform1f(this.lineShader.uniforms.thickness,m),this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY,u),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}draw3DLine(u,m,A=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,A),this.gl.uniform2fv(this.line3DShader.uniforms.startXY,u),this.gl.uniform3fv(this.line3DShader.uniforms.endXYZ,m),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.gl.bindVertexArray(this.unusedVAO)}drawDottedLine(u,m=1,A=[1,0,0,-1]){if(this.gl.bindVertexArray(this.genericVAO),!this.lineShader)throw new Error("lineShader undefined");this.lineShader.use(this.gl);const S=A[3]<0?[...this.opts.crosshairColor]:[...A];S[3]=.3;const v=fromValues(u[2]-u[0],u[3]-u[1]),w=length(v);normalize(v,v);const R=this.opts.textHeight*Math.min(this.gl.canvas.height,this.gl.canvas.width)*1;scale(v,v,R/2);const P=length(v);let L=Math.floor(w/P);w%P&&L++;const d=[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,m);for(let E=0;E0&&m===0){const e=D,N=1;for(let q=0;q0&&m===1){const e=D,N=2;for(let q=0;q0&&m===2){const e=D,N=2;for(let q=0;q0&&m===0){const e=D,N=0;for(let q=0;q0&&m===1){const e=D,N=0;for(let q=0;q0&&m===2){const e=D,N=1;for(let q=0;q0){const P=w.leftTopWidthHeight.slice();let L=2;m===0&&(L=1);const d=this.frac2mm([.5,.5,.5]);for(let E=0;E0){const P=w.leftTopWidthHeight.slice(),L=w.fovMM[0]<0;let d=0;m===2&&(d=1);const E=this.frac2mm([.5,.5,.5]);for(let e=0;e"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 m=this.getMaxVols(),A=this.opts.sliceType===3&&m>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{const v=isFinite(this.drawPenLocation[0])&&this.opts.drawingEnabled,{volScale:w}=this.sliceScale();typeof this.opts.multiplanarPadPixels!="number"&&log.debug("multiplanarPadPixels must be numeric");const D=parseFloat(`${this.opts.multiplanarPadPixels}`),R=this.scaleSlice(w[0]+w[1],w[1]+w[2],D*1,D*1),P=Math.max(Math.max(w[1],w[2]),w[0]),L=this.scaleSlice(w[0]+w[0]+w[1],Math.max(w[1],w[2]),D*2),d=this.scaleSlice(w[0]+w[0]+w[1]+P,Math.max(w[1],w[2]),D*3),E=this.scaleSlice(P,w[1]+w[2]+w[2],0,D*2),e=this.scaleSlice(P,w[1]+w[2]+w[2]+P,0,D*3);let N=!v&&(m<2||!A),q=!1,z=!1,e0=!1;if(this.opts.multiplanarLayout===1?q=!0:this.opts.multiplanarLayout===2?z=!0:this.opts.multiplanarLayout===3?e0=!0:E[4]>L[4]&&E[4]>R[4]?q=!0:L[4]>R[4]?e0=!0:z=!0,q){let H=E;this.opts.multiplanarForceRender||e[4]>=E[4]?H=e:N=!1;const j=w[0]*H[4],y=w[1]*H[4],Y=w[2]*H[4],G=P*H[4];this.draw2D([H[0],H[1],j,y],0),this.draw2D([H[0],H[1]+y+D,j,Y],1),this.draw2D([H[0],H[1]+y+D+Y+D,y,Y],2),N&&this.draw3D([H[0],H[1]+y+Y+Y+D*3,G,G])}else if(e0){let H=L;this.opts.multiplanarForceRender||d[4]>=L[4]?H=d:N=!1;const j=w[0]*H[4],y=w[1]*H[4],Y=w[2]*H[4];this.draw2D([H[0],H[1],j,y],0),this.draw2D([H[0]+j+D,H[1],j,Y],1),this.draw2D([H[0]+j+j+D*2,H[1],y,Y],2),N&&this.draw3D([H[0]+j+j+y+D*3,H[1],H[3],H[3]])}else if(z){const H=R,j=w[0]*H[4],y=w[1]*H[4],Y=w[2]*H[4];this.draw2D([H[0],H[1]+Y+D,j,y],0),this.draw2D([H[0],H[1],j,Y],1),this.draw2D([H[0]+j+D,H[1],y,Y],2),N&&this.draw3D([H[0]+j+D,H[1]+Y+D,y,y])}}if(this.opts.isRuler&&this.drawRuler(),this.opts.isColorbar&&this.drawColorbar(),A&&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}};async function main(){async function T(R){await v.loadFromFile(R[0]),v.setColormap(v.volumes[2].id,"red"),lesionSlider.oninput()}function u(R,P,L){let d=L-P,E=(R-P)/d;return Math.min(Math.max(E,0),1)}function m(){if(v.volumes.length!==3){window.alert('Please reload the page and open lesions with the "Open Lesion Map" button');return}let R=v.volumes[1].img,P=v.volumes[2].img,L=P.length;if(L!==R.length){window.alert("Lesion must precisely match mask");return}let d=0,E=0;const e=5,N=Math.floor(w.length/e);let q=new Float64Array(L);for(let y=0;y0&&R[y]>0&&d++,R[y]>1&&(q[E++]=P[y]);if(N!==E){window.alert("PCA and mask have different number of elements");return}let z=new Float64Array(e);for(let y=0;y Niivue Neglect Predictions - + diff --git a/mask.nii.gz b/mask.nii.gz new file mode 100644 index 0000000..5700fda Binary files /dev/null and b/mask.nii.gz differ diff --git a/models_5x10_diff.mat b/models_5x10_diff.mat new file mode 100644 index 0000000..6f28513 Binary files /dev/null and b/models_5x10_diff.mat differ diff --git a/neglect_predict.m b/neglect_predict.m new file mode 100644 index 0000000..829c65b --- /dev/null +++ b/neglect_predict.m @@ -0,0 +1,100 @@ +function neglect_predict(fnm, acuteCoC) +%Predict chronic recovery based on lesion map and acute center of +%cancellation score. +%Examples +% neglect_predict; %use GUI; +% neglect_predict('M2095_lesion.nii.gz', 0.65) + if nargin < 1 + [p_file, p_path] = uigetfile('*.nii.gz;*.nii', 'Select lesion map'); + if p_file==0 + return + end + fnm = fullfile(p_path, p_file); + end + if nargin < 2 + prompt = {'Enter acute CoC score (-1..1):'}; + dlgtitle = 'Input'; + fieldsize = [1]; + definput = {'0.65'}; + answer = inputdlg(prompt,dlgtitle,fieldsize,definput); + if length(answer) == 0 + return + end + acuteCoC = str2double(answer{1}); + end + lesion = niftiread(fnm); + mpath = fileparts(mfilename("fullpath")); + fnmMsk = fullfile(mpath, 'mask.nii.gz'); + if ~exist(fnmMsk,'file') + error('Unable to find %s', fnmMsk) + end + maskVox012 = uint8(niftiread(fnmMsk)); + maskROI = uint8(maskVox012 > 0); + maskVox = uint8(maskVox012 > 1); + ROI_volVox = nnz(maskROI & lesion); + ROI_volML = ROI_volVox / 1000; %convert voxels to ML + fprintf("%d lesioned voxels in ROI mask: %g ml\n", ROI_volVox, ROI_volML); + %PCA + fnmPCA = fullfile(mpath, 'pca_values_5x21220.mat'); + if ~exist(fnmPCA,'file') + error('Unable to find %s', fnmPCA) + end + pca_val = load(fnmPCA).pca_val; + if size(pca_val.mu,2) ~= nnz(maskVox) + error('maskVox size does not match pca_val') + end + map_org = double(lesion(:)); + mask_log = maskVox ~=0; + map = map_org(mask_log); + PC = (map'-pca_val.mu)*pca_val.coeff; + if numel(PC) ~= 5 + error('scores_new should have 5 values') + end + %normalize values to range 0..1 + % -> PCs: min = -51.9073; max = 110.0535 + % -> CoC: min = -0.024243014; max = 0.951938077 + % -> ROI_vol: min = 0; max = 21.625 + for i = 1:5 + PC(i) = norm0to1(PC(i), -51.9073, 110.0535); + end + acuteCoC = norm0to1(acuteCoC, -0.024243014, 0.951938077); + ROI_volML = norm0to1(ROI_volML, 0, 21.625); + % input_vector = [PC1, PC2, PC3, PC4, PC5, CoC, ROI_vol]; + % Exemplary patient data (with outcome = 0.8155): + input_vector = [PC(1), PC(2), PC(3), PC(4), PC(5), acuteCoC, ROI_volML]; + fnmModel = fullfile(mpath, 'models_5x10_diff.mat'); + if ~exist(fnmPCA,'file') + error('Unable to find %s', fnmPCA) + end + models = load(fnmModel).models; + % Blank prediction-array + predictions_mdls = zeros(numel(models),1); + + % For each single model (5-fold nested cross validation x 10 model repetitions) + for i = 1:numel(models) + % Extract information from the i-th model + support_vectors_i = models{i}.SVs; + coefficients_i = models{i}.sv_coef; + bias_i = -models{i}.rho; + % Define RBF kernel function + gamma_i = models{i}.Parameters(4); + rbf_kernel = @(x1, x2) exp(-gamma_i * sum((x1 - x2).^2)); + % Calculate the kernel values + kernel_values_i = arrayfun(@(j) rbf_kernel(input_vector, support_vectors_i(j, :)), 1:size(support_vectors_i, 1)); + % Calculate the prediction using the regression function + predictions_mdls(i) = sum(coefficients_i' .* kernel_values_i) + bias_i; + % Feature weights and bias term + % w = coefficients_i' * support_vectors_i; + % b = bias_i; + end + % Calculate mean prediction + prediction_mean = mean(predictions_mdls); + disp(['Mean prediction: ' num2str(prediction_mean)]); +end +function ret = norm0to1(val, mn, mx) + %return normalized 0..1, linearly interpolated min..max + range = mx - mn; + ret = (val - mn)/range; + ret = min(max(ret,0),1); +end + diff --git a/pca_values_5x21220.mat b/pca_values_5x21220.mat new file mode 100644 index 0000000..8c838f7 Binary files /dev/null and b/pca_values_5x21220.mat differ diff --git a/pca_values_coeff.nii.gz b/pca_values_coeff.nii.gz new file mode 100644 index 0000000..a00910f Binary files /dev/null and b/pca_values_coeff.nii.gz differ diff --git a/pca_values_mu.nii.gz b/pca_values_mu.nii.gz new file mode 100644 index 0000000..ef9ddb9 Binary files /dev/null and b/pca_values_mu.nii.gz differ