@galaxyproject/nora
Version:
NORA Medical Imaging Viewer
1,793 lines (1,423 loc) • 194 kB
JavaScript
// ======================================================================================
// ======================================================================================
// ============= KObject3DTool
// ======================================================================================
// ======================================================================================
function KObject3DTool(master)
{
/** objects that are typically looked in 3D like fiber tracts/surfaces/ccmats are controlled from here
* @class
* @alias KObject3DTool
* @augments KToolWindow
*/
var that = new KToolWindow(master,
$("<div class='KView_tool '><i class='fa fa-cubes fa-1x'></i></div>")
.append( $("<ul class='KView_tool_menu'></ul>").append($("<li>Objects 3D</li>")) ) );
that.$topRow.addClass("Obj3DTool_topmenu")
var fibertool = master.obj3dTool;
that.tracking_panel = KTrackingPanel();
that.tracking_panel.toggle();
that.visitmap_res = 2;
that.termmap_res = 2;
that.termmap_len = 2;
that.name = 'Objects 3D';
that.attach_helper(function(){
window.open("https://www.nora-imaging.org/doc/books/nora-documentation/page/fiber-viewer",'_blank');
})
// the list of 3D objects as key/value pairs
that.objs = {};
// the top menu
var $menu = $("<ul class='KView_tool_menu'></ul>");
that.$topRow.append( $("<li ><a>Objects 3D</a></li>").append($menu) );
$menu.append($("<li><a>Object Statistics</a> </i></li>").click(function()
{
that.statdlg.toggle();
that.statdlg.dostats();
}
));
that.statdlg = statistics_dialog(that);
// that tool table
var $innerDIV = $("<div ondragover='event.preventDefault();' class='annotation_tool_listDIV'></div>").appendTo(that.$container);
var $table = $("<table class='localfiletable'></table>").appendTo($innerDIV);
// drop handler
$innerDIV.on("drop",function(e)
{
e.preventDefault();
var params = getloadParamsFromDrop(e.originalEvent,undefined);
if (params.length > 0)
{
params[0].progressSpinner = that.progressSpinner;
params[0].callback = that.hideSpinner;
master.dataManager.loadData(params[0]);
}
});
// resize handler
that.resize = function(hei)
{
that.$container.height(hei);
$innerDIV.height(hei-that.$container.find('.KToolsTopMenu').height());
}
/***************************************************************************************
* object management
****************************************************************************************/
that.addObject = function (fileObject)
{
that.objs[fileObject.fileID] = fileObject;
that.update();
}
that.clearAll = function ()
{
var obs = Object.keys(that.objs);
for (var k = 0; k< obs.length;k++)
delete that.objs[obs[k]].content;
KViewer.obj3dTool.objs = {};
KViewer.obj3dTool.update();
}
that.uid_cnt = 0;
that.cloneFibersFromSelection = function (tck,viewer,parent,name,color)
{
var fobj = tck.fibers;
var children;
if (tck.isParentView)
children = tck.children;
else
children = tck.parent.children;
if (tck.trackingVol == undefined)
{
if (fobj.content.selections == undefined)
fobj.content.selections =[];
var max = 0;
for (var k = 0 ; k < children.length;k++)
{
if (children[k].Selection != undefined)
if (children[k].Selection.name.substring(0,9) == 'selection')
max = Math.max(max,parseInt(children[k].Selection.name.substring(9)));
}
if (name == undefined)
name = 'selection' + (max+1);
fobj.content.selections.push({subset: tck.subsetToDisplay, name:name, signs:tck.fiberSign});
that.update();
return that.createFiberView(fobj,viewer,{select: tck.fibers.content.selections.length-1 , parent:parent, color:color,donotmakecurrent:true,visible:true});
}
else
{
var fobj_ = {content:{tracts : fobj.content.tracts,
tracts_max : fobj.content.tracts_max,
tracts_min : fobj.content.tracts_min,
tracts_len : fobj.content.tracts_len,
tot_points : fobj.content.tot_points,
max : fobj.content.max,
min : fobj.content.min } };
var max = 0;
for (var k = 0 ; k < children.length;k++)
{
if (children[k].fibers.filename.substring(0,9) == 'selection')
max = Math.max(max,parseInt(children[k].fibers.filename.substring(9)));
}
if (name == undefined)
name = 'selection' + (max+1);
that.buildOctree(fobj_.content,that.progressSpinner);
fobj_.filename = name;
var intent = {parent:parent, donotmakecurrent:true}
// if (typeof color == "number")
intent.color = color;
if (tck.subsetToDisplay != undefined)
{
fobj_.content.selections = [{subset: tck.subsetToDisplay, name:name}];
intent.select = 0;
}
that.update();
return that.createFiberView(fobj_,viewer,intent);
}
}
/***************************************************************************************
* table updater
****************************************************************************************/
that.update = function()
{
$table.children().remove();
var $thead = $("<thead>").appendTo($table);
var $row = $("<tr class='filecache'></tr>").appendTo($thead);
$row.append($("<td class='fixedwidth' fixedwidth='6'><i class='fa fa-fw fa-square-o'></i> </td>"));
$row.append($("<td>name </td>"));
$row.append($("<td>type</td>"));
$row.append($("<td class='fixedwidth' fixedwidth='6'></td>"));
$row.append($("<td class='fixedwidth' fixedwidth='6'></td>"));
$row.append($("<td>info</td>"));
var $tbody = $("<tbody>").appendTo($table);
for (var k in that.objs)
{
var id = that.objs[k].fileID;
var dragstuff = "draggable='true' data-type='file' data-filename='"+that.objs[k].filename+"' data-fileID='"+that.objs[k].fileID+"' data-mime='tracts'";
dragstuff = dragstuff + " ondragstart='setdragstart(event);' ondragend='setdragend(event);' ondblclick='loadDataOndblClick(event);'";
var $row = $("<tr class='maintck' " + dragstuff + "></tr>").appendTo($tbody);
$row.append($("<td><i class='fa fa-fw fa-circle-o'></i> </td>").click(function(e){
var toselect = $(e.target).parent();
if (!$(e.target).parent().is("tr"))
toselect = toselect.parent();
toselect = toselect.nextAll();
for (var k = 0; k < toselect.length;k++)
{
var to = $(toselect[k]).find(".fa-square-o,.fa-check-square-o");
if (to.length > 0)
toggle_file(to);
else
break;
}
return false;
}));
$row.append($("<td >" + that.objs[k].filename + "</td>"));
$row.append($("<td>" + that.objs[k].contentType + "</td>"));
if (that.objs[k].contentType == 'tracts' & that.objs[k].content.selections != undefined)
$row.append($("<td> <i class='fa tablebutton fa-fw fa-save'></td>").click(function(t) { return function() { that.save(t) } }(that.objs[k])));
//if (that.objs[k].contentType == 'gii' && that.objs[k].content.nifti != undefined && that.objs[k].content.nifti.labels == undefined )
// $row.append($("<td> <i class='fa fa-fw fa-refresh'></td>").click(function(t) { return function() {
// that.computeIsoSurf(t);
// for (var j=0;j < t.content.update.length;j++) t.content.update[j]();
// } }(that.objs[k])));
$row.append($("<td> <i class='tablebutton fa fa-fw fa-close'></td>").click(function(k) {return function(ev){
ignoreDblClickBeforeClose(ev);
ev.preventDefault();
if (that.objs[k].fileinfo.roireference != undefined)
delete that.objs[k].fileinfo.roireference.fileinfo.surfreference;
KViewer.iterateMedViewers(function (medViewer) {
for (var i = 0; i < medViewer.objects3D.length; i++)
if (medViewer.objects3D[i].surf == that.objs[k])
medViewer.objects3D[i].close();
});
delete that.objs[k];
that.update();
} }(k) ));
if (that.objs[k].contentType == 'tracts')
$row.append($("<td > tracts:" + that.objs[k].content.tracts.length+ "</td>"));
if (that.objs[k].content.Contours != undefined)
{
for (var j = 0; j < that.objs[k].content.Contours.length;j++)
{
var selection = that.objs[k].content.Contours[j];
var dragstuff = "draggable='true' data-type='file' data-filename='tck:"+selection.name+"' data-fileID='"+that.objs[k].fileID+"' data-mime='contour' data-intent='select:"+j+"'";
dragstuff += " ondragstart='setdragstart(event);' ondragend='setdragend(event);' ondblclick='loadDataOndblClick(event);'";
var $row = $("<tr class='filecache' " + dragstuff + "'></tr>").appendTo($table);
$row.append($("<td><i class='fa fa-fw fa-square-o'></i> </td>").click(function(e){ toggle_file(e.target); return false; }));
$row.on("contextmenu", function (ev) { formContextMenu(ev); });
var $namediv = $("<td>" + selection.name + "</td>");
$row.append($namediv);
$row.append($("<td> contour </td>"));
$row.append($("<td> <i class='fa tablebutton fa-fw fa-close'></td>").click(function(k,j) {return function(ev){
ignoreDblClickBeforeClose(ev);
ev.preventDefault();
that.objs[k].content.selections.splice(j,1);
that.update();
} }(k,j) ));
$row.click(function(ev){
if (ev.ctrlKey)
toggle_file(ev.target);
})
}
}
if (that.objs[k].content.selections != undefined)
{
var list_to_sort = []
for (var j = 0; j < that.objs[k].content.selections.length;j++)
list_to_sort.push({id:j,name:that.objs[k].content.selections[j].name})
list_to_sort.sort(function(a,b) {return (a.name > b.name)?1:-1 })
for (var j_ = 0; j_ < that.objs[k].content.selections.length;j_++)
{
var j = list_to_sort[j_].id;
var selection = that.objs[k].content.selections[j];
var dragstuff = "draggable='true' data-type='file' data-filename='tck:"+selection.name+"' data-fileID='"+that.objs[k].fileID+"' data-mime='tracts' data-intent='select:"+j+"'";
dragstuff += " ondragstart='setdragstart(event);' ondragend='setdragend(event);' ondblclick='loadDataOndblClick(event);'";
var $row = $("<tr class='filecache' " + dragstuff + "'></tr>").appendTo($table);
$row.append($("<td><i class='fa fa-fw fa-square-o'></i> </td>").click(function(e){ toggle_file(e.target); return false; }));
$row.on("contextmenu", function (ev) { formContextMenu(ev); });
var $namediv = $("<td>" + selection.name + "</td>");
$row.append($namediv);
if (selection.namedivs == undefined)
selection.namedivs = {};
selection.namedivs.manager = $namediv;
$namediv.keydown(function(ev) { if (ev.keyCode == 13) { $(ev.target).blur(); return false } })
.keyup( function(sel) { return function(ev)
{
sel.name = $(ev.target).text();
if (sel.namedivs != undefined)
{
for (var i in sel.namedivs)
{
//if (sel.namedivs[i].constructor.name == 'm')
$(sel.namedivs[i]).text(sel.name);
}
}
} }(selection)
).on('blur', that.update);
makeEditableOnDoubleClick($namediv);
$row.append($("<td> tractselection </td>"));
$row.append($("<td> <i class='fa tablebutton fa-fw fa-save'></td>").click(function(t,s) { return function() { that.save(t,s) } }(that.objs[k],selection)));
$row.append($("<td> <i class='fa tablebutton fa-fw fa-close'></td>").click(function(k,j) {return function(ev){
ignoreDblClickBeforeClose(ev);
ev.preventDefault();
that.objs[k].content.selections.splice(j,1);
that.update();
} }(k,j) ));
$row.click(function(ev){
if (ev.ctrlKey)
toggle_file(ev.target);
})
if (that.objs[k].contentType == 'tracts')
$row.append($("<td > tracts:" +that.objs[k].content.selections[j].subset.length+ "</td>"));
}
}
}
that.attachTableOperator($table.parent());
}
function toggle_file(target)
{
if (!$(target).hasClass("fa"))
target = $(target).parent().find(".fa");
toggle(target);
}
function toggle(target)
{
if (target.length > 1)
target = target[0];
$(target).toggleClass("fa-square-o");
$(target).toggleClass("fa-check-square-o");
$(target).parent().parent().toggleClass("selected");
}
that.saveTCK = function(tck,callback)
{
var fibers = tck.fibers;
var content = [fibers.content];
var tot_points = content[0].tot_points;
var tracts_length = content[0].tracts.length
if (tck.children != undefined && tck.children.length > 1)
{
content = [];
tot_points = 0;
tracts_length = 0;
for (var k = 0; k < tck.children.length;k++)
{
if (tck.children[k].Selection && tck.children[k].Selection.subset)
{
var sset =tck.children[k].Selection.subset;
var tmp = [];
var tmp_tp = 0;
for (var i = 0; i<sset.length;i++)
{
var t = tck.children[k].fibers.content.tracts[sset[i]];
tmp.push(t);
tmp_tp += t.length/3;
}
content.push({tracts:tmp})
tracts_length += sset.length;
tot_points += tmp_tp
}
else
{
content.push(tck.children[k].fibers.content)
tot_points += tck.children[k].fibers.content.tot_points;
tracts_length += tck.children[k].fibers.content.tracts.length;
}
}
}
else if (tck.Selection != undefined && tck.Selection.subset && tck.Selection.subset.length>0)
{
content = [];
tot_points = 0;
tracts_length = 0;
var sset = tck.Selection.subset
var tmp = [];
var tmp_tp = 0;
for (var i = 0; i<sset.length;i++)
{
var t = fibers.content.tracts[sset[i]];
tmp.push(t);
tmp_tp += t.length/3;
}
content.push({tracts:tmp})
tracts_length += sset.length;
tot_points += tmp_tp
}
var utf8encoder = new TextEncoder()
var hdr = "mrtrix tracks\ndatatype: Float32LE\ncount: "+ tracts_length + "\nfile: . 1024\nEND";
var buf = new ArrayBuffer(1024+ ((tot_points*4)*3 + tracts_length*4*3 ));
var uint8 = new Uint8Array(buf)
var hdrbuf = utf8encoder.encode(hdr);
uint8.set(hdrbuf);
var nanbuf = new Float32Array(3)
nanbuf[0] = NaN
nanbuf[1] = NaN
nanbuf[2] = NaN
var offs = 1024
for (var j = 0; j < content.length;j++)
for (var k = 0; k < content[j].tracts.length;k++)
{
uint8.set(new Uint8Array(content[j].tracts[k].buffer),offs);
offs = offs + content[j].tracts[k].buffer.byteLength;
uint8.set(new Uint8Array(nanbuf.buffer),offs);
offs = offs + nanbuf.buffer.byteLength;
}
fibers.content.buffer = buf;
if (fibers.fileinfo == undefined)
fibers.fileinfo = {}
fibers.fileID = "TCK_1"
fibers.filename = fibers.filename.replace("\.tck","")
fibers.filename = fibers.filename + ".tck"
fibers.contentType = "tracts"
fibers.fileinfo.Tag = "";
// add a unique patient id first if not set
if (fibers.fileinfo.patients_id == undefined)
extendWithUniquePSID(fibers.fileinfo);
var zipped = false;
updateTag(fibers.fileinfo,[],userinfo.username)
if (fibers.fileinfo.patients_id != undefined)
{
KViewer.dataManager.setFile(fibers.fileID,fibers);
uploadUnregisteredBinary(fibers, {
permission: "rwp",
}, that.progressSpinner,
function(newid, id) {
KViewer.dataManager.delFile(fibers.fileID,true)
if (callback)
callback();
},zipped);
}
else
{
alertify.alert("There is no unique patient id set for this file.")
}
var selections = [];
var offs = 0;
for (var j = 0; j < tck.children.length;j++)
{
var num_fibs = content[j].tracts.length;
var subset = [];
for (var k = 0; k < num_fibs;k++)
subset.push(k+offs);
selections.push({subset:subset, name: tck.children[j].Selection.name, color:tck.children[j].color})
offs += num_fibs;
}
return selections;
}
// save a selection as json
that.save = function(fibers,selection,donotupload,name)
{
var sels;
if (selection != undefined)
{
if (Array.isArray(selection))
sels = selection
else
sels = [selection];
}
else
sels = fibers.content.selections;
var csels = [];
if (sels != undefined)
{
for (var k = 0; k < sels.length; k++)
{
csels[k]= $.extend(false,{},sels[k]);
csels[k].namedivs = undefined;
delete csels[k].namedivs;
}
}
var fileinfo = fibers.fileinfo;
if (fibers.tckjsonref != undefined)
fileinfo = fibers.tckjsonref.fileinfo;
var obj = {assoc: {fileID:fibers.fileID,filename:fibers.filename,subfolder:fibers.fileinfo.SubFolder,filepath:fibers.fileinfo.FilePath,md5:fibers.content.md5},
selections: csels };
if (donotupload != undefined && donotupload == true)
return obj;
function saveit(name,finfo)
{
that.lastSaveName = name;
finfo.tag = 'TCKSEL';
name = spliceSubFolder(name,finfo)
uploadJSON(name,obj,finfo,function(e,fobj){
//fibers.tckjsonref = fobj
});
}
if (name == undefined)
saveDialog("fiber collection",saveit,that.lastSaveName,fileinfo)
else
saveit(name,fileinfo)
return obj;
}
/***************************************************************************************
* context menu
****************************************************************************************/
var fiberContextMenu = KContextMenu(
function(ev) {
var target = ev.target;
for (var k = 0;k< 3;k++)
{
if ($(target).is("tr"))
break;
target = $(target).parent();
}
prepObjectInfo(target);
var $menu = $("<ul class='menu_context'>")
$menu.append($("<li onchoice='save' >save</li>"));
return $menu;
},
function (str,ev)
{
if (str=="save")
{
}
});
/***************************************************************************************
* creation of 3D objects from volume data
****************************************************************************************/
that.createSurfaceFromROI = function(fobj,ondone,thres,progress)
{
var labelObj;
if (thres != undefined)
labelObj = {threshold:thres};
if (progress == undefined)
progress = that.progressSpinner;
if (fobj.fileinfo.surfreference != undefined)
{
var fileObject = fobj.fileinfo.surfreference;
that.computeIsoSurf2(fileObject,labelObj,progress,function()
{
for (var j=0;j < fileObject.content.update.length;j++)
fileObject.content.update[j]();
});
return;
}
var fileObject = {};
fileObject.content = {nifti:fobj.content,buffer:undefined,update:[] };
fileObject.fileID = 'SURF_' + KObject3DTool.uidCnter++;
fileObject.filename = 'surf.' + fobj.filename;
fileObject.contentType = 'gii';
fileObject.fileinfo = {roireference:fobj};
fobj.fileinfo.surfreference = fileObject;
fileObject.content.min = math.multiply(fobj.content.edges,[0,0,0,1])._data;
fileObject.content.max = math.multiply(fobj.content.edges,[
fobj.content.sizes[0],fobj.content.sizes[1],fobj.content.sizes[2],1])._data;
// forward an update callback from the roi if desired
if(fobj.update != undefined)
fileObject.content.update.push(fobj.update);
progress("creating isosurface");
that.computeIsoSurf2(fileObject,labelObj,progress,function(){
progress();
if (ondone)
ondone(fileObject);
});
}
that.createSurfaceFromAtlas = function(fobj,label,ondone)
{
var fileObject = {};
fileObject.content = {nifti:fobj.content,buffer:undefined,update:[] };
fileObject.fileID = 'SURF_' + KObject3DTool.uidCnter++;
fileObject.filename = 'surf.' + ((label==undefined)?"unknown":label.name);
fileObject.contentType = 'gii';
fileObject.fileinfo = {};
var Labels;
if (label)
{
if (label.key != undefined) // thats a single label
{
label.surfacereference = fileObject;
Labels = parseInt(label.key);
}
else
{
Labels = label;
}
}
that.progressSpinner("creating isosurface");
that.computeIsoSurf2(fileObject,Labels,that.progressSpinner,function()
{
KViewer.dataManager.setFile(fileObject.fileID,fileObject);
KViewer.cacheManager.update();
KViewer.obj3dTool.addObject(fileObject);
if (!that.enabled)
KViewer.obj3dTool.$toggle.trigger("click");
that.progressSpinner();
if (ondone)
ondone(fileObject);
});
}
function smooth(verts,trigs)
{
var verts_sm = new Float32Array(verts.length);
var ncnt = new Int32Array(verts.length/3);
for (var k=0;k<trigs.length/3;k++)
{
var v = [0,0,0];
for (var j = 0; j < 3;j++)
{
for (var i = 0; i < 3; i++)
v[i] += verts[3*trigs[3*k+j]+i]*0.3333;
}
for (var j = 0; j < 3;j++)
{
for (var i = 0; i < 3; i++)
verts_sm[3*trigs[3*k+j]+i] += v[i];
ncnt[trigs[3*k+j]]++;
}
}
for (var k = 0; k < verts.length/3;k++)
{
verts_sm[3*k] /= ncnt[k];
verts_sm[3*k+1] /= ncnt[k];
verts_sm[3*k+2] /= ncnt[k];
}
return verts_sm;
}
that.computeIsoSurf2 = function(fobj,label,progress,done)
{
if (fobj.cache == undefined)
fobj.cache = {};
var key = JSON.stringify(fobj.content.nifti.currentTimePoint)
if (fobj.cache[key] != undefined && !(fobj.filename && fobj.filename.search("pointROI")>-1 ))
{
if (fobj.changed)
{
if (fobj.changed && fobj.content.nifti.currentTimePoint &&
fobj.changed[fobj.content.nifti.currentTimePoint.t] == undefined)
{
$.extend(fobj.content,fobj.cache[key] );
done(fobj);
return;
}
else
fobj.changed[fobj.content.nifti.currentTimePoint.t] = undefined;
}
else if (fobj.cache[key].last_label == JSON.stringify(label))
{
$.extend(fobj.content,fobj.cache[key] );
done(fobj);
return;
}
}
var edges = fobj.content.nifti.edges;
var fid = fobj.fileID;
if (fobj.fileinfo && fobj.fileinfo.roireference)
fid = fobj.fileinfo.roireference.fileID
if (KViewer.navigationTool.isinstance && (( master.navigationTool.movingObjs[fid] != undefined & KViewer.navigationMode == 0) )) //| KViewer.navigationMode == 2 ))
edges = kmath.multiply(kmath.inv(KViewer.reorientationMatrix.matrix),edges)
// edges = kmath.multiply((KViewer.reorientationMatrix.matrix),edges)
executeImageWorker.createIsoSurf_running = true
progress("creating isosurface");
var worker = executeImageWorker({func:'createISOSurf',
data:fobj.content.nifti.data,
sizes:fobj.content.nifti.sizes,
edges:edges,
currentTimePoint:fobj.content.nifti.currentTimePoint,
detsign:fobj.content.nifti.detsign,
label:label
},[],
function(e)
{
if (progress)
progress(e);
}
,
function(e)
{
if (progress)
progress();
executeImageWorker.createIsoSurf_running = undefined
$.extend(fobj.content,e.execObj);
fobj.cache[key] = e.execObj;
fobj.cache[key].last_label = JSON.stringify(label);
done(fobj);
}
);
return worker;
}
/*
that.computeIsoSurf = function(fobj,label)
{
var nii = fobj.content.nifti;
var data = nii.data;
var sizes = nii.sizes;
var w = sizes[0];
var h = sizes[1];
var d = sizes[2];
var wh = sizes[0]*sizes[1];
var cnt = 0;
var vertsIDX = [];
function addVert(i)
{
if (vertsIDX[i] == undefined)
{
vertsIDX[i] = cnt;
cnt++;
return cnt-1;
}
else
return vertsIDX[i];
}
var trigs = [];
var addTrigs;
if (nii.detsign == -1)
addTrigs = function( i00,i10,i11,i01)
{
trigs.push(i00,i10,i11,i00,i11,i01);
}
else
addTrigs = function( i00,i10,i11,i01)
{
trigs.push(i00,i01,i11,i00,i11,i10);
}
var compfun = function(x) { return x>0 }
var negcompfun = function(x) { return x==0 }
if (label)
{
var key = parseInt(label.key);
compfun = function(x) {
return x==key;
}
negcompfun = function(x) {
return x!=key;
}
}
for (var z = 1; z < sizes[2]-1;z++)
for (var y = 1; y < sizes[1]-1;y++)
for (var x = 1; x < sizes[0]-1;x++)
{
var idx = z*wh + w*y + x;
if (compfun(data[idx] > 0))
{
var i00,i10,i01,i11;
if (negcompfun(data[idx-1]))
{
i00 = addVert(idx);
i10 = addVert(idx+w);
i11 = addVert(idx+w+wh);
i01 = addVert(idx+wh);
addTrigs(i00,i01,i11,i10);
}
if (negcompfun(data[idx+1]))
{
i00 = addVert(idx+1);
i10 = addVert(idx+1+wh);
i11 = addVert(idx+1+w+wh);
i01 = addVert(idx+1+w);
addTrigs(i00,i01,i11,i10);
}
if (negcompfun(data[idx-w]))
{
i00 = addVert(idx);
i10 = addVert(idx+1);
i11 = addVert(idx+1+wh);
i01 = addVert(idx+wh);
addTrigs(i00,i10,i11,i01);
}
if (negcompfun(data[idx+w]))
{
i00 = addVert(idx+w);
i10 = addVert(idx+w+wh);
i11 = addVert(idx+w+1+wh);
i01 = addVert(idx+w+1);
addTrigs(i00,i10,i11,i01);
}
if (negcompfun(data[idx-wh]))
{
i00 = addVert(idx);
i10 = addVert(idx+w);
i11 = addVert(idx+w+1);
i01 = addVert(idx+1);
addTrigs(i00,i10,i11,i01);
}
if (negcompfun(data[idx+wh]))
{
i00 = addVert(idx+wh);
i10 = addVert(idx+wh+1);
i11 = addVert(idx+wh+w+1);
i01 = addVert(idx+wh+w);
addTrigs(i00,i10,i11,i01);
}
}
}
var pts = Object.keys(vertsIDX);
var verts = new Float32Array(pts.length*3);
for (var k = 0; k < pts.length;k++)
{
var x = pts[k]%w -0.5;
var y = math.floor(pts[k]/w)%h -0.5;
var z = math.floor(pts[k]/wh) -0.5;
var v = math.multiply(nii.edges,[x,y,z,1])._data;
var i = vertsIDX[pts[k]];
verts[3*i] = v[0];
verts[3*i+1] = v[1];
verts[3*i+2] = v[2];
}
verts = smooth(verts,trigs);
verts = smooth(verts,trigs);
verts = smooth(verts,trigs);
var normal = new Float32Array(trigs.length);
for (var k=0;k<trigs.length/3;k++)
{
var t1 = trigs[3*k];
var t2 = trigs[3*k+1];
var t3 = trigs[3*k+2];
var a = [verts[3*t1+0],verts[3*t1+1],verts[3*t1+2]]
var b = [verts[3*t2+0],verts[3*t2+1],verts[3*t2+2]]
var c = [verts[3*t3+0],verts[3*t3+1],verts[3*t3+2]]
var n = [(b[1]-a[1])*(c[2]-a[2]) - (b[2]-a[2])*(c[1]-a[1]),
(b[2]-a[2])*(c[0]-a[0]) - (b[0]-a[0])*(c[2]-a[2]),
(b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0])];
for (var j=0;j<3;j++)
{
normal[3*trigs[3*k+j]+0] -= n[0];
normal[3*trigs[3*k+j]+1] -= n[1];
normal[3*trigs[3*k+j]+2] -= n[2];
}
}
var c = fobj.content;
c.points = verts;
c.indices = trigs;
c.normals = normal;
}*/
/***************************************************************************************
* unpacking/reading of Surface data
****************************************************************************************/
that.prepareSurfaceData = function(fileObject,uint8Response,processinfo,arrived)
{
if (fileObject.filename.search("\\.gii") > -1)
{
var scriptname = 'gifti-reader-min.js' + '?' + static_info.softwareversion;
scriptLoader.loadScript(scriptname, function() {
var gii = gifti.parse(ab2str(uint8Response));
fileObject.content = {gifti:gii,buffer:uint8Response.buffer };
var c = fileObject.content;
c.points = gii.getPointsDataArray();
var max = [-99999,-99999,-99999];
var min = [99999,99999,99999];
if (c.points != null) c.points = c.points.getData();
for (var k = 0;k < c.points.length/3;k++)
{
for (var j=0; j < 3;j++)
{
if (c.points[k*3+j] > max[j])
max[j] = c.points[k*3+j];
if (c.points[k*3+j] < min[j])
min[j] = c.points[k*3+j];
}
}
c.max = max;
c.min = min;
c.indices = gii.getTrianglesDataArray();
if (c.indices != null) c.indices = c.indices.getData();
c.normals = gii.getNormalsDataArray();
if (c.normals != null) c.normals= c.normals.getData();
else
{
console.log("computing normals")
c.normals = new Float32Array(c.points.length)
for (var k = 0;k < c.indices.length/3;k++)
{
var a = [c.points[3*c.indices[3*k]],c.points[3*c.indices[3*k]+1],c.points[3*c.indices[3*k]+2]];
var b = [c.points[3*c.indices[3*k+1]],c.points[3*c.indices[3*k+1]+1],c.points[3*c.indices[3*k+1]+2]];
var d = [c.points[3*c.indices[3*k+2]],c.points[3*c.indices[3*k+2]+1],c.points[3*c.indices[3*k+2]+2]];
var n = [(b[1]-a[1])*(d[2]-a[2]) - (b[2]-a[2])*(d[1]-a[1]),
(b[2]-a[2])*(d[0]-a[0]) - (b[0]-a[0])*(d[2]-a[2]),
(b[0]-a[0])*(d[1]-a[1]) - (b[1]-a[1])*(d[0]-a[0])];
for (var j=0;j<3;j++)
{
c.normals[3*c.indices[3*k+j]+0] += n[0];
c.normals[3*c.indices[3*k+j]+1] += n[1];
c.normals[3*c.indices[3*k+j]+2] += n[2];
}
}
for (var k = 0; k < c.normals.length/3;k++)
{
var no = math.sqrt(c.normals[3*k]*c.normals[3*k] +c.normals[3*k+1]*c.normals[3*k+1] +c.normals[3*k+2]*c.normals[3*k+2]);
c.normals[3*k] /= no;
c.normals[3*k+1] /= no;
c.normals[3*k+2] /= no;
}
}
c.colors = gii.getColorsDataArray();
if (c.colors != null) c.colors= c.colors.getData();
if (arrived != undefined)
arrived();
});
}
else // a stl
{
fileObject.content = {buffer:uint8Response.buffer };
var c = fileObject.content;
var view = new DataView(uint8Response.buffer);
var LE = true;
var numTrigs = view.getUint32(80,LE);
var pos = 84;
var normals = [];
var indices = [];
var points = [];
var vals = [];
var bbox_max = [-10000,-10000,-10000];
var bbox_min = [10000,10000,10000];
for (var k = 0; k < numTrigs;k++)
{
var n = [-view.getFloat32(pos,LE),-view.getFloat32(pos+4,LE),-view.getFloat32(pos+8,LE)];
normals.push(n[0],n[1],n[2],n[0],n[1],n[2],n[0],n[1],n[2]);
var p1 = [view.getFloat32(pos+12,LE),view.getFloat32(pos+16,LE),view.getFloat32(pos+20,LE)];
var p2 = [view.getFloat32(pos+24,LE),view.getFloat32(pos+28,LE),view.getFloat32(pos+32,LE)];
var p3 = [view.getFloat32(pos+36,LE),view.getFloat32(pos+40,LE),view.getFloat32(pos+44,LE)];
points.push(p1[0],p1[1],p1[2]);
points.push(p2[0],p2[1],p2[2]);
points.push(p3[0],p3[1],p3[2]);
for (var i = 0 ; i < 3;i++)
{
bbox_max[i] = math.max(bbox_max[i],p1[i]);
bbox_max[i] = math.max(bbox_max[i],p2[i]);
bbox_max[i] = math.max(bbox_max[i],p3[i]);
bbox_min[i] = math.min(bbox_min[i],p1[i]);
bbox_min[i] = math.min(bbox_min[i],p2[i]);
bbox_min[i] = math.min(bbox_min[i],p3[i]);
}
indices[3*k] = 3*k;
indices[3*k+1] = 3*k+1;
indices[3*k+2] = 3*k+2;
vals.push(view.getUint16(pos+48,LE))
pos += 50;
}
c.points = new Float32Array(points);
c.normals = new Float32Array(normals);
c.indices = new Int32Array(indices);
c.vals = new Uint16Array(vals);
var minmax = getMinMax(c.vals,c.vals.length,500);
c.histogram = comphisto(minmax.min,minmax.max,20,c.vals,c.vals.length,500);
if (Math.abs(minmax.min-minmax.max) < 0.0000000001)
c.vals = undefined;
c.max = bbox_max;
c.min = bbox_min;
if (arrived != undefined)
arrived();
}
}
that.prepareConmatData = function(fileObject,processinfo,arrived)
{
fileObject.content= JSON.parse(fileObject.content);
fileObject.content.themat = fileObject.content.cc;
if (fileObject.content.themat.length > 0)
{
var m = fileObject.content.themat[0].length;
var ac;
if (m>1)
ac = fileObject.content.themat.reduce(function(a, b) {
return a.concat(b);
});
else
{
ac = fileObject.content.themat.slice(0);
fileObject.content.themat = [ac];
}
var n = ac.length;
var ac_minmax = getMinMax(ac,n,500);
fileObject.histogram = comphisto(ac_minmax.min,ac_minmax.max,20,ac,n,500);
}
else
alertify.error("Problem while reading connectivty matrix");
processinfo();
arrived();
}
/***************************************************************************************
* unpacking/reading of Fiber data
****************************************************************************************/
that.prepareFiberData = function(fileObject,uint8Response,processinfo,arrived)
{
if (fileObject.filename.search('.tck') > 0)
{
that.importandbuildOcttree(fileObject,uint8Response,processinfo, arrived,"TCK")
}
else if (fileObject.filename.search('.trk') > 0)
{
that.importandbuildOcttree(fileObject,uint8Response,processinfo, arrived,"TRK")
}
/*
if (fileObject.filename.search('.tck') > 0)
{
importTCK(fileObject,uint8Response,processinfo,
function()
{
if (fileObject.content != undefined)
{
fileObject.content.md5 = SparkMD5.ArrayBuffer.hash(uint8Response.buffer);
that.buildOctree(fileObject.content,processinfo);
}
arrived();
});
}
else if (fileObject.filename.search('.trk') > 0)
{
importTRK(fileObject,uint8Response,processinfo,
function()
{
if (fileObject.content != undefined)
{
fileObject.content.md5 = SparkMD5.ArrayBuffer.hash(uint8Response.buffer);
that.buildOctree(fileObject.content,processinfo);
}
arrived();
});
}*/
}
that.importandbuildOcttree = function(fileObject,uint8Response,processinfo,arrived,typ)
{
var worker = new startWorker('KTools/KOctreeImportWorker.js');
fileObject.octreeWorker = worker;
worker.queryInProgress = false;
worker.ready = false;
worker.postMessage = worker.webkitPostMessage || worker.postMessage;
worker.addEventListener('message', function(e) {
e = e.data;
if (e.msg == 'index_progress')
{
processinfo(e.detail);
if (e.detail == undefined)
worker.ready = true;
worker.queryInProgress = false;
}
else if (e.msg == 'query_done')
{
worker.queryInProgress = false;
worker.currentCallback(e.selection);
}
else if (e.msg == 'import_done')
{
worker.queryInProgress = false;
var content = e.tracts;
KObject3DTool.unpackTracts(content);
content.octreeWorker = worker;
fileObject.content = content;
fileObject.content.buffer = tmp_buf_save;
fileObject.content.md5 = e.md5;
arrived(content)
}
else if (e.msg.substr(0,13) == 'import_failed')
{
alertify.error(e.msg);
worker.postMessage({message:'kill'},[]);
arrived()
}
}, false);
var tmp_buf_save; // for local files do nbot discard buffer (for possible upload)
if (fileObject.fileID.substring(0,9) == 'localfile')
tmp_buf_save = new Uint8Array(uint8Response)
worker.postMessage({message:'import',typ:typ,buffer: uint8Response },[uint8Response.buffer]);
worker.kill = function()
{
worker.postMessage({message:'kill'},[]);
}
worker.queue = [];
worker.findFibers = function (p,r,d,callback,force)
{
if (!worker.queryInProgress | force)
launch()
function launch()
{
worker.postMessage({message:'query', query:p,radius:r,dirsel:d,qid:worker.currentQueryID}, []);
worker.currentCallback = callback;
worker.queryInProgress = true;
}
}
}
that.buildOctree = function(tracking,processinfo)
{
if (KObject3DTool.useOctreeWorker)
{
var scriptname = 'KTools/KOctreeWorker.js' + '?' + static_info.softwareversion;;
if (typeof url_pref != "undefined")
scriptname = url_pref + scriptname;
var worker = new Worker(scriptname);
tracking.octreeWorker = worker;
worker.queryInProgress = false;
worker.ready = false;
worker.postMessage = worker.webkitPostMessage || worker.postMessage;
worker.addEventListener('message', function(e) {
e = e.data;
if (e.msg == 'index_progress')
{
processinfo(e.detail);
if (e.detail == undefined)
worker.ready = true;
worker.queryInProgress = false;
}
else if (e.msg == 'query_done')
{
worker.queryInProgress = false;
worker.currentCallback(e.selection);
}
}, false);
worker.postMessage({message:'createOctree',
tracts: KObject3DTool.packTractsForTransfer({content:tracking}) }, []);
worker.kill = function()
{
worker.postMessage({message:'kill'},[]);
}
worker.queue = [];
worker.findFibers = function (p,r,d,callback,force)
{
/* worker.queue.push(launch);
var toexec = worker.queue[0];
worker.queue.splice(0,1);
if (!worker.queryInProgress)
toexec();
*/
if (!worker.queryInProgress | force)
launch()
function launch()
{
worker.postMessage({message:'query', query:p,radius:r,dirsel:d,qid:worker.currentQueryID}, []);
worker.currentCallback = callback;
worker.queryInProgress = true;
}
}
}
else
{
var tracts = tracking.tracts;
var octree = tracking.octree;
myOctree.fiberstep = math.floor(tracking.tot_points/5000000);
if (myOctree.fiberstep < 1)
myOctree.fiberstep = 1;
tracts.chunk( function(t,j){
octree.add(t,j);
},512, 1, function(i) {
processinfo("building octree " + Math.round(100*i/tracts.length) + "%");} ,
function() {processinfo(undefined)} );
}
}
/***************************************************************************************
* SubView creation
****************************************************************************************/
that.createView = function(imageStruct,viewer,intent)
{
var fobj = imageStruct;
if (imageStruct.contentType == 'tracts')
{
var parent_view;
if (intent.jsonsubsets)
{
fobj.content.selections = intent.jsonsubsets.selections;
}
if (intent.select >= 0 || (typeof intent.select == 'string' && intent.select != 'allselections' && intent.select != 'all'))
{
var found = false;
for (var k = 0;k < viewer.objects3D.length;k++)
if (viewer.objects3D[k].fibers &&
viewer.objects3D[k].fibers.fileID == fobj.fileID)
{
found = true;
parent_view = viewer.objects3D[k];
break;
}
if (!found)
{
parent_view = that.createFiberView(fobj,viewer,{visible:false,isParentView:true})
parent_view.Selection = undefined;
viewer.objects3D.push(parent_view);
}
}
else
intent.isParentView = true;
intent.parent = parent_view;
var view = that.createFiberView(fobj,viewer,intent);
if (!view)
return
if (!intent.isParentView)
{
view.parent = parent_view;
if (parent_view.children == undefined)
parent_view.children = [];
parent_view.children.push(view);
}
return view
}
else if (imageStruct.contentType == 'gii')
return that.createSurfaceView(fobj,viewer,intent);
else if (imageStruct.contentType == 'rtstruct')
return that.createContourView(fobj,viewer,intent);
else if (imageStruct.filename.search(".cc.json") != -1)
return that.createConmatView(fobj,viewer,intent);
else
{
console.log('contenttype unknown');
return;
}
}
/***************************************************************************************
* The fiber SubView
****************************************************************************************/
that.createFiberView = function(fobj,viewer,intent)
{
var viewer = viewer;
var alpha = 1;
if (state.viewer.fiberAlpha)
alpha = 0.15;
var fibcut_default = viewer.computeMaxExtentFac()/100
var tck = { fibers:fobj,
fiberUpdater:undefined,
fibcut:-1,
fibcut_thres:fibcut_default,
fibcut_proj:-1,
color: (viewer.objects3D.length)%KColor.list.length,
alpha:alpha,
type:"fiber",
flow_param:0,
annotation_subsets:{},
associated_annotation:-1,
subsetToDisplay:undefined,
fiberSign:undefined,
Selection:{},
children:[],
viewer:viewer,
uid: KObject3DTool.uidCnter++,
isCurrent: false
};
tck.getViewProperties = function()
{
return {color:this.color,
fibcut:this.fibcut,
fibcut_thres:this.fibcut_thres,
fibcut_proj:this.fibcut_proj,
alpha:this.alpha,
flow_param:this.flow_param
};
}
tck.toggleFlow = function()
{
var viewer = tck.viewer;
if (viewer.flow_id != undefined)
clearInterval(viewer.flow_id);
if (this.flow_param > 0)
{
tck.fibers.content.flow_content = 1 ;
tck.viewer.gl.timeout = 100000;
viewer.flow_time=0;
var flow_param = parseFloat(this.flow_param)
tck.fiberDirColor_shader.setFloat("flow_len",0.02);
viewer.flow_id = setInterval(function ()
{
viewer.flow_time = (viewer.flow_time+0.1*flow_param)%1;
for (var k = 0; k < viewer.objects3D.length;k++)
{
if (viewer.objects3D[k].flow_param>0)
viewer.objects3D[k].fiberDirColor_shader.setFloat("flow", viewer.flow_time);
}
},50)
}
else
{
tck.viewer.gl.timeout = tck.viewer.gl.timeout_default;
tck.fiberDirColor_shader.setFloat("flow",-1);
}
}
if (intent.isParentView && intent.dirvolref == undefined)
tck.Selection = undefined;
if (intent != undefined)
{
if (intent.dirvolref)
{
var scale = intent.dirvolref.nii.voxSize[0];
var params = that.tracking_panel.params;
params.Jitter = scale / 40;
params.Stepwidth = scale / 5;
tck.trackingVolHistoman = intent.dirvolref.histoManager;
tck.trackingVol = intent.dirvolref.nii
tck.trackingVolID = intent.dirvolref.currentFileID
tck.autogenerate_tracks = true;
if (intent.dirvolref.nii.sizes[4]>1)
{
tck.flow_param = 0.2;
params.sign = 1;
params.Maxlen=150;
params.Minlen=10;
params.SmoothWidth=0;
params.Density=200;
params.Jitter=0;
params.t_start_rand=1;
params.Stepwidth = 0.01;
}
that.tracking_panel.update(tck);
}
else
{
if (intent.select != undefined && intent.select != 'all')
{
if (tck.fibers.content.selections != undefined)
{
if (typeof intent.select == 'string')
{
for (var k = 0; k < tck.fibers.content.selections.length;k++)
if (tck.fibers.content.selections[k].name == intent.select)
{
intent.select = k;
break;
}
}
tck.Selection = tck.fibers.content.selections[intent.select];
for (var k = 0; k < intent.parent.children.length;k++)
{
if (intent.parent.children[k].Selection == tck.Selection)
return false;
}
if (tck.Selection != undefined)
tck.subsetToDisplay = tck.Selection.subset;
}
}
}
tck.parent = intent.parent;
if (intent.flow_param != undefined) tck.flow_param = intent.flow_param;
if (intent.alpha != undefined) tck.alpha = intent.alpha;
if (intent.fibcut != undefined) tck.fibcut = intent.fibcut;
if (intent.fibcut_thres != undefined) tck.fibcut_thres = intent.fibcut_thres;
if (intent.fibcut_proj != undefined) tck.fibcut_proj = intent.fibcut_proj;
if (intent.isParentView != undefined) tck.isParentView = intent.isParentView;
}
var fiberDirColor_shader = viewer.gl.createFiberShader()
fiberDirColor_shader.setFloat("planesThres",parseFloat(tck.fibcut_thres));
fiberDirColor_shader.setFloat("planesNum",tck.fibcut);
fiberDirColor_shader.setFloat("alpha",tck.alpha);
fiberDirColor_shader.setFloat("planesProj",tck.fibcut_proj);
tck.fiberDirColor_shader = fiberDirColor_shader;
tck.selectFibersReset =selectFibersReset;
function selectFibersReset(str)
{
removeAnnotationAssoc();
tck.associated_annotation = -1;
tck.autogenerate_tracks = false;
if (str == 'all' || str == 'accumulate' )
{
tck.subsetToDisplay = undefined;
if (tck.Selection != undefined)
{
if (tck.Selection.subset != undefined)
{
tck.subsetToDisplay = tck.Selection.subset;
tck.subsetParameterColors = tck.Selection.colmode
viewer.statusbar.report(tck.subsetToDisplay.length + " fibers shown");
}
}
if (tck.trackingVol != undefined)
{
tck.autogenerate_tracks = true;
if (str == 'accumulate')
tck.autogenerate_tracks = 2;
}
}
else
tck.subsetToDisplay = [];
tck.updateFibers();
}
function computeFiberMorphology(type)
{
var vismap = cloneNifti(viewer.content,"tmpvisit","float");
createFiberVisitMap(tck.fibers.content.tracts,tck.subsetToDisplay,-1,
vismap,
viewer.viewport.progressSpinner,
function()
{
vismap.content = prepareMedicalImageData(parse(vismap.buffer), vismap, {});
if (type == "dilate")
{
tck.subsetToDisplay = undefined;
selectFibersByROI(tck,vismap,false,0.6,0);
}
else if (type == "erode")
{
selectFibersByROI(tck,vismap,false,0.8,1.5);
}
else
{
tck.subsetToDisplay = undefined;
selectFibersByROI(tck,vismap,false,0.8,1.5);
}
});
}
////////////// color context menu
var colors = ["dir"];
colors = colors.concat(KColor.list);
var $colselector = KColorSelector(colors,
function(c) { if (c=="dir") return ""; else return "background:"+RGB2HTML(c[0],c[1],c[2])+";"; },
function (col,colindex)
{
if (colindex != undefined && tck.Selection)
tck.Selection.color = colindex;
viewer.gl.activateRenderLoop();
fiberDirColor_shader.setFloat("alpha",tck.alpha);
if (col == "flow")
{
tck.toggleFlow()
return;
}
if (col != undefined)
{
if (col.color != undefined)
col = col.color;
if (col == 'dir')
fiberDirColor_shader.setVector4("col",new BABYLON.Vector4(0,0,0,0));
else
fiberDirColor_shader.setVector4("col",new BABYLON.Vector4(col[0]/255,col[1]/255,col[2]/255,1));
if (colindex == undefined)
tck.color = col;
}
if (tck.fiberUpdater && tck.fiberUpdater.nicefibs!= undefined)
tck.showNiceFibs();
},
tck,{manual:true,alpha:true});
if (intent != undefined && intent.color != undefined)
{
if (typeof intent.color == "number")
tck.color = intent.color%colors.length ;
else
tck.color = intent.color;
if (tck.Selection)
tck.Selection.color = tck.color;
$colselector.updateColor();
}
/***************************************************************************************
* fiber selection context menu
****************************************************************************************/
tck.fiberSelAction = fiberSelAction;
function fiberSelAction(str)
{
//console.log(tck);
if (str == '' | str == undefined)
return;
else if (str == 'all' | str == 'none')
{
selectFibersReset(str)
}
else if (str == 'fiberode')
{
computeFiberMorphology("erode");
}
else if (str == 'fibdilate')
{
computeFiberMorphology("dilate");
}
else if (str == 'fibclose')
{
computeFiberMorphology(false);
}
else if (str == 'trackparams')
{
that.tracking_panel.toggle();
}
else if (str.substring(0,4) == 'ROI_' | str.substring(0,9) == 'minusROI_' )
{
var minus = false;
if (str.substring(0,5) == 'minus') { str = str.substring(5); minus = true; }
var roi = KViewer.roiTool.ROIs[str.substring(4)];
alertify.prompt("Minimal overlap of streamline with ROI in percent (0 - at least one vertex touches)", function(e,str)
{
if (e)
{
var percentage = parseFloat(str)/100;
selectFibersByROI(tck,roi,minus,percentage)
}
},"0");
}
else if (str.substring(0,8) == 'seedROI_' )
{
var roi = KViewer.roiTool.ROIs[str.substring(8)];
if (roi.content.onVoxels == undefined)
{