|
1 | 1 |
|
| 2 | +@inline function _det(r11, r12, r13, r21, r22, r23, r31, r32, r33) |
| 3 | + r11 * r22 * r33 - |
| 4 | + r11 * r32 * r23 - |
| 5 | + r21 * r12 * r33 + |
| 6 | + r21 * r32 * r13 + |
| 7 | + r31 * r12 * r23 - |
| 8 | + r31 * r22 * r13 |
| 9 | +end |
| 10 | + |
| 11 | +@inline function _mul_trace( |
| 12 | + x11, x12, x13, x21, x22, x23, x31, x32, x33, |
| 13 | + y11, y12, y13, y21, y22, y23, y31, y32, y33 |
| 14 | +) |
| 15 | + |
| 16 | + return (x11 * y11 + x12 * y21 + x13 * y31) + # z11 |
| 17 | + (x21 * y12 + x22 * y22 + x23 * y32) + # z22 |
| 18 | + (x31 * y13 + x32 * y23 + x33 * y33) # z33 |
| 19 | +end |
| 20 | + |
2 | 21 | get_sform(x::NIVolume) = get_sform(x.header)
|
3 | 22 | function get_sform(hdr::NIfTI1Header)
|
4 | 23 | if hdr.sform_code > 0
|
@@ -89,3 +108,233 @@ function setaffine(h::NIfTI1Header, affine::Array{T,2}) where {T}
|
89 | 108 | h
|
90 | 109 | end
|
91 | 110 |
|
| 111 | +""" |
| 112 | + orientation(img)::Tuple{Symbol,Symbol,Symbol} |
| 113 | +
|
| 114 | +Returns a tuple providing the orientation of a NIfTI image. |
| 115 | +""" |
| 116 | +orientation(x) = orientation(x.header) |
| 117 | +function orientation(hdr::NIfTI1Header) |
| 118 | + if hdr.sform_code > 0 |
| 119 | + return @inbounds _dir2ori( |
| 120 | + hdr.srow_x[1], hdr.srow_x[2], hdr.srow_x[3], |
| 121 | + hdr.srow_y[1], hdr.srow_y[2], hdr.srow_y[3], |
| 122 | + hdr.srow_z[1], hdr.srow_z[2], hdr.srow_z[3] |
| 123 | + ) |
| 124 | + elseif hdr.qform_code <= 0 |
| 125 | + return @inbounds _dir2ori( |
| 126 | + hdr.pixdim[2], 0, 0, |
| 127 | + 0, hdr.pixdim[3], 0, |
| 128 | + 0, 0, hdr.pixdim[4] |
| 129 | + ) |
| 130 | + else |
| 131 | + dx = hdr.pixdim[2] |
| 132 | + dy = hdr.pixdim[3] |
| 133 | + # aka qfac left handedness |
| 134 | + if hdr.pixdim[1] < 0 |
| 135 | + dz = -hdr.pixdim[4] |
| 136 | + else |
| 137 | + dz = hdr.pixdim[4] |
| 138 | + end |
| 139 | + b = hdr.quatern_b |
| 140 | + c = hdr.quatern_c |
| 141 | + d = hdr.quatern_d |
| 142 | + b2 = b*b |
| 143 | + c2 = c*c |
| 144 | + d2 = d*d |
| 145 | + a = 1 - b2 - c2 - d2 |
| 146 | + if a < 1.e-7 |
| 147 | + a = 1 / sqrt(b2 + c2 + d2) |
| 148 | + b *= a |
| 149 | + c *= a |
| 150 | + d *= a # normalize (b,c,d) vector |
| 151 | + a = zero(a) # a = 0 ==> 180 degree rotation |
| 152 | + else |
| 153 | + a = sqrt(a) # angle = 2*arccos(a) |
| 154 | + end |
| 155 | + return _dir2ori( |
| 156 | + (a*a+b*b-c*c-d*d)*dx, (2*b*c-2*a*d)*dy, (2*b*d+2*a*c)*dz, |
| 157 | + (2*b*c+2*a*d)*dx, (a*a+c*c-b*b-d*d)*dy, (2*c*d-2*a*b)*dz, |
| 158 | + (2*b*d-2*a*c)*dx, (2*c*d+2*a*b)*dy, (a*a+d*d-c*c-b*b)*dz, |
| 159 | + ) |
| 160 | + end |
| 161 | +end |
| 162 | + |
| 163 | +_encoding_name(x) = _encoding_name(Int(x)) |
| 164 | +@inline function _encoding_name(x::Int) |
| 165 | + if x === 1 |
| 166 | + return :left |
| 167 | + elseif x === -1 |
| 168 | + return :right |
| 169 | + elseif x === 2 |
| 170 | + return :posterior |
| 171 | + elseif x === -2 |
| 172 | + return :anterior |
| 173 | + elseif x === 3 |
| 174 | + return :inferior |
| 175 | + elseif x === -3 |
| 176 | + return :superior |
| 177 | + else |
| 178 | + error("$x does not map to a dimension name.") |
| 179 | + end |
| 180 | +end |
| 181 | + |
| 182 | +function _dir2ori(xi, xj, xk, yi, yj, yk, zi, zj, zk) |
| 183 | + # Normalize column vectors to get unit vectors along each ijk-axis |
| 184 | + # normalize i axis |
| 185 | + val = sqrt(xi*xi + yi*yi + zi*zi) |
| 186 | + if val == 0 |
| 187 | + error("Invalid rotation directions.") |
| 188 | + end |
| 189 | + xi /= val |
| 190 | + yi /= val |
| 191 | + zi /= val |
| 192 | + |
| 193 | + # normalize j axis |
| 194 | + val = sqrt(xj*xj + yj*yj + zj*zj) |
| 195 | + if val == 0 |
| 196 | + error("Invalid rotation directions.") |
| 197 | + end |
| 198 | + xj /= val |
| 199 | + yj /= val |
| 200 | + zj /= val |
| 201 | + |
| 202 | + # orthogonalize j axis to i axis, if needed |
| 203 | + val = xi*xj + yi*yj + zi* zj # dot product between i and j |
| 204 | + if abs(val) > .0001 |
| 205 | + xj -= val*xi |
| 206 | + yj -= val*yi |
| 207 | + zj -= val*zi |
| 208 | + |
| 209 | + val = sqrt(xj*xj + yj*yj + zj*zj) # must renormalize |
| 210 | + if val == 0 |
| 211 | + error("The first and second dimensions cannot be parallel.") |
| 212 | + end |
| 213 | + xj /= val |
| 214 | + yj /= val |
| 215 | + zj /= val |
| 216 | + end |
| 217 | + |
| 218 | + # normalize k axis; if it is zero, make it the cross product i x j |
| 219 | + val = sqrt(xk*xk + yk*yk + zk*zk) |
| 220 | + if val == 0 |
| 221 | + xk = yi*zj-zi*yj |
| 222 | + yk = zi*xj-zj*xi |
| 223 | + zk = xi*yj-yi*xj |
| 224 | + else |
| 225 | + xk = xk/val |
| 226 | + yk = yk/val |
| 227 | + zk = zk/val |
| 228 | + end |
| 229 | + |
| 230 | + # orthogonalize k to i |
| 231 | + val = xi*xk + yi*yk + zi*zk # dot product between i and k |
| 232 | + if abs(val) > 0.0001 |
| 233 | + xk -= val*xi |
| 234 | + yk -= val*yi |
| 235 | + zk -= val*zi |
| 236 | + |
| 237 | + # must renormalize |
| 238 | + val = sqrt(xk*xk + yk*yk + zk*zk) |
| 239 | + if val == 0 |
| 240 | + return 0 # I think this is suppose to be an error output |
| 241 | + end |
| 242 | + xk /= val |
| 243 | + yk /= val |
| 244 | + zk /= val |
| 245 | + end |
| 246 | + |
| 247 | + # orthogonalize k to j */ |
| 248 | + val = xj*xk + yj*yk + zj*zk # dot product between j and k |
| 249 | + if abs(val) > 0.0001 |
| 250 | + xk -= val*xj |
| 251 | + yk -= val*yj |
| 252 | + zk -= val*zj |
| 253 | + |
| 254 | + val = sqrt(xk*xk + yk*yk + zk*zk) |
| 255 | + if val == 0 |
| 256 | + return 0 # bad |
| 257 | + end |
| 258 | + xk /= val |
| 259 | + yk /= val |
| 260 | + zk /= val |
| 261 | + end |
| 262 | + |
| 263 | + # at this point Q is the rotation matrix from the (i,j,k) to (x,y,z) axes |
| 264 | + detQ = _det(xi, xj, xk, yi, yj, yk, zi, zj, zk) |
| 265 | + # if( detQ == 0.0 ) return ; /* shouldn't happen unless user is a DUFIS */ |
| 266 | + |
| 267 | + # Build and test all possible +1/-1 coordinate permutation matrices P; |
| 268 | + # then find the P such that the rotation matrix M=PQ is closest to the |
| 269 | + # identity, in the sense of M having the smallest total rotation angle. |
| 270 | + |
| 271 | + # Despite the formidable looking 6 nested loops, there are |
| 272 | + # only 3*3*3*2*2*2 = 216 passes, which will run very quickly. |
| 273 | + vbest = -666 |
| 274 | + ibest = pbest=qbest=rbest= 1 |
| 275 | + jbest = 2 |
| 276 | + kbest = 3 |
| 277 | + for (i, j, k) in ((1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)) |
| 278 | + for p in (-1, 1) # p,q,r are -1 or +1 |
| 279 | + for q in (-1, 1) # and go into rows 1,2,3 |
| 280 | + for r in (-1, 1) |
| 281 | + p11, p12, p13 = _nval_other_zero(i, p) |
| 282 | + p21, p22, p23 = _nval_other_zero(j, q) |
| 283 | + p31, p32, p33 = _nval_other_zero(k, r) |
| 284 | + #= |
| 285 | + P[1,i] = p |
| 286 | + P[2,j] = q |
| 287 | + P[3,k] = r |
| 288 | + detP = det(P) # sign of permutation |
| 289 | + =# |
| 290 | + detP = _det(p11, p12, p13, p21, p22, p23, p31, p32, p33) |
| 291 | + # doesn't match sign of Q |
| 292 | + if detP * detQ >= 0.0 |
| 293 | + # angle of M rotation = 2.0 * acos(0.5 * sqrt(1.0 + trace(M))) |
| 294 | + # we want largest trace(M) == smallest angle == M nearest to I |
| 295 | + val = _mul_trace( |
| 296 | + p11, p12, p13, p21, p22, p23, p31, p32, p33, |
| 297 | + xi, xj, xk, yi, yj, yk, zi, zj, zk |
| 298 | + ) |
| 299 | + if val > vbest |
| 300 | + vbest = val |
| 301 | + ibest = i |
| 302 | + jbest = j |
| 303 | + kbest = k |
| 304 | + pbest = p |
| 305 | + qbest = q |
| 306 | + rbest = r |
| 307 | + end |
| 308 | + end |
| 309 | + end |
| 310 | + end |
| 311 | + end |
| 312 | + end |
| 313 | + # At this point ibest is 1 or 2 or 3; pbest is -1 or +1; etc. |
| 314 | + |
| 315 | + # The matrix P that corresponds is the best permutation approximation |
| 316 | + # to Q-inverse; that is, P (approximately) takes (x,y,z) coordinates |
| 317 | + # to the (i,j,k) axes. |
| 318 | + |
| 319 | + # For example, the first row of P (which contains pbest in column ibest) |
| 320 | + # determines the way the i axis points relative to the anatomical |
| 321 | + # (x,y,z) axes. If ibest is 2, then the i axis is along the y axis, |
| 322 | + # which is direction P2A (if pbest > 0) or A2P (if pbest < 0). |
| 323 | + |
| 324 | + # So, using ibest and pbest, we can assign the output code for |
| 325 | + # the i axis. Mutatis mutandis for the j and k axes, of course. |
| 326 | + |
| 327 | + return (_encoding_name(ibest*pbest), _encoding_name(jbest*qbest), _encoding_name(kbest*rbest)) |
| 328 | +end |
| 329 | + |
| 330 | +@inline function _nval_other_zero(n, val) |
| 331 | + if n === 1 |
| 332 | + return val, 0, 0 |
| 333 | + elseif n === 2 |
| 334 | + return 0, val, 0 |
| 335 | + else |
| 336 | + return 0, 0, val |
| 337 | + end |
| 338 | +end |
| 339 | + |
| 340 | + |
0 commit comments