Commit 5bd902b0 authored by roberto's avatar roberto

separate code into sections

parent c7e7185f
......@@ -4,19 +4,12 @@ public/project/braincatalogue-dev
node_modules
tmp
js/mghtest.js
js/orig.gz
js/orig.mgh
js/orig.mgz
js/orig.nii.gz
js/sample-001.mgz
js/sample-001.nii.gz
js/sample-002.mgz
js/sample-002.nii.gz
public/.DS_Store
var AtlasMakerDraw = {
resizeWindow: function resizeWindow() {
var me=AtlasMakerWidget;
var l=me.traceLog(resizeWindow,1);if(l)console.log(l);
var wH=me.container.height();
var wW=me.container.width();
var wAspect=wW/wH;
var bAspect=me.brain_W*me.brain_Wdim/(me.brain_H*me.brain_Hdim);
if(me.editMode==1) {
// In edit mode width or height can be fixed to 100%
// depending on the slice and container aspect ratio
if(wAspect>bAspect)
$('#resizable').css({width:(100*bAspect/wAspect)+'%',height:'100%'});
else
$('#resizable').css({width:'100%',height:(100*wAspect/bAspect)+'%'});
} else {
// In display mode slice width is always fixed to 100%
$('#resizable').css({width:'100%',height:(100*wAspect/bAspect)+'%'});
// Slice height cannot be larger than window's inner height:
var sliceH=me.container.height();
var windowH=window.innerHeight;
if(sliceH>windowH) {
var f=windowH/sliceH;
$('#resizable').css({width:(f*100)+'%',height:f*(100*wAspect/bAspect)+'%'});
}
}
},
configureBrainImage: function configureBrainImage() {
var me=AtlasMakerWidget;
var l=me.traceLog(configureBrainImage);if(l)console.log(l);
if(me.User.view==null)
me.User.view="sag";
var s2v=me.User.s2v;
switch(me.User.view) {
case 'sag': me.brain_W=s2v.sdim[1]; me.brain_H=s2v.sdim[2]; me.brain_D=s2v.sdim[0]; me.brain_Wdim=s2v.wpixdim[1]; me.brain_Hdim=s2v.wpixdim[2]; break; // sagital
case 'cor': me.brain_W=s2v.sdim[0]; me.brain_H=s2v.sdim[2]; me.brain_D=s2v.sdim[1]; me.brain_Wdim=s2v.wpixdim[0]; me.brain_Hdim=s2v.wpixdim[2]; break; // coronal
case 'axi': me.brain_W=s2v.sdim[0]; me.brain_H=s2v.sdim[1]; me.brain_D=s2v.sdim[2]; me.brain_Wdim=s2v.wpixdim[0]; me.brain_Hdim=s2v.wpixdim[1]; break; // axial
}
me.canvas.width=me.brain_W;
me.canvas.height=me.brain_H*me.brain_Hdim/me.brain_Wdim;
me.brain_offcn.width=me.brain_W;
me.brain_offcn.height=me.brain_H;
me.brain_px=me.brain_offtx.getImageData(0,0,me.brain_offcn.width,me.brain_offcn.height);
if(me.User.slice==null || me.User.slice>=me.brain_D)
me.User.slice=parseInt(me.brain_D/2);
me.sendUserDataMessage("configure brain image");
// configure toolbar slider
$(".slider#slice").data({max:me.brain_D,val:me.User.slice});
if($("#slice .thumb")[0]) $("#slice .thumb")[0].style.left=(me.User.slice/me.brain_D*100)+"%";
me.drawImages();
me.initCursor();
},
configureAtlasImage: function configureAtlasImage() {
var me=AtlasMakerWidget;
var l=me.traceLog(configureAtlasImage);if(l)console.log(l);
// has to be run *after* configureBrainImage
me.atlas_offcn.width=me.brain_W;
me.atlas_offcn.height=me.brain_H;
me.atlas_px=me.atlas_offtx.getImageData(0,0,me.atlas_offcn.width,me.atlas_offcn.height);
},
nearestNeighbour: function nearestNeighbour(ctx) {
var me=AtlasMakerWidget;
var l=me.traceLog(nearestNeighbour,1);if(l)console.log(l);
ctx.imageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
},
computeSegmentedVolume: function computeSegmentedVolume() {
var me=AtlasMakerWidget;
var l=me.traceLog(computeSegmentedVolume,1);if(l)console.log(l);
var i,sum=0;
var data=me.atlas.data;
var dim=me.atlas.dim;
for(i=0;i<dim[0]*dim[1]*dim[2];i++) {
if(data[i]>0)
sum++;
}
return sum*me.User.pixdim[0]*me.User.pixdim[1]*me.User.pixdim[2];
},
displayInformation: function displayInformation() {
var me=AtlasMakerWidget;
var l=me.traceLog(displayInformation,1);if(l)console.log(l);
me.info.slice=me.User.slice;
var i=0,info=me.container.find("#info");
var str="";
for(var k in me.info) {
str+="<text x='5' y='"+(15+15*i++)+"' fill='white'>"+k+": "+me.info[k]+"</text>";
}
if(me.User.measureLength) {
var W=parseFloat($('#atlasMaker canvas').css('width'));
var w=parseFloat($('#atlasMaker canvas').attr('width'));
var zx=W/w,zy=zx*me.brain_Hdim/me.brain_Wdim,p=me.User.measureLength,str1;
var W=parseFloat($('#atlasMaker canvas').css('width'));
var w=parseFloat($('#atlasMaker canvas').attr('width'));
str1="M"+zx*p[0].x+","+zy*p[0].y;
for(i=1;i<p.length;i++)
str1+="L"+zx*p[i].x+","+zy*p[i].y;
str+=[ "<circle fill='#00ff00' cx="+zx*p[0].x+" cy="+zy*p[0].y+" r=3 />",
"<path stroke='#00ff00' fill='none' d='"+str1+"'/>",
(i>0)?"<circle fill='#00ff00' cx="+zx*p[i-1].x+" cy="+zy*p[i-1].y+" r=3 />":""].join("\n");
}
info.html(str);
},
drawImages: function drawImages() {
var me=AtlasMakerWidget;
var l=me.traceLog(drawImages,1);if(l)console.log(l);
if(me.brain_img.img) {
me.context.clearRect(0,0,me.context.canvas.width,me.canvas.height);
me.displayInformation();
me.nearestNeighbour(me.context);
me.context.drawImage(me.brain_img.img,0,0,me.brain_W,me.brain_H*me.brain_Hdim/me.brain_Wdim);
me.drawAtlasImage(me.flagLoadingImg.view,me.flagLoadingImg.slice);
}
if(!me.brain_img.img || me.brain_img.view!=me.User.view || me.brain_img.slice!=me.User.slice) {
me.sendRequestSliceMessage();
}
},
drawAtlasImage: function drawAtlasImage(view,slice) {
var me=AtlasMakerWidget;
var l=me.traceLog(drawAtlasImage,1);if(l)console.log(l);
if(!me.atlas)
return;
var data=me.atlas.data;
var dim=me.atlas.dim;
var s,val;
ys=yc=ya=slice;
for(y=0;y<me.brain_H;y++)
for(x=0;x<me.brain_W;x++) {
switch(view) {
case 'sag':s=[ys,x,me.brain_H-1-y]; break;
case 'cor':s=[x,yc,me.brain_H-1-y]; break;
case 'axi':s=[x,me.brain_H-1-y,ya]; break;
}
i=me.S2I(s,me.User);
var c=me.ontologyValueToColor(data[i]);
var alpha=(data[i]>0)?255:0;
i=(y*me.atlas_offcn.width+x)*4;
me.atlas_px.data[ i ] =c[0];
me.atlas_px.data[ i+1 ]=c[1];
me.atlas_px.data[ i+2 ]=c[2];
me.atlas_px.data[ i+3 ]=alpha*me.alphaLevel;
}
me.atlas_offtx.putImageData(me.atlas_px, 0, 0);
me.nearestNeighbour(me.context);
me.context.drawImage(me.atlas_offcn,0,0,me.brain_W,me.brain_H*me.brain_Hdim/me.brain_Wdim);
}
};
This diff is collapsed.
var AtlasMakerIO = {
encodeNifti: function encodeNifti() {
var me=AtlasMakerWidget;
var l=me.traceLog(encodeNifti);if(l)console.log(l);
var sizeof_hdr=348;
var dimensions=4; // number of dimension values provided
var spacetimeunits=2+8; // 2=nifti code for millimetres | 8=nifti code for seconds
var datatype=2; // datatype for 8 bits (DT_UCHAR8 in nifti or UCHAR in analyze)
var voxel_offset=348;
var hdr=new ArrayBuffer(sizeof_hdr);
var dv=new DataView(hdr);
dv.setInt32(0,sizeof_hdr,true);
dv.setInt16(40,dimensions,true);
dv.setInt16(42,me.brain_dim[0],true);
dv.setInt16(44,me.brain_dim[1],true);
dv.setInt16(46,me.brain_dim[2],true);
dv.setInt16(48,1,true);
dv.setInt16(70,datatype,true);
dv.setInt16(72,8,true); // bits per voxel
// dv.setInt16(72,datatype,true);
// dv.setInt16(74,8,true); // bits per voxel
dv.setFloat32(76,1,true); // first pixdim value
dv.setFloat32(80,me.brain_pixdim[0],true);
dv.setFloat32(84,me.brain_pixdim[1],true);
dv.setFloat32(88,me.brain_pixdim[2],true);
dv.setFloat32(108,voxel_offset,true);
dv.setInt8(123,spacetimeunits);
var data=me.atlas.data;
var i;
var nii = new Uint8Array(voxel_offset+data.length);
for(i=0;i<sizeof_hdr;i++)
nii[i]=dv.getUint8(i);
for(i=0;i<data.length;i++)
nii[i+voxel_offset]=data[i];
var niigz=new pako.Deflate({gzip:true});
niigz.push(nii,true);
return niigz.result;
},
saveNifti: function saveNifti() {
var me=AtlasMakerWidget;
var l=me.traceLog(saveNifti);if(l)console.log(l);
var niigz=me.encodeNifti();
var niigzBlob = new Blob([niigz]);
$("a#download_atlas").attr("href",window.URL.createObjectURL(niigzBlob));
$("a#download_atlas").attr("download",me.User.atlasFilename);
},
loadNifti: function loadNifti(nii) {
var me=AtlasMakerWidget;
var l=me.traceLog(loadNifti,1);if(l)console.log(l);
var dv=new DataView(nii);
var vox_offset=352;
var sizeof_hdr=dv.getInt32(0,true);
var dimensions=dv.getInt16(40,true);
var mri={};
mri.hdr=nii.slice(0,vox_offset);
mri.dim=[];
mri.dim[0]=dv.getInt16(42,true);
mri.dim[1]=dv.getInt16(44,true);
mri.dim[2]=dv.getInt16(46,true);
mri.datatype=dv.getInt16(70,true);
mri.pixdim=[];
mri.pixdim[0]=dv.getFloat32(80,true);
mri.pixdim[1]=dv.getFloat32(84,true);
mri.pixdim[2]=dv.getFloat32(88,true);
vox_offset=dv.getFloat32(108,true);
switch(mri.datatype)
{
case 2: // UCHAR
mri.data=new Uint8Array(nii,vox_offset);
break;
case 4: // SHORT
mri.data=new Int16Array(nii,vox_offset);
break;
case 8: // INT
mri.data=new Int32Array(nii,vox_offset);
break;
case 16: // FLOAT
mri.data=new Float32Array(nii,vox_offset);
break;
default:
console.log("ERROR: Unknown dataType: "+mri.datatype);
}
return mri;
},
/*
{Linear algebra
*/
computeS2VTransformation: function computeS2VTransformation() {
var me=AtlasMakerWidget;
var l=me.traceLog(computeS2VTransformation);if(l)console.log(l);
var v2w=me.User.v2w;
var wori=me.User.wori;
var wpixdim=me.subVecVec(me.mulMatVec(v2w,[1,1,1]),me.mulMatVec(v2w,[0,0,0]));
var wvmax=me.addVecVec(me.mulMatVec(v2w,me.User.dim),wori);
var wvmin=me.addVecVec(me.mulMatVec(v2w,[0,0,0]),wori);
var wmin=[Math.min(wvmin[0],wvmax[0]),Math.min(wvmin[1],wvmax[1]),Math.min(wvmin[2],wvmax[2])];
var wmax=[Math.max(wvmin[0],wvmax[0]),Math.max(wvmin[1],wvmax[1]),Math.max(wvmin[2],wvmax[2])];
var w2s=[[1/Math.abs(wpixdim[0]),0,0],[0,1/Math.abs(wpixdim[1]),0],[0,0,1/Math.abs(wpixdim[2])]];
var s2w=me.invMat(w2s);
console.log(["v2w",v2w, "wori",wori, "wpixdim",wpixdim, "wvmax",wvmax, "wvmin",wvmin, "wmin",wmin, "wmax",wmax, "w2s",w2s]);
me.User.s2v = {
sdim: [(wmax[0]-wmin[0])/Math.abs(wpixdim[0]),(wmax[1]-wmin[1])/Math.abs(wpixdim[1]),(wmax[2]-wmin[2])/Math.abs(wpixdim[2])],
s2w: s2w,
sori: [-wmin[0]/Math.abs(wpixdim[0]),-wmin[1]/Math.abs(wpixdim[1]),-wmin[2]/Math.abs(wpixdim[2])],
wpixdim: [Math.abs(wpixdim[0]),Math.abs(wpixdim[1]),Math.abs(wpixdim[2])],
w2v: me.invMat(v2w),
wori: wori
};
},
testS2VTransformation: function testS2VTransformation() {
var me=AtlasMakerWidget;
var l=me.traceLog(testS2VTransformation);if(l)console.log(l);
/*
check the S2V transformation to see if it looks correct.
If it does not, reset it
*/
var mri=me.User; // this line is different from server
var doReset=false;
console.log("Transformation TEST:");
console.log(" 1. transformation volume");
var vv=mri.dim[0]*mri.dim[1]*mri.dim[2];
var vs=mri.s2v.sdim[0]*mri.s2v.sdim[1]*mri.s2v.sdim[2];
var diff=(vs-vv)/vv;
if(Math.abs(diff)>0.001) {
console.log(" ERROR: Difference is too large");
console.log(" original volume:",vv);
console.log(" rotated volume:",vs);
console.log(" % difference:",diff*100);
doReset=true;
} else {
console.log(" ok.");
}
console.log(" 2. transformation origin");
if( mri.s2v.sori[0]<0||mri.s2v.sori[0]>mri.s2v.sdim[0] ||
mri.s2v.sori[1]<0||mri.s2v.sori[1]>mri.s2v.sdim[1] ||
mri.s2v.sori[2]<0||mri.s2v.sori[2]>mri.s2v.sdim[2]) {
console.log(" Origin point is outside the dimensions of the data");
doReset=true;
} else {
console.log(" ok.");
}
if(doReset) {
console.log("THE TRANSFORMATION WILL BE RESET");
mri.v2w=[[mri.pixdim[0],0,0],[0,-mri.pixdim[1],0],[0,0,-mri.pixdim[2]]];
mri.wori=[0,mri.dim[1],mri.dim[2]];
// re-compute the transformation from voxel space to screen space
me.computeS2VTransformation(); // this line is different from server
console.log(mri.dir);
console.log(mri.ori);
console.log(mri.s2v);
}
},
S2I: function S2I(s,mri) {
var me=AtlasMakerWidget;
var l=me.traceLog(S2I,3);if(l)console.log(l);
var s2v=mri.s2v;
var i=null,w,s,v;
w=me.mulMatVec(s2v.s2w,me.subVecVec(s,s2v.sori)); // screen to world: w=s2w*(s-sori)
v=me.mulMatVec(s2v.w2v,me.subVecVec(w,mri.wori)); // world to voxel
v=[Math.round(v[0]),Math.round(v[1]),Math.round(v[2])]; // round to integer
if(v[0]>=0&&v[0]<mri.dim[0]&&v[0]>=0&&v[0]<mri.dim[0]&&v[0]>=0&&v[0]<mri.dim[0])
i= v[2]*mri.dim[1]*mri.dim[0]+ v[1]*mri.dim[0] +v[0];
return i;
},
mulMatVec: function mulMatVec(m,v) {
return [
m[0][0]*v[0]+m[0][1]*v[1]+m[0][2]*v[2],
m[1][0]*v[0]+m[1][1]*v[1]+m[1][2]*v[2],
m[2][0]*v[0]+m[2][1]*v[1]+m[2][2]*v[2]
];
},
invMat: function invMat(m) {
var det;
var w=[[],[],[]];
det=m[0][1]*m[1][2]*m[2][0] + m[0][2]*m[1][0]*m[2][1] + m[0][0]*m[1][1]*m[2][2] - m[0][2]*m[1][1]*m[2][0] - m[0][0]*m[1][2]*m[2][1] - m[0][1]*m[1][0]*m[2][2];
w[0][0]=(m[1][1]*m[2][2] - m[1][2]*m[2][1])/det;
w[0][1]=(m[0][2]*m[2][1] - m[0][1]*m[2][2])/det;
w[0][2]=(m[0][1]*m[1][2] - m[0][2]*m[1][1])/det;
w[1][0]=(m[1][2]*m[2][0] - m[1][0]*m[2][2])/det;
w[1][1]=(m[0][0]*m[2][2] - m[0][2]*m[2][0])/det;
w[1][2]=(m[0][2]*m[1][0] - m[0][0]*m[1][2])/det;
w[2][0]=(m[1][0]*m[2][1] - m[1][1]*m[2][0])/det;
w[2][1]=(m[0][1]*m[2][0] - m[0][0]*m[2][1])/det;
w[2][2]=(m[0][0]*m[1][1] - m[0][1]*m[1][0])/det;
return w;
},
subVecVec: function subVecVec(a,b) {
return [a[0]-b[0],a[1]-b[1],a[2]-b[2]];
},
addVecVec: function addVecVec(a,b) {
return [a[0]+b[0],a[1]+b[1],a[2]+b[2]];
},
/*
Linear Algebra}
*/
};
\ No newline at end of file
var AtlasMakerPaint = {
//====================================================================================
// Paint functions common to all users
//====================================================================================
paintxy: function paintxy(u,c,x,y,usr) {
var me=AtlasMakerWidget;
var l=me.traceLog(paintxy,1);if(l)console.log(l);
// u: user number
// c: command
// x, y: coordinates
msg={"c":c,"x":x,"y":y};
if(u==-1 && JSON.stringify(msg)!=JSON.stringify(me.msg0)) {
me.sendPaintMessage(msg);
me.msg0=msg;
}
var dim=me.atlas.dim;
var coord={"x":x,"y":y,"z":usr.slice};
if(usr.x0<0) {
usr.x0=coord.x;
usr.y0=coord.y;
}
var val=usr.penValue;
switch(c) {
case 'le':
me.line(coord.x,coord.y,0,usr);
break;
case 'lf':
me.line(coord.x,coord.y,val,usr);
break;
case 'e':
me.fill(coord.x,coord.y,coord.z,0,usr.view);
break;
case 'f':
me.fill(coord.x,coord.y,coord.z,val,usr.view);
break;
}
usr.x0=coord.x;
usr.y0=coord.y;
},
paintvol: function paintvol(voxels) {
/* this function is exclusively used for undoing */
var me=AtlasMakerWidget;
var l=me.traceLog(paintvol);if(l)console.log(l);
var i,
ind, // voxel index
val; // voxel delta-value, such that -=val undoes
for(i=0;i<voxels.length;i++) {
ind=voxels[i][0];
val=voxels[i][1];
me.atlas.data[ind]=val;
}
me.drawImages();
},
fill: function fill(x,y,z,val,myView) {
var me=AtlasMakerWidget;
var l=me.traceLog(fill);if(l)console.log(l);
var Q=[],n;
var atlas=me.atlas;
var dim=atlas.dim;
var i;
var bval=atlas.data[me.slice2index(x,y,z,myView)]; // background-value: value of the voxel where the click occurred
if(bval==val) // nothing to do
return;
Q.push({"x":x,"y":y});
while(Q.length>0) {
n=Q.pop();
x=n.x;
y=n.y;
if(atlas.data[me.slice2index(x,y,z,myView)]==bval) {
atlas.data[me.slice2index(x,y,z,myView)]=val;
if(x-1>=0 && atlas.data[me.slice2index(x-1,y,z,myView)]==bval)
Q.push({"x":x-1,"y":y});
if(x+1<me.brain_W && atlas.data[me.slice2index(x+1,y,z,myView)]==bval)
Q.push({"x":x+1,"y":y});
if(y-1>=0 && atlas.data[me.slice2index(x,y-1,z,myView)]==bval)
Q.push({"x":x,"y":y-1});
if(y+1<me.brain_H && atlas.data[me.slice2index(x,y+1,z,myView)]==bval)
Q.push({"x":x,"y":y+1});
}
}
me.drawImages();
},
line: function line(x,y,val,usr) {
var me=AtlasMakerWidget;
var l=me.traceLog(line,1);if(l)console.log(l);
// Bresenham's line algorithm adapted from
// http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript
var atlas=me.atlas;
var dim=atlas.dim;
var xyzi1=new Array(4);
var xyzi2=new Array(4);
var i;
var x1=usr.x0;
var y1=usr.y0;
var x2=x;
var y2=y;
var z=usr.slice;
// Define differences and error check
var dx = Math.abs(x2 - x1);
var dy = Math.abs(y2 - y1);
var sx = (x1 < x2) ? 1 : -1;
var sy = (y1 < y2) ? 1 : -1;
var err = dx - dy;
xyzi1=me.slice2xyzi(x1,y1,z,usr.view);
xyzi2=me.slice2xyzi(x2,y2,z,usr.view);
me.annotationLength+=Math.sqrt( Math.pow(me.brain_pixdim[0]*(xyzi1[0]-xyzi2[0]),2)+
Math.pow(me.brain_pixdim[1]*(xyzi1[1]-xyzi2[1]),2)+
Math.pow(me.brain_pixdim[2]*(xyzi1[2]-xyzi2[2]),2));
for(j=0;j<usr.penSize;j++)
for(k=0;k<usr.penSize;k++) {
i=me.slice2index(x1+j,y1+k,z,usr.view);
atlas.data[i]=val;
}
while (!((x1 == x2) && (y1 == y2))) {
var e2 = err << 1;
if (e2 > -dy) {
err -= dy;
x1 += sx;
}
if (e2 < dx) {
err += dx;
y1 += sy;
}
for(j=0;j<usr.penSize;j++)
for(k=0;k<usr.penSize;k++) {
i=me.slice2index(x1+j,y1+k,z,usr.view);
atlas.data[i]=val;
}
}
me.drawImages();
},
slice2index: function slice2index(mx,my,mz,myView) {
var me=AtlasMakerWidget;
var l=me.traceLog(slice2index,3);if(l)console.log(l);
var dim=me.atlas.dim;
var x,y,z,i;
switch(myView) {
case 'sag': x=mz; y=mx; z=me.brain_H-1-my;break; // sagital
case 'cor': x=mx; y=mz; z=me.brain_H-1-my;break; // coronal
case 'axi': x=mx; y=me.brain_H-1-my; z=mz;break; // axial
}
/*
TRANSFORM SCREEN SPACE INTO VOXEL INDEX
*/
var s=[x,y,z];
i=me.S2I(s,me.User);
/*
i=z*dim[1]*dim[0]+y*dim[0]+x;
*/
return i;
},
slice2xyzi: function slice2xyzi(mx,my,mz,myView) {
var me=AtlasMakerWidget;
var l=me.traceLog(slice2xyzi,1);if(l)console.log(l);
var dim=me.atlas.dim;
var x,y,z,i;
switch(myView) {
case 'sag': x=mz; y=mx; z=me.brain_H-1-my;break; // sagital
case 'cor': x=mx; y=mz; z=me.brain_H-1-my;break; // coronal
case 'axi': x=mx; y=me.brain_H-1-my; z=mz;break; // axial
}
/*
TRANSFORM SCREEN SPACE INTO VOXEL INDEX
*/
var s=[x,y,z];
i=me.S2I(s,me.User);
/*
i=z*dim[1]*dim[0]+y*dim[0]+x;
*/
return [x,y,z,i];
}
};
var AtlasMakerUI = {
slider: function slider(elem,callback) {
var me=AtlasMakerWidget;
var l=me.traceLog(slider,2);if(l)console.log(l);
// Initialise a 'slider' control
$(elem).data({
drag:false,