UNPKG

@galaxyproject/nora

Version:

NORA Medical Imaging Viewer

1,793 lines (1,423 loc) 194 kB
// ====================================================================================== // ====================================================================================== // ============= 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) {