Search code examples
xtkami.js

Using XTK or AMI.js to display Freesurfer while/pial objects on T1.mgz


I'd like to recreate a web version of FreeSurfer pial/white surfaces overlaid on T1.mgz similar to the first freeview image at https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/PialEdits_freeview. Using XTK I can get something that hints at that using advice from Othographic Projection in XTK. The code I used to create the image (along with kruft from multiple attempts) is below the image.

XTK pial/white overlay attempt

Is this possible with XTK or should I switch over to AMI.js (which has freesurfer surfaces and MGZ file formats on their roadmap but are not implemented)?

In either case, pointers to how to accomplish this would be appreciated.

Thanks.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <title>FS XTK test</title>
  </head>
  <body>
    <script type="text/javascript" src="/Xdevel/lib/google-closure-library/closure/goog/base.js"></script>
    <script type="text/javascript" src="/Xdevel/xtk-deps.js"></script>
    <script type="text/javascript" src="/Xdevel/xtk_xdat.gui.js"></script>
    <script type="text/javascript">
      var view2D_X = null;
      var view2D_Y = null;
      var view2D_Z = null;
      var view3D = null;
      var volume3D = null;
      var meshes = new Array(6);
      var meshFiles = new Array(6);

      var t1File = 'T1.mgz';
      meshFiles[0]='lh.orig';
      meshFiles[1]='rh.orig';
      meshFiles[2]='lh.pial';
      meshFiles[3]='rh.pial';
      meshFiles[4]='lh.white.pial';
      meshFiles[5]='rh.white.pial';

      var colors = [ //Matlab jet(28)
              [         0,         1,         0],
              [         0,         1,         0],
              [         1,         0,         0],
              [         1,         0,         0],
              [         0,         0,         1],
              [         0,         0,         1]
      ];


      function setView(pos)
      {
        switch(pos)
        {
          case 1:
            camPos=[ 0,  0, -1, 0,
                    -1,  0, -0, 0,
                     0,  1,  0, 0,
                     1,  0, -1, 1];
            break;
          case 2:
            camPos=[-1,  0,  0, 0,
                     0,  0,  1, 0,
                     0,  1, -0, 0,
                     0, -1, -1, 1];
            break;
          default: //Case 3
            camPos=[-1,  0, -0, 0,
                    -0,  1, -0, 0,
                     0,  0,  1, 0,
                     0, -0, -1, 1];
          break;
        }
        camPos[14] = 200*camPos[14]; //zoomout
        view3D.camera.view=new Float32Array(camPos);
      }

      // include all used X-classes here
      // this is only required when using the xtk-deps.js file
      goog.require('X.renderer2D');
      goog.require('X.renderer3D');
      goog.require('X.mesh');

      function addLoadEvent(func) {
        var oldonload = window.onload;
        if (typeof window.onload != 'function') {
          window.onload = func;
        } else {
          window.onload = function() {
            if (oldonload) {
              oldonload();
            }
            func();
          }
        }
      }
    </script>

    <div id="view3D_div" style="background-color: #000; width: 399px; height: 399px;"></div>
    <script type="text/javascript">
      function loadMeshes()
      {
        for (var a = 0; a < 6; a++)
        {
          try
          {
            meshes[a] = new X.mesh();
            meshes[a].file=meshFiles[a];
            meshes[a].color = colors[a];
            meshes[a].visible=true;
            view3D.add(meshes[a]);
          }
          catch(err)
          {
            console.log('failed to load: '+meshFiles[a]);
            console.log(err.message);
          }
        }
      }

      var _meshConfig = {
        'width' : 399,
        'height' : 399,
        'unknown' : 180.5,
        'diff' : 0.3
      };

      function setMainSlice()
      {
//        console.log('height: ' + height + ' width: ' + width);
        console.log('X: ' + volume3D.indexX + 'Y: ' + volume3D.indexY + 'Z: ' + volume3D.indexZ);
        _meshConfig.unknown=volume3D.indexZ+92.5;
        console.log('width: '+_meshConfig.width + ' height: ' + _meshConfig.height + ' unknown: ' + _meshConfig.unknown);
        view3D.camera._perspective=X.matrix.makeOrtho(X.matrix.identity(), -(_meshConfig.width/2), (_meshConfig.width/2), -(_meshConfig.height/2), (_meshConfig.height/2), _meshConfig.unknown+_meshConfig.diff, _meshConfig.unknown-_meshConfig.diff);
//        view3D.camera._perspective=goog.vec.Mat4.createFromValues(1,0,0,0,0,1,0,0,0,0,1,0,volume3D.indexX,volume3D.indexY,volume3D.indexZ,1);
//        view3D.camera._perspective=goog.vec.Mat4.createFromValues(0.005,0,0,0, 0,0.005,0,0, 0,0,3,0, 0,0,256+(volume3D.indexZ*2),1);
      }

      addLoadEvent(function () {
        view3D = new X.renderer3D();
        view3D.container = 'view3D_div';
        view3D.init();
        volume3D = new X.volume();
        volume3D.file = t1File;
//        volume3D.labelmap.file='all.white.mgz';

        view3D.add(volume3D);

        loadMeshes();
        setView(3);
//              view3D.camera.position=[-0, 0, 90];
//              view3D.camera.view[14] = -200;
        view3D.render();

        view3D.onShowtime = function () {
          view2D_X.onScroll = setMainSlice;
          view2D_X.add(volume3D);
          view2D_X.render();
          view2D_Y.onScroll = setMainSlice;
          view2D_Y.add(volume3D);
          view2D_Y.render();
          view2D_Z.onScroll = setMainSlice;
          view2D_Z.add(volume3D);
          view2D_Z.render();
          setView(3);
        };


        var gui = new dat.GUI();
        var anat_folder = gui.addFolder('T1');
        anat_folder.add(volume3D,'visible');
        anat_folder.add(volume3D,'opacity',0,1);
        anat_folder.add(volume3D,'indexX');
        anat_folder.add(volume3D,'indexY');
        anat_folder.add(volume3D,'indexZ',0,256);
        anat_folder.open();
        var lh_orig_folder = gui.addFolder('Freesurfer lh.orig');
        lh_orig_folder.add(meshes[0],'visible');
        lh_orig_folder.add(meshes[0],'opacity',0,1);
        lh_orig_folder.addColor(meshes[0],'color');
//        lh_orig_folder.open();
        var rh_orig_folder = gui.addFolder('Freesurfer rh.orig');
        rh_orig_folder.add(meshes[1],'visible');
        rh_orig_folder.add(meshes[1],'opacity',0,1);
        rh_orig_folder.addColor(meshes[1],'color');
//        rh_orig_folder.open();
        var lh_pial_folder = gui.addFolder('Freesurfer lh.pial');
        lh_pial_folder.add(meshes[2],'visible');
        lh_pial_folder.add(meshes[2],'opacity',0,1);
        lh_pial_folder.addColor(meshes[2],'color');
//        lh_pial_folder.open();
        var rh_pial_folder = gui.addFolder('Freesurfer rh.pial');
        rh_pial_folder.add(meshes[3],'visible');
        rh_pial_folder.add(meshes[3],'opacity',0,1);
        rh_pial_folder.addColor(meshes[3],'color');
//        rh_pial_folder.open();
        var lh_white_folder = gui.addFolder('Freesurfer lh.white');
        lh_white_folder.add(meshes[4],'visible');
        lh_white_folder.add(meshes[4],'opacity',0,1);
        lh_white_folder.addColor(meshes[4],'color');
//        lh_white_folder.open();
        var rh_white_folder = gui.addFolder('Freesurfer rh.white');
        rh_white_folder.add(meshes[5],'visible');
        rh_white_folder.add(meshes[5],'opacity',0,1);
        rh_white_folder.addColor(meshes[5],'color');
//        rh_white_folder.open();
        var mesh_folder = gui.addFolder('Mesh');
        mesh_folder.add(_meshConfig,'height');
        mesh_folder.add(_meshConfig,'width');
        mesh_folder.add(_meshConfig,'unknown');
        mesh_folder.open();

        for (c in gui.__controllers)
        {
          gui.__controllers[c].onFinishChange(update);
        }
      });


    </script>


    <table style="border-collapse: collapse">
      <tr>
        <td style="background-color: red;">
          <div id="view2D_X_div" style="background-color: #000; width: 131px; height: 131px;"></div>
          <script type="text/javascript">
            addLoadEvent(function () {
              view2D_X = new X.renderer2D();
              view2D_X.container = 'view2D_X_div';
              view2D_X.orientation = 'X';
              view2D_X.init();
            });
          </script>
        </td>
        <td style="background-color: green;">
          <div id="view2D_Y_div" style="background-color: #000; width: 131px; height: 131px;"></div>
          <script type="text/javascript">
            addLoadEvent(function () {
              view2D_Y = new X.renderer2D();
              view2D_Y.container = 'view2D_Y_div';
              view2D_Y.orientation = 'Y';
              view2D_Y.init();
            });
          </script>
        </td>
        <td style="background-color: blue;">
          <div id="view2D_Z_div" style="background-color: #000; width: 131px; height: 131px;"></div>
          <script type="text/javascript">
            addLoadEvent(function () {
              view2D_Z = new X.renderer2D();
              view2D_Z.container = 'view2D_Z_div';
              view2D_Z.orientation = 'Z';
              view2D_Z.init();
            });
          </script>
        </td>
      </tr>
      <tr>
        <td style="background-color: red;">
<!--                <button onClick="setView([-90,0,0]);">Set View</button>-->
          <button onClick="setView(1);">Set View</button>
        </td>
        <td style="background-color: green;">
<!--                <button onClick="setView([0,90,0]);">Set View</button>-->
          <button onClick="setView(2);">Set View</button>
        </td>
        <td style="background-color: blue;">
<!--                <button onClick="setView([0,0,90]);">Set View</button>-->
          <button onClick="setView(3);">Set View</button>
        </td>
      </tr>
    </table>

    Green is orig<br>
    Red is pial<br>
    Blue is white<br>


  </body>
</html>

Solution

  • You can now do it in AMI (thanks to your PR: https://fnndsc.github.io/ami/#viewers_quadview)

    1. Display intersection between a mesh and a plane

    2. Post process the intersection to display the contours.

    There are different techniques to display mesh/plane intersection:

    1. With a stencil buffer (https://github.com/daign/clipping-with-caps)
    2. Playing the the mesh opacity (https://github.com/FNNDSC/ami/tree/dev/examples/viewers_quadview)

    All those techniques are computationally expensive at it requires 3 renders pass to display contours of 1 mesh and there may be a better approach but not sure what would be the best alternative.

    HTH