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