PTdecode/CImg-1.3.0/examples/dtmri_view.cpp

Fri, 25 Sep 2009 10:50:44 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Fri, 25 Sep 2009 10:50:44 +0100
changeset 21
629637abfe1f
parent 5
1204ebf9340d
permissions
-rwxr-xr-x

added dots-per-inch to status readback

philpem@5 1 /*
philpem@5 2 #
philpem@5 3 # File : dtmri_view.cpp
philpem@5 4 # ( C++ source file )
philpem@5 5 #
philpem@5 6 # Description : A viewer of Diffusion-Tensor MRI volumes (medical imaging).
philpem@5 7 # This file is a part of the CImg Library project.
philpem@5 8 # ( http://cimg.sourceforge.net )
philpem@5 9 #
philpem@5 10 # Copyright : David Tschumperle
philpem@5 11 # ( http://www.greyc.ensicaen.fr/~dtschump/ )
philpem@5 12 #
philpem@5 13 # License : CeCILL v2.0
philpem@5 14 # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
philpem@5 15 #
philpem@5 16 # This software is governed by the CeCILL license under French law and
philpem@5 17 # abiding by the rules of distribution of free software. You can use,
philpem@5 18 # modify and/ or redistribute the software under the terms of the CeCILL
philpem@5 19 # license as circulated by CEA, CNRS and INRIA at the following URL
philpem@5 20 # "http://www.cecill.info".
philpem@5 21 #
philpem@5 22 # As a counterpart to the access to the source code and rights to copy,
philpem@5 23 # modify and redistribute granted by the license, users are provided only
philpem@5 24 # with a limited warranty and the software's author, the holder of the
philpem@5 25 # economic rights, and the successive licensors have only limited
philpem@5 26 # liability.
philpem@5 27 #
philpem@5 28 # In this respect, the user's attention is drawn to the risks associated
philpem@5 29 # with loading, using, modifying and/or developing or reproducing the
philpem@5 30 # software by the user in light of its specific status of free software,
philpem@5 31 # that may mean that it is complicated to manipulate, and that also
philpem@5 32 # therefore means that it is reserved for developers and experienced
philpem@5 33 # professionals having in-depth computer knowledge. Users are therefore
philpem@5 34 # encouraged to load and test the software's suitability as regards their
philpem@5 35 # requirements in conditions enabling the security of their systems and/or
philpem@5 36 # data to be ensured and, more generally, to use and operate it in the
philpem@5 37 # same conditions as regards security.
philpem@5 38 #
philpem@5 39 # The fact that you are presently reading this means that you have had
philpem@5 40 # knowledge of the CeCILL license and that you accept its terms.
philpem@5 41 #
philpem@5 42 */
philpem@5 43
philpem@5 44 #include "CImg.h"
philpem@5 45 using namespace cimg_library;
philpem@5 46
philpem@5 47 // The lines below are necessary when using a non-standard compiler as visualcpp6.
philpem@5 48 #ifdef cimg_use_visualcpp6
philpem@5 49 #define std
philpem@5 50 #endif
philpem@5 51 #ifdef min
philpem@5 52 #undef min
philpem@5 53 #undef max
philpem@5 54 #endif
philpem@5 55
philpem@5 56 // Compute fractional anisotropy (FA) of a tensor
philpem@5 57 //-------------------------------------------
philpem@5 58 template<typename T> float get_FA(const T& val1, const T& val2, const T& val3) {
philpem@5 59 const float
philpem@5 60 l1 = val1>0?val1:0, l2 = val2>0?val2:0, l3 = val3>0?val3:0,
philpem@5 61 lm = (l1+l2+l3)/3,
philpem@5 62 tr2 = 2*( l1*l1 + l2*l2 + l3*l3 ),
philpem@5 63 ll1 = l1-lm,
philpem@5 64 ll2 = l2-lm,
philpem@5 65 ll3 = l3-lm;
philpem@5 66 if (tr2>0) return (float)std::sqrt( 3*(ll1*ll1 + ll2*ll2 + ll3*ll3)/tr2 );
philpem@5 67 return 0;
philpem@5 68 }
philpem@5 69
philpem@5 70 // Insert an ellipsoid in a CImg 3D scene
philpem@5 71 //----------------------------------------
philpem@5 72 template<typename t,typename tp,typename tf,typename tc>
philpem@5 73 void insert_ellipsoid(const CImg<t>& tensor,const float X,const float Y,const float Z,const float tfact,
philpem@5 74 const float vx, const float vy, const float vz,
philpem@5 75 CImgList<tp>& points, CImgList<tf>& faces, CImgList<tc>& colors,
philpem@5 76 const unsigned int res1 = 20, const unsigned int res2 = 20) {
philpem@5 77
philpem@5 78 // Compute eigen elements
philpem@5 79 const float l1 = tensor[0], l2 = tensor[1], l3 = tensor[2], fa = get_FA(l1,l2,l3);
philpem@5 80
philpem@5 81 CImg<> vec = CImg<>::matrix(tensor[3],tensor[6],tensor[9],
philpem@5 82 tensor[4],tensor[7],tensor[10],
philpem@5 83 tensor[5],tensor[8],tensor[11]);
philpem@5 84 const int
philpem@5 85 r = (int)cimg::min(30+1.5f*cimg::abs(255*fa*tensor[3]),255.0f),
philpem@5 86 g = (int)cimg::min(30+1.5f*cimg::abs(255*fa*tensor[4]),255.0f),
philpem@5 87 b = (int)cimg::min(30+1.5f*cimg::abs(255*fa*tensor[5]),255.0f);
philpem@5 88
philpem@5 89 // Define mesh points
philpem@5 90 const unsigned int N0 = points.size;
philpem@5 91 for (unsigned int v=1; v<res2; v++)
philpem@5 92 for (unsigned int u=0; u<res1; u++) {
philpem@5 93 const float
philpem@5 94 alpha = (float)(u*2*cimg::valuePI/res1),
philpem@5 95 beta = (float)(-cimg::valuePI/2 + v*cimg::valuePI/res2),
philpem@5 96 x = (float)(tfact*l1*std::cos(beta)*std::cos(alpha)),
philpem@5 97 y = (float)(tfact*l2*std::cos(beta)*std::sin(alpha)),
philpem@5 98 z = (float)(tfact*l3*std::sin(beta));
philpem@5 99 points.insert((CImg<tp>::vector(X,Y,Z)+vec*CImg<tp>::vector(x,y,z)).mul(CImg<tp>::vector(vx,vy,vz)));
philpem@5 100 }
philpem@5 101 const unsigned int N1 = points.size;
philpem@5 102 points.insert((CImg<tp>::vector(X,Y,Z)+vec*CImg<tp>::vector(0,0,-l3*tfact)));
philpem@5 103 points.insert((CImg<tp>::vector(X,Y,Z)+vec*CImg<tp>::vector(0,0,l3*tfact)));
philpem@5 104 points[points.size-2](0)*=vx; points[points.size-2](1)*=vy; points[points.size-2](2)*=vz;
philpem@5 105 points[points.size-1](0)*=vx; points[points.size-1](1)*=vy; points[points.size-1](2)*=vz;
philpem@5 106
philpem@5 107 // Define mesh triangles
philpem@5 108 for (unsigned int vv=0; vv<res2-2; vv++)
philpem@5 109 for (unsigned int uu=0; uu<res1; uu++) {
philpem@5 110 const int nv = (vv+1)%(res2-1), nu = (uu+1)%res1;
philpem@5 111 faces.insert(CImg<tf>::vector(N0+res1*vv+nu,N0+res1*nv+uu,N0+res1*vv+uu));
philpem@5 112 faces.insert(CImg<tf>::vector(N0+res1*vv+nu,N0+res1*nv+nu,N0+res1*nv+uu));
philpem@5 113 colors.insert(CImg<tc>::vector(r,g,b));
philpem@5 114 colors.insert(CImg<tc>::vector(r,g,b));
philpem@5 115 }
philpem@5 116 for (unsigned int uu=0; uu<res1; uu++) {
philpem@5 117 const int nu = (uu+1)%res1;
philpem@5 118 faces.insert(CImg<tf>::vector(N0+nu,N0+uu,N1));
philpem@5 119 faces.insert(CImg<tf>::vector(N0+res1*(res2-2)+nu, N1+1,N0+res1*(res2-2)+uu));
philpem@5 120 colors.insert(CImg<tc>::vector(r,g,b));
philpem@5 121 colors.insert(CImg<tc>::vector(r,g,b));
philpem@5 122 }
philpem@5 123 }
philpem@5 124
philpem@5 125 // Insert a fiber in a CImg 3D scene
philpem@5 126 //-----------------------------------
philpem@5 127 template<typename T,typename te,typename tp, typename tf, typename tc>
philpem@5 128 void insert_fiber(const CImg<T>& fiber, const CImg<te>& eigen, const CImg<tc>& palette,
philpem@5 129 const int xm, const int ym, const int zm,
philpem@5 130 const float vx, const float vy, const float vz,
philpem@5 131 CImgList<tp>& points, CImgList<tf>& primitives, CImgList<tc>& colors) {
philpem@5 132 const int N0 = points.size;
philpem@5 133 float x0 = fiber(0,0), y0 = fiber(0,1), z0 = fiber(0,2), fa0 = eigen.linear_atXYZ(x0,y0,z0,12);
philpem@5 134 points.insert(CImg<>::vector(vx*(x0-xm),vy*(y0-ym),vz*(z0-zm)));
philpem@5 135 for (int l=1; l<fiber.dimx(); l++) {
philpem@5 136 float x1 = fiber(l,0), y1 = fiber(l,1), z1 = fiber(l,2), fa1 = eigen.linear_atXYZ(x1,y1,z1,12);
philpem@5 137 points.insert(CImg<tp>::vector(vx*(x1-xm),vy*(y1-ym),vz*(z1-zm)));
philpem@5 138 primitives.insert(CImg<tf>::vector(N0+l-1,N0+l));
philpem@5 139 const unsigned char
philpem@5 140 icol = (unsigned char)(fa0*255),
philpem@5 141 r = palette(icol,0),
philpem@5 142 g = palette(icol,1),
philpem@5 143 b = palette(icol,2);
philpem@5 144 colors.insert(CImg<unsigned char>::vector(r,g,b));
philpem@5 145 x0=x1; y0=y1; z0=z1; fa0=fa1;
philpem@5 146 }
philpem@5 147 }
philpem@5 148
philpem@5 149 // Compute fiber tracking using 4th-order Runge Kutta integration
philpem@5 150 //-----------------------------------------------------------------
philpem@5 151 template<typename T>
philpem@5 152 CImg<> get_fibertrack(CImg<T>& eigen,
philpem@5 153 const int X0, const int Y0, const int Z0, const float lmax=100,
philpem@5 154 const float dl = 0.1f, const float FAmin=0.7f, const float cmin=0.5f) {
philpem@5 155
philpem@5 156 #define align_eigen(i,j,k) \
philpem@5 157 { T &u = eigen(i,j,k,3), &v = eigen(i,j,k,4), &w = eigen(i,j,k,5); \
philpem@5 158 if (u*cu+v*cv+w*cw<0) { u=-u; v=-v; w=-w; }}
philpem@5 159
philpem@5 160 CImgList<> resf;
philpem@5 161
philpem@5 162 // Forward tracking
philpem@5 163 float normU = 0, normpU = 0, l = 0, X = (float)X0, Y = (float)Y0, Z = (float)Z0;
philpem@5 164 T
philpem@5 165 pu = eigen(X0,Y0,Z0,3),
philpem@5 166 pv = eigen(X0,Y0,Z0,4),
philpem@5 167 pw = eigen(X0,Y0,Z0,5);
philpem@5 168 normpU = (float)std::sqrt(pu*pu+pv*pv+pw*pw);
philpem@5 169 bool stopflag = false;
philpem@5 170
philpem@5 171 while (!stopflag) {
philpem@5 172 if (X<0 || X>eigen.dimx()-1 || Y<0 || Y>eigen.dimy()-1 || Z<0 || Z>eigen.dimz()-1 ||
philpem@5 173 eigen((int)X,(int)Y,(int)Z,12)<FAmin || l>lmax) stopflag = true;
philpem@5 174 else {
philpem@5 175 resf.insert(CImg<>::vector(X,Y,Z));
philpem@5 176
philpem@5 177 const int
philpem@5 178 cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>=eigen.dimx())?eigen.dimx()-1:cx+1,
philpem@5 179 cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>=eigen.dimy())?eigen.dimy()-1:cy+1,
philpem@5 180 cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>=eigen.dimz())?eigen.dimz()-1:cz+1;
philpem@5 181 const T cu = eigen(cx,cy,cz,3), cv = eigen(cx,cy,cz,4), cw = eigen(cx,cy,cz,5);
philpem@5 182
philpem@5 183 align_eigen(px,py,pz); align_eigen(cx,py,pz); align_eigen(nx,py,pz);
philpem@5 184 align_eigen(px,cy,pz); align_eigen(cx,cy,pz); align_eigen(nx,cy,pz);
philpem@5 185 align_eigen(px,ny,pz); align_eigen(cx,ny,pz); align_eigen(nx,ny,pz);
philpem@5 186 align_eigen(px,py,cz); align_eigen(cx,py,cz); align_eigen(nx,py,cz);
philpem@5 187 align_eigen(px,cy,cz); align_eigen(nx,cy,cz);
philpem@5 188 align_eigen(px,ny,cz); align_eigen(cx,ny,cz); align_eigen(nx,ny,cz);
philpem@5 189 align_eigen(px,py,nz); align_eigen(cx,py,nz); align_eigen(nx,py,nz);
philpem@5 190 align_eigen(px,cy,nz); align_eigen(cx,cy,nz); align_eigen(nx,cy,nz);
philpem@5 191 align_eigen(px,ny,nz); align_eigen(cx,ny,nz); align_eigen(nx,ny,nz);
philpem@5 192
philpem@5 193 const T
philpem@5 194 u0 = 0.5f*dl*eigen.linear_atXYZ(X,Y,Z,3),
philpem@5 195 v0 = 0.5f*dl*eigen.linear_atXYZ(X,Y,Z,4),
philpem@5 196 w0 = 0.5f*dl*eigen.linear_atXYZ(X,Y,Z,5),
philpem@5 197 u1 = 0.5f*dl*eigen.linear_atXYZ(X+u0,Y+v0,Z+w0,3),
philpem@5 198 v1 = 0.5f*dl*eigen.linear_atXYZ(X+u0,Y+v0,Z+w0,4),
philpem@5 199 w1 = 0.5f*dl*eigen.linear_atXYZ(X+u0,Y+v0,Z+w0,5),
philpem@5 200 u2 = 0.5f*dl*eigen.linear_atXYZ(X+u1,Y+v1,Z+w1,3),
philpem@5 201 v2 = 0.5f*dl*eigen.linear_atXYZ(X+u1,Y+v1,Z+w1,4),
philpem@5 202 w2 = 0.5f*dl*eigen.linear_atXYZ(X+u1,Y+v1,Z+w1,5),
philpem@5 203 u3 = 0.5f*dl*eigen.linear_atXYZ(X+u2,Y+v2,Z+w2,3),
philpem@5 204 v3 = 0.5f*dl*eigen.linear_atXYZ(X+u2,Y+v2,Z+w2,4),
philpem@5 205 w3 = 0.5f*dl*eigen.linear_atXYZ(X+u2,Y+v2,Z+w2,5);
philpem@5 206 T
philpem@5 207 u = u0/3 + 2*u1/3 + 2*u2/3 + u3/3,
philpem@5 208 v = v0/3 + 2*v1/3 + 2*v2/3 + v3/3,
philpem@5 209 w = w0/3 + 2*w1/3 + 2*w2/3 + w3/3;
philpem@5 210 if (u*pu+v*pv+w*pw<0) { u=-u; v=-v; w=-w; }
philpem@5 211 normU = (float)std::sqrt(u*u+v*v+w*w);
philpem@5 212 const float scal = (u*pu+v*pv+w*pw)/(normU*normpU);
philpem@5 213 if (scal<cmin) stopflag=true;
philpem@5 214
philpem@5 215 X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
philpem@5 216 normpU = normU;
philpem@5 217 l+=dl;
philpem@5 218 }
philpem@5 219 }
philpem@5 220
philpem@5 221 // Backward tracking
philpem@5 222 l = dl; X = (float)X0; Y = (float)Y0; Z = (float)Z0;
philpem@5 223 pu = eigen(X0,Y0,Z0,3);
philpem@5 224 pv = eigen(X0,Y0,Z0,4);
philpem@5 225 pw = eigen(X0,Y0,Z0,5);
philpem@5 226 normpU = (float)std::sqrt(pu*pu+pv*pv+pw*pw);
philpem@5 227 stopflag = false;
philpem@5 228
philpem@5 229 while (!stopflag) {
philpem@5 230 if (X<0 || X>eigen.dimx()-1 || Y<0 || Y>eigen.dimy()-1 || Z<0 || Z>eigen.dimz()-1 ||
philpem@5 231 eigen((int)X,(int)Y,(int)Z,12)<FAmin || l>lmax) stopflag = true;
philpem@5 232 else {
philpem@5 233
philpem@5 234 const int
philpem@5 235 cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>=eigen.dimx())?eigen.dimx()-1:cx+1,
philpem@5 236 cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>=eigen.dimy())?eigen.dimy()-1:cy+1,
philpem@5 237 cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>=eigen.dimz())?eigen.dimz()-1:cz+1;
philpem@5 238 const T cu = eigen(cx,cy,cz,3), cv = eigen(cx,cy,cz,4), cw = eigen(cx,cy,cz,5);
philpem@5 239
philpem@5 240 align_eigen(px,py,pz); align_eigen(cx,py,pz); align_eigen(nx,py,pz);
philpem@5 241 align_eigen(px,cy,pz); align_eigen(cx,cy,pz); align_eigen(nx,cy,pz);
philpem@5 242 align_eigen(px,ny,pz); align_eigen(cx,ny,pz); align_eigen(nx,ny,pz);
philpem@5 243 align_eigen(px,py,cz); align_eigen(cx,py,cz); align_eigen(nx,py,cz);
philpem@5 244 align_eigen(px,cy,cz); align_eigen(nx,cy,cz);
philpem@5 245 align_eigen(px,ny,cz); align_eigen(cx,ny,cz); align_eigen(nx,ny,cz);
philpem@5 246 align_eigen(px,py,nz); align_eigen(cx,py,nz); align_eigen(nx,py,nz);
philpem@5 247 align_eigen(px,cy,nz); align_eigen(cx,cy,nz); align_eigen(nx,cy,nz);
philpem@5 248 align_eigen(px,ny,nz); align_eigen(cx,ny,nz); align_eigen(nx,ny,nz);
philpem@5 249
philpem@5 250 const T
philpem@5 251 u0 = 0.5f*dl*eigen.linear_atXYZ(X,Y,Z,3),
philpem@5 252 v0 = 0.5f*dl*eigen.linear_atXYZ(X,Y,Z,4),
philpem@5 253 w0 = 0.5f*dl*eigen.linear_atXYZ(X,Y,Z,5),
philpem@5 254 u1 = 0.5f*dl*eigen.linear_atXYZ(X+u0,Y+v0,Z+w0,3),
philpem@5 255 v1 = 0.5f*dl*eigen.linear_atXYZ(X+u0,Y+v0,Z+w0,4),
philpem@5 256 w1 = 0.5f*dl*eigen.linear_atXYZ(X+u0,Y+v0,Z+w0,5),
philpem@5 257 u2 = 0.5f*dl*eigen.linear_atXYZ(X+u1,Y+v1,Z+w1,3),
philpem@5 258 v2 = 0.5f*dl*eigen.linear_atXYZ(X+u1,Y+v1,Z+w1,4),
philpem@5 259 w2 = 0.5f*dl*eigen.linear_atXYZ(X+u1,Y+v1,Z+w1,5),
philpem@5 260 u3 = 0.5f*dl*eigen.linear_atXYZ(X+u2,Y+v2,Z+w2,3),
philpem@5 261 v3 = 0.5f*dl*eigen.linear_atXYZ(X+u2,Y+v2,Z+w2,4),
philpem@5 262 w3 = 0.5f*dl*eigen.linear_atXYZ(X+u2,Y+v2,Z+w2,5);
philpem@5 263 T
philpem@5 264 u = u0/3 + 2*u1/3 + 2*u2/3 + u3/3,
philpem@5 265 v = v0/3 + 2*v1/3 + 2*v2/3 + v3/3,
philpem@5 266 w = w0/3 + 2*w1/3 + 2*w2/3 + w3/3;
philpem@5 267 if (u*pu+v*pv+w*pw<0) { u=-u; v=-v; w=-w; }
philpem@5 268 normU = (float)std::sqrt(u*u+v*v+w*w);
philpem@5 269 const float scal = (u*pu+v*pv+w*pw)/(normU*normpU);
philpem@5 270 if (scal<cmin) stopflag=true;
philpem@5 271
philpem@5 272 X-=(pu=u); Y-=(pv=v); Z-=(pw=w);
philpem@5 273 normpU=normU;
philpem@5 274 l+=dl;
philpem@5 275
philpem@5 276 resf.insert(CImg<>::vector(X,Y,Z),0);
philpem@5 277 }
philpem@5 278 }
philpem@5 279
philpem@5 280 return resf.get_append('x');
philpem@5 281 }
philpem@5 282
philpem@5 283 // Main procedure
philpem@5 284 //----------------
philpem@5 285 int main(int argc,char **argv) {
philpem@5 286
philpem@5 287 // Read and init data
philpem@5 288 //--------------------
philpem@5 289 cimg_usage("A viewer of Diffusion-Tensor MRI volumes.");
philpem@5 290 const char *file_i = cimg_option("-i",(char*)0,"Input : Filename of tensor field (volume wxhxdx6)");
philpem@5 291 const char* vsize = cimg_option("-vsize","1x1x1","Input : Voxel aspect");
philpem@5 292 const bool normalize = cimg_option("-normalize",true,"Input : Enable tensor normalization");
philpem@5 293 const char *file_f = cimg_option("-f",(char*)0,"Input : Input fibers\n");
philpem@5 294 const float dl = cimg_option("-dl",0.5f,"Fiber computation : Integration step");
philpem@5 295 const float famin = cimg_option("-famin",0.3f,"Fiber computation : Fractional Anisotropy threshold");
philpem@5 296 const float cmin = cimg_option("-cmin",0.2f,"Fiber computation : Curvature threshold");
philpem@5 297 const float lmin = cimg_option("-lmin",10.0f,"Fiber computation : Minimum length\n");
philpem@5 298 const float lmax = cimg_option("-lmax",1000.0f,"Fiber computation : Maximum length\n");
philpem@5 299 const float tfact = cimg_option("-tfact",1.2f,"Display : Tensor size factor");
philpem@5 300 const char *bgcolor = cimg_option("-bg","0,0,0","Display : Background color");
philpem@5 301 unsigned int bgr=0, bgg=0, bgb=0;
philpem@5 302 std::sscanf(bgcolor,"%u%*c%u%*c%u",&bgr,&bgg,&bgb);
philpem@5 303
philpem@5 304 CImg<> tensors;
philpem@5 305 if (file_i) {
philpem@5 306 std::fprintf(stderr,"\n- Loading tensors '%s'",cimg::basename(file_i));
philpem@5 307 tensors.load(file_i);
philpem@5 308 } else {
philpem@5 309 // Create a synthetic tensor field here
philpem@5 310 std::fprintf(stderr,"\n- No input files : Creating a synthetic tensor field");
philpem@5 311 tensors.assign(32,32,32,6);
philpem@5 312 const CImg<> Id = CImg<>::diagonal(0.3f,0.3f,0.3f);
philpem@5 313 cimg_forXYZ(tensors,x,y,z) {
philpem@5 314 const float
philpem@5 315 u = x-tensors.dimx()/2.0f,
philpem@5 316 v = y-tensors.dimy()/2.0f,
philpem@5 317 w = z-tensors.dimz()/2.0f,
philpem@5 318 norm = (float)std::sqrt(1e-5f+u*u+v*v+w*w),
philpem@5 319 nu = u/norm, nv = v/norm, nw = w/norm;
philpem@5 320 const CImg<>
philpem@5 321 dir1 = CImg<>::vector(nu,nv,nw),
philpem@5 322 dir2 = CImg<>::vector(-nv,nu,nw),
philpem@5 323 dir3 = CImg<>::vector(nw*(nv-nu),-nw*(nu+nv),nu*nu+nv*nv);
philpem@5 324 tensors.set_tensor_at(2.0*dir1*dir1.get_transpose() +
philpem@5 325 1.0*dir2*dir2.get_transpose() +
philpem@5 326 0.7*dir3*dir3.get_transpose(),
philpem@5 327 x,y,z);
philpem@5 328 }
philpem@5 329 }
philpem@5 330 float voxw=1,voxh=1,voxd=1;
philpem@5 331 std::sscanf(vsize,"%f%*c%f%*c%f",&voxw,&voxh,&voxd);
philpem@5 332
philpem@5 333 std::fprintf(stderr," : %ux%ux%u image, voxsize=%gx%gx%g.",
philpem@5 334 tensors.dimx(),tensors.dimy(),tensors.dimz(),
philpem@5 335 voxw,voxh,voxd);
philpem@5 336
philpem@5 337
philpem@5 338 CImgList<> fibers;
philpem@5 339 if (file_f) {
philpem@5 340 std::fprintf(stderr,"\n- Loading fibers '%s'.",cimg::basename(file_f));
philpem@5 341 fibers.load(file_f);
philpem@5 342 }
philpem@5 343
philpem@5 344 const CImg<unsigned char> fiber_palette =
philpem@5 345 CImg<>(2,1,1,3).fill(200,255,0,255,0,200).RGBtoHSV().resize(256,1,1,3,3).HSVtoRGB();
philpem@5 346
philpem@5 347 // Compute eigen elements
philpem@5 348 //------------------------
philpem@5 349 std::fprintf(stderr,"\n- Compute eigen elements.");
philpem@5 350 CImg<unsigned char> coloredFA(tensors.dimx(),tensors.dimy(),tensors.dimz(),3);
philpem@5 351 CImg<> eigen(tensors.dimx(),tensors.dimy(),tensors.dimz(),13);
philpem@5 352 CImg<> val,vec;
philpem@5 353 float eigmax = 0;
philpem@5 354 cimg_forXYZ(tensors,x,y,z) {
philpem@5 355 tensors.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
philpem@5 356 eigen(x,y,z,0) = val[0]; eigen(x,y,z,1) = val[1]; eigen(x,y,z,2) = val[2];
philpem@5 357 if (val[0]<0) val[0]=0;
philpem@5 358 if (val[1]<0) val[1]=0;
philpem@5 359 if (val[2]<0) val[2]=0;
philpem@5 360 if (val[0]>eigmax) eigmax = val[0];
philpem@5 361 eigen(x,y,z,3) = vec(0,0); eigen(x,y,z,4) = vec(0,1); eigen(x,y,z,5) = vec(0,2);
philpem@5 362 eigen(x,y,z,6) = vec(1,0); eigen(x,y,z,7) = vec(1,1); eigen(x,y,z,8) = vec(1,2);
philpem@5 363 eigen(x,y,z,9) = vec(2,0); eigen(x,y,z,10) = vec(2,1); eigen(x,y,z,11) = vec(2,2);
philpem@5 364 const float fa = get_FA(val[0],val[1],val[2]);
philpem@5 365 eigen(x,y,z,12) = fa;
philpem@5 366 const int
philpem@5 367 r = (int)cimg::min(255.0f,1.5f*cimg::abs(255*fa*vec(0,0))),
philpem@5 368 g = (int)cimg::min(255.0f,1.5f*cimg::abs(255*fa*vec(0,1))),
philpem@5 369 b = (int)cimg::min(255.0f,1.5f*cimg::abs(255*fa*vec(0,2)));
philpem@5 370 coloredFA(x,y,z,0) = (unsigned char)r;
philpem@5 371 coloredFA(x,y,z,1) = (unsigned char)g;
philpem@5 372 coloredFA(x,y,z,2) = (unsigned char)b;
philpem@5 373 }
philpem@5 374 tensors.assign();
philpem@5 375 std::fprintf(stderr,"\n- Maximum diffusivity = %g, Maximum FA = %g",eigmax,eigen.get_shared_channel(12).max());
philpem@5 376 if (normalize) {
philpem@5 377 std::fprintf(stderr,"\n- Normalize tensors.");
philpem@5 378 eigen.get_shared_channels(0,2)/=eigmax;
philpem@5 379 }
philpem@5 380
philpem@5 381 // Init display and begin user interaction
philpem@5 382 //-----------------------------------------
philpem@5 383 std::fprintf(stderr,"\n- Open user window.");
philpem@5 384 CImgDisplay disp(256,256,"DTMRI Viewer",0);
philpem@5 385 CImgDisplay disp3d(800,600,"3D Local View",0,false,true);
philpem@5 386 unsigned int XYZ[3];
philpem@5 387 XYZ[0] = eigen.dimx()/2; XYZ[1] = eigen.dimy()/2; XYZ[2] = eigen.dimz()/2;
philpem@5 388
philpem@5 389 while (!disp.is_closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC) {
philpem@5 390 const CImg<int> s = coloredFA.get_select(disp,2,XYZ);
philpem@5 391 if (!disp.is_closed) switch (disp.key) {
philpem@5 392
philpem@5 393 // Open 3D visualization window
philpem@5 394 //-----------------------------
philpem@5 395 case cimg::keyA:
philpem@5 396 case 0: {
philpem@5 397 unsigned char white[1] = { 255 };
philpem@5 398 disp3d.display(CImg<unsigned char>(disp3d.dimx(),disp3d.dimy(),1,1,0).draw_text(10,10,"Please wait...",white)).show();
philpem@5 399 int xm,ym,zm,xM,yM,zM;
philpem@5 400 if (!disp.key) { xm=s[0]; ym=s[1]; zm=s[2]; xM=s[3]; yM=s[4]; zM=s[5]; }
philpem@5 401 else { xm=ym=zm=0; xM=eigen.dimx()-1; yM=eigen.dimy()-1; zM=eigen.dimy()-1; }
philpem@5 402 const CImg<> img = eigen.get_crop(xm,ym,zm,xM,yM,zM);
philpem@5 403 CImgList<> points;
philpem@5 404 CImgList<unsigned int> primitives;
philpem@5 405 CImgList<unsigned char> colors;
philpem@5 406
philpem@5 407 // Add ellipsoids to the 3D scene
philpem@5 408 int X = img.dimx()/2, Y = img.dimy()/2, Z = img.dimz()/2;
philpem@5 409 { cimg_forXY(img,x,y) insert_ellipsoid(img.get_vector_at(x,y,Z),(float)x,(float)y,(float)Z,tfact,voxw,voxh,voxd,points,primitives,colors,10,6); }
philpem@5 410 { cimg_forXZ(img,x,z) insert_ellipsoid(img.get_vector_at(x,Y,z),(float)x,(float)Y,(float)z,tfact,voxw,voxh,voxd,points,primitives,colors,10,6); }
philpem@5 411 { cimg_forYZ(img,y,z) insert_ellipsoid(img.get_vector_at(X,y,z),(float)X,(float)y,(float)z,tfact,voxw,voxh,voxd,points,primitives,colors,10,6); }
philpem@5 412
philpem@5 413 // Add computed fibers to the 3D scene
philpem@5 414 const CImg<> veigen = eigen.get_crop(xm,ym,zm,xM,yM,zM);
philpem@5 415 cimglist_for(fibers,l) {
philpem@5 416 const CImg<>& fiber = fibers[l];
philpem@5 417 if (fiber.dimx()) insert_fiber(fiber,eigen,fiber_palette,
philpem@5 418 xm,ym,zm,voxw,voxh,voxd,
philpem@5 419 points,primitives,colors);
philpem@5 420 }
philpem@5 421
philpem@5 422 // Display 3D object
philpem@5 423 CImg<unsigned char> visu = CImg<unsigned char>(3,disp3d.dimx(),disp3d.dimy(),1,0).fill(bgr,bgg,bgb).permute_axes("yzvx");
philpem@5 424 bool stopflag = false;
philpem@5 425 while (!disp3d.is_closed && !stopflag) {
philpem@5 426 visu.display_object3d(disp3d,points,primitives,colors,true,4,-1,false,800,0.05f,1.0f);
philpem@5 427 switch (disp3d.key) {
philpem@5 428 case cimg::keyM: { // Create movie
philpem@5 429 std::fprintf(stderr,"\n- Movie mode.\n");
philpem@5 430 const unsigned int N = 256;
philpem@5 431 CImg<> pts = points.get_append('x');
philpem@5 432 CImgList<> cpoints(points);
philpem@5 433 CImg<> x = pts.get_shared_line(0), y = pts.get_shared_line(1), z = pts.get_shared_line(2);
philpem@5 434 float
philpem@5 435 xm, xM = x.maxmin(xm),
philpem@5 436 ym, yM = y.maxmin(ym),
philpem@5 437 zm, zM = z.maxmin(zm),
philpem@5 438 ratio = 2.0f*cimg::min(visu.dimx(),visu.dimy())/(3.0f*cimg::max(xM-xm,yM-ym,zM-zm)),
philpem@5 439 dx = 0.5f*(xM+xm), dy = 0.5f*(yM+ym), dz = 0.5f*(zM+zm);
philpem@5 440 cimglist_for(points,l) {
philpem@5 441 cpoints(l,0) = (float)((points(l,0)-dx)*ratio);
philpem@5 442 cpoints(l,1) = (float)((points(l,1)-dy)*ratio);
philpem@5 443 cpoints(l,2) = (float)((points(l,2)-dz)*ratio);
philpem@5 444 }
philpem@5 445
philpem@5 446 for (unsigned int i=0; i<N; i++) {
philpem@5 447 std::fprintf(stderr,"\r- Frame %u/%u.",i,N);
philpem@5 448 const float alpha = (float)(i*2*cimg::valuePI/N);
philpem@5 449 const CImg<> rot = CImg<>::rotation_matrix(0,1,0,alpha)*CImg<>::rotation_matrix(1,0,0,1.30f);
philpem@5 450 CImgList<> rotated(cpoints);
philpem@5 451 cimglist_for(rotated,l) rotated[l] = rot*cpoints[l];
philpem@5 452 visu.fill(0).draw_object3d(visu.dimx()/2.0f,visu.dimy()/2.0f,-500.0f,rotated,primitives,colors,
philpem@5 453 4,false,800.0f,visu.dimx()/2.0f,visu.dimy()/2.0f,-800.0f,0.05f,1.0f).display(disp3d);
philpem@5 454 visu.save("frame.png",i);
philpem@5 455 }
philpem@5 456 visu.fill(0);
philpem@5 457 } break;
philpem@5 458 default: stopflag = true;
philpem@5 459 }
philpem@5 460 }
philpem@5 461 if (disp3d.is_fullscreen) disp3d.toggle_fullscreen().resize(800,600).close();
philpem@5 462 } break;
philpem@5 463
philpem@5 464 // Compute region statistics
philpem@5 465 //---------------------------
philpem@5 466 case cimg::keyR: {
philpem@5 467 std::fprintf(stderr,"\n- Statistics computation. Select region."); std::fflush(stderr);
philpem@5 468 const CImg<int> s = coloredFA.get_select(disp,2,XYZ);
philpem@5 469 int xm,ym,zm,xM,yM,zM;
philpem@5 470 if (!disp.key) { xm=s[0]; ym=s[1]; zm=s[2]; xM=s[3]; yM=s[4]; zM=s[5]; }
philpem@5 471 else { xm=ym=zm=0; xM=eigen.dimx()-1; yM=eigen.dimy()-1; zM=eigen.dimy()-1; }
philpem@5 472 const CImg<> img = eigen.get_crop(xm,ym,zm,xM,yM,zM);
philpem@5 473 std::fprintf(stderr,"\n- Mean diffusivity = %g, Mean FA = %g\n",
philpem@5 474 eigen.get_shared_channel(0).mean(),
philpem@5 475 eigen.get_shared_channel(12).mean());
philpem@5 476 } break;
philpem@5 477
philpem@5 478 // Track fiber bundle (single region)
philpem@5 479 //----------------------------------
philpem@5 480 case cimg::keyF: {
philpem@5 481 std::fprintf(stderr,"\n- Tracking mode (single region). Select starting region.\n"); std::fflush(stderr);
philpem@5 482 const CImg<int> s = coloredFA.get_select(disp,2,XYZ);
philpem@5 483 const unsigned int N = fibers.size;
philpem@5 484 for (int z=s[2]; z<=s[5]; z++)
philpem@5 485 for (int y=s[1]; y<=s[4]; y++)
philpem@5 486 for (int x=s[0]; x<=s[3]; x++) {
philpem@5 487 const CImg<> fiber = get_fibertrack(eigen,x,y,z,lmax,dl,famin,cmin);
philpem@5 488 if (fiber.dimx()>lmin) {
philpem@5 489 std::fprintf(stderr,"\rFiber %u : Starting from (%d,%d,%d)\t\t",fibers.size,x,y,z);
philpem@5 490 fibers.insert(fiber);
philpem@5 491 }
philpem@5 492 }
philpem@5 493 std::fprintf(stderr,"\n- %u fiber(s) added (total %u).",fibers.size-N,fibers.size);
philpem@5 494 } break;
philpem@5 495
philpem@5 496 // Track fiber bundle (double regions)
philpem@5 497 //------------------------------------
philpem@5 498 case cimg::keyG: {
philpem@5 499 std::fprintf(stderr,"\n- Tracking mode (double region). Select starting region."); std::fflush(stderr);
philpem@5 500 const CImg<int> s = coloredFA.get_select(disp,2,XYZ);
philpem@5 501 std::fprintf(stderr," Select ending region."); std::fflush(stderr);
philpem@5 502 const CImg<int> ns = coloredFA.get_select(disp,2,XYZ);
philpem@5 503 const unsigned int N = fibers.size;
philpem@5 504
philpem@5 505 // Track from start to end
philpem@5 506 for (int z=s[2]; z<=s[5]; z++)
philpem@5 507 for (int y=s[1]; y<=s[4]; y++)
philpem@5 508 for (int x=s[0]; x<=s[3]; x++) {
philpem@5 509 const CImg<> fiber = get_fibertrack(eigen,x,y,z,lmax,dl,famin,cmin);
philpem@5 510 if (fiber.dimx()>lmin) {
philpem@5 511 bool valid_fiber = false;
philpem@5 512 cimg_forX(fiber,k) {
philpem@5 513 const int fx = (int)fiber(k,0), fy = (int)fiber(k,1), fz = (int)fiber(k,2);
philpem@5 514 if (fx>=ns[0] && fx<=ns[3] &&
philpem@5 515 fy>=ns[1] && fy<=ns[4] &&
philpem@5 516 fz>=ns[2] && fz<=ns[5]) valid_fiber = true;
philpem@5 517 }
philpem@5 518 if (valid_fiber) fibers.insert(fiber);
philpem@5 519 }
philpem@5 520 }
philpem@5 521
philpem@5 522 // Track from end to start
philpem@5 523 { for (int z=ns[2]; z<=ns[5]; z++)
philpem@5 524 for (int y=ns[1]; y<=ns[4]; y++)
philpem@5 525 for (int x=ns[0]; x<=ns[3]; x++) {
philpem@5 526 const CImg<> fiber = get_fibertrack(eigen,x,y,z,lmax,dl,famin,cmin);
philpem@5 527 if (fiber.dimx()>lmin) {
philpem@5 528 bool valid_fiber = false;
philpem@5 529 cimg_forX(fiber,k) {
philpem@5 530 const int fx = (int)fiber(k,0), fy = (int)fiber(k,1), fz = (int)fiber(k,2);
philpem@5 531 if (fx>=s[0] && fx<=s[3] &&
philpem@5 532 fy>=s[1] && fy<=s[4] &&
philpem@5 533 fz>=s[2] && fz<=s[5]) valid_fiber = true;
philpem@5 534 }
philpem@5 535 if (valid_fiber) {
philpem@5 536 std::fprintf(stderr,"\rFiber %u : Starting from (%d,%d,%d)\t\t",fibers.size,x,y,z);
philpem@5 537 fibers.insert(fiber);
philpem@5 538 }
philpem@5 539 }
philpem@5 540 }}
philpem@5 541
philpem@5 542 std::fprintf(stderr," %u fiber(s) added (total %u).",fibers.size-N,fibers.size);
philpem@5 543 } break;
philpem@5 544
philpem@5 545 // Clear fiber bundle
philpem@5 546 //-------------------
philpem@5 547 case cimg::keyC: {
philpem@5 548 std::fprintf(stderr,"\n- Fibers removed.");
philpem@5 549 fibers.assign();
philpem@5 550 } break;
philpem@5 551
philpem@5 552 // Save fibers
philpem@5 553 //-------------
philpem@5 554 case cimg::keyS: {
philpem@5 555 fibers.save("fibers.cimg");
philpem@5 556 std::fprintf(stderr,"\n- Fibers saved.");
philpem@5 557 } break;
philpem@5 558
philpem@5 559 }
philpem@5 560 }
philpem@5 561
philpem@5 562 std::fprintf(stderr,"\n- Exit.\n\n\n");
philpem@5 563 return 0;
philpem@5 564 }