Mon, 03 Aug 2009 14:09:20 +0100
added P-touch decoder source
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 | } |