1 |
|
|
/* |
2 |
|
|
Teem: Tools to process and visualize scientific data and images . |
3 |
|
|
Copyright (C) 2013, 2012, 2011, 2010, 2009 University of Chicago |
4 |
|
|
Copyright (C) 2008, 2007, 2006, 2005 Gordon Kindlmann |
5 |
|
|
Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998 University of Utah |
6 |
|
|
|
7 |
|
|
This library is free software; you can redistribute it and/or |
8 |
|
|
modify it under the terms of the GNU Lesser General Public License |
9 |
|
|
(LGPL) as published by the Free Software Foundation; either |
10 |
|
|
version 2.1 of the License, or (at your option) any later version. |
11 |
|
|
The terms of redistributing and/or modifying this software also |
12 |
|
|
include exceptions to the LGPL that facilitate static linking. |
13 |
|
|
|
14 |
|
|
This library is distributed in the hope that it will be useful, |
15 |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 |
|
|
Lesser General Public License for more details. |
18 |
|
|
|
19 |
|
|
You should have received a copy of the GNU Lesser General Public License |
20 |
|
|
along with this library; if not, write to Free Software Foundation, Inc., |
21 |
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 |
|
|
*/ |
23 |
|
|
|
24 |
|
|
#include "ten.h" |
25 |
|
|
#include "privateTen.h" |
26 |
|
|
|
27 |
|
|
#define INFO "Generate twisting helical tensor field" |
28 |
|
|
static const char *_tend_helixInfoL = |
29 |
|
|
(INFO |
30 |
|
|
". The main utility of such a field is to debug handling of coordinate " |
31 |
|
|
"systems in tensor field visualization. The \"space directions\" and " |
32 |
|
|
"\"space origin\" fields of the NRRD header determines the mapping from " |
33 |
|
|
"coordinates in the index space of the image to coordinates in the " |
34 |
|
|
"world space in which the image is " |
35 |
|
|
"sampled. The \"measurement frame\" field determines the mapping from " |
36 |
|
|
"the coordinates of the tensor itself, to coordinates of the world space. " |
37 |
|
|
"When these are correctly handled, the " |
38 |
|
|
"region of high anisotropy is a right-handed helix (same as DNA). " |
39 |
|
|
"Using differing axes sizes (via \"-s\") helps make sure that the " |
40 |
|
|
"raster ordering of axes is correct. In addition, the tensors twist " |
41 |
|
|
"relative to the helix, which exposes handling of the measurement frame. " |
42 |
|
|
"If you trace paths guided by the principal eigenvector of the tensors, " |
43 |
|
|
"along the surface of the helical cylinder, you get another " |
44 |
|
|
"right-handed helix, as if the the tensor field is modeling the result " |
45 |
|
|
"if twisting a set of fibers into single-stranded helical bundle. "); |
46 |
|
|
|
47 |
|
|
void |
48 |
|
|
tend_helixDoit(Nrrd *nout, double bnd, |
49 |
|
|
double orig[3], double i2w[9], double mf[9], |
50 |
|
|
double r, double R, double S, double angle, int incrtwist, |
51 |
|
|
double ev[3], double bgEval, int verbose) { |
52 |
|
|
int sx, sy, sz, xi, yi, zi; |
53 |
|
|
double th, t0, t1, t2, t3, v1, v2, |
54 |
|
|
wpos[3], vpos[3], mfT[9], |
55 |
|
|
W2H[9], H2W[9], H2C[9], C2H[9], fv[3], rv[3], uv[3], mA[9], mB[9], inside, |
56 |
|
|
tmp[3], len; |
57 |
|
|
float *out; |
58 |
|
|
|
59 |
|
16 |
sx = nout->axis[1].size; |
60 |
|
8 |
sy = nout->axis[2].size; |
61 |
|
8 |
sz = nout->axis[3].size; |
62 |
|
8 |
out = (float*)nout->data; |
63 |
|
8 |
ELL_3M_TRANSPOSE(mfT, mf); |
64 |
✓✓ |
768 |
for (zi=0; zi<sz; zi++) { |
65 |
✗✓ |
376 |
if (verbose) { |
66 |
|
|
fprintf(stderr, "zi = %d/%d\n", zi, sz); |
67 |
|
|
} |
68 |
✓✓ |
35344 |
for (yi=0; yi<sy; yi++) { |
69 |
✓✓ |
1591232 |
for (xi=0; xi<sx; xi++) { |
70 |
|
778320 |
ELL_3V_SET(tmp, xi, yi, zi); |
71 |
|
778320 |
ELL_3MV_MUL(vpos, i2w, tmp); |
72 |
|
778320 |
ELL_3V_INCR(vpos, orig); |
73 |
|
|
|
74 |
|
|
#define WPOS(pos, th) ELL_3V_SET((pos),R*cos(th), R*sin(th), S*(th)/(2*AIR_PI)) |
75 |
|
|
#define VAL(th) (WPOS(wpos, th), ELL_3V_DIST(wpos, vpos)) |
76 |
|
|
#define RR 0.61803399 |
77 |
|
|
#define CC (1.0-RR) |
78 |
|
|
#define SHIFT3(a,b,c,d) (a)=(b); (b)=(c); (c)=(d) |
79 |
|
|
#define SHIFT2(a,b,c) (a)=(b); (b)=(c) |
80 |
|
|
|
81 |
|
778320 |
th = atan2(vpos[1], vpos[0]); |
82 |
|
778320 |
th += 2*AIR_PI*floor(0.5 + vpos[2]/S - th/(2*AIR_PI)); |
83 |
✓✓ |
778320 |
if (S*th/(2*AIR_PI) > vpos[2]) { |
84 |
|
389160 |
t0 = th - AIR_PI; t3 = th; |
85 |
|
389160 |
} else { |
86 |
|
389160 |
t0 = th; t3 = th + AIR_PI; |
87 |
|
|
} |
88 |
|
778320 |
t1 = RR*t0 + CC*t3; |
89 |
|
778320 |
t2 = CC*t0 + RR*t3; |
90 |
|
778320 |
v1 = VAL(t1); |
91 |
|
778320 |
v2 = VAL(t2); |
92 |
✓✓ |
23577680 |
while ( t3-t0 > 0.000001*(AIR_ABS(t1)+AIR_ABS(t2)) ) { |
93 |
✓✓ |
22021040 |
if (v1 < v2) { |
94 |
|
11009664 |
SHIFT3(t3, t2, t1, CC*t0 + RR*t2); |
95 |
|
11009664 |
SHIFT2(v2, v1, VAL(t1)); |
96 |
|
11009664 |
} else { |
97 |
|
11011376 |
SHIFT3(t0, t1, t2, RR*t1 + CC*t3); |
98 |
|
11011376 |
SHIFT2(v1, v2, VAL(t2)); |
99 |
|
|
} |
100 |
|
|
} |
101 |
|
|
/* t1 (and t2) are now the th for which the point on the helix |
102 |
|
|
(R*cos(th), R*sin(th), S*(th)/(2*AIR_PI)) is closest to vpos */ |
103 |
|
|
|
104 |
|
778320 |
WPOS(wpos, t1); |
105 |
|
778320 |
ELL_3V_SUB(wpos, vpos, wpos); |
106 |
|
778320 |
ELL_3V_SET(fv, -R*sin(t1), R*cos(t1), S/AIR_PI); /* helix tangent */ |
107 |
|
778320 |
ELL_3V_NORM(fv, fv, len); |
108 |
|
|
ELL_3V_COPY(rv, wpos); |
109 |
|
778320 |
ELL_3V_NORM(rv, rv, len); |
110 |
|
778320 |
len = ELL_3V_DOT(rv, fv); |
111 |
|
778320 |
ELL_3V_SCALE(tmp, -len, fv); |
112 |
|
778320 |
ELL_3V_ADD2(rv, rv, tmp); |
113 |
|
778320 |
ELL_3V_NORM(rv, rv, len); /* rv now normal to helix, closest to |
114 |
|
|
pointing to vpos */ |
115 |
|
778320 |
ELL_3V_CROSS(uv, rv, fv); |
116 |
|
778320 |
ELL_3V_NORM(uv, uv, len); /* (rv,fv,uv) now right-handed frame */ |
117 |
|
|
ELL_3MV_ROW0_SET(W2H, uv); /* as is (uv,rv,fv) */ |
118 |
|
|
ELL_3MV_ROW1_SET(W2H, rv); |
119 |
|
|
ELL_3MV_ROW2_SET(W2H, fv); |
120 |
|
|
ELL_3M_TRANSPOSE(H2W, W2H); |
121 |
|
778320 |
inside = 0.5 - 0.5*airErf((ELL_3V_LEN(wpos)-r)/(bnd + 0.0001)); |
122 |
✓✗ |
778320 |
if (incrtwist) { |
123 |
|
778320 |
th = angle*ELL_3V_LEN(wpos)/r; |
124 |
|
778320 |
} else { |
125 |
|
|
th = angle; |
126 |
|
|
} |
127 |
|
778320 |
ELL_3M_ROTATE_Y_SET(H2C, th); |
128 |
|
|
ELL_3M_TRANSPOSE(C2H, H2C); |
129 |
|
778320 |
ELL_3M_SCALE_SET(mA, |
130 |
|
|
AIR_LERP(inside, bgEval, ev[1]), |
131 |
|
|
AIR_LERP(inside, bgEval, ev[2]), |
132 |
|
|
AIR_LERP(inside, bgEval, ev[0])); |
133 |
|
778320 |
ELL_3M_MUL(mB, mA, H2C); |
134 |
|
778320 |
ELL_3M_MUL(mA, mB, W2H); |
135 |
|
778320 |
ELL_3M_MUL(mB, mA, mf); |
136 |
|
778320 |
ELL_3M_MUL(mA, C2H, mB); |
137 |
|
778320 |
ELL_3M_MUL(mB, H2W, mA); |
138 |
|
778320 |
ELL_3M_MUL(mA, mfT, mB); |
139 |
|
|
|
140 |
|
778320 |
TEN_M2T_TT(out, float, mA); |
141 |
|
778320 |
out[0] = 1.0; |
142 |
|
778320 |
out += 7; |
143 |
|
|
} |
144 |
|
|
} |
145 |
|
|
} |
146 |
|
|
return; |
147 |
|
8 |
} |
148 |
|
|
|
149 |
|
|
int |
150 |
|
|
tend_helixMain(int argc, const char **argv, const char *me, |
151 |
|
|
hestParm *hparm) { |
152 |
|
|
int pret; |
153 |
|
18 |
hestOpt *hopt = NULL; |
154 |
|
9 |
char *perr, *err; |
155 |
|
|
airArray *mop; |
156 |
|
|
|
157 |
|
9 |
int size[3], nit, verbose; |
158 |
|
|
Nrrd *nout; |
159 |
|
9 |
double R, r, S, bnd, angle, ev[3], ip[3], iq[4], mp[3], mq[4], tmp[9], |
160 |
|
|
orig[3], i2w[9], rot[9], mf[9], spd[4][3], bge; |
161 |
|
9 |
char *outS; |
162 |
|
|
|
163 |
|
9 |
hestOptAdd(&hopt, "s", "size", airTypeInt, 3, 3, size, NULL, |
164 |
|
|
"sizes along fast, medium, and slow axes of the sampled volume, " |
165 |
|
|
"often called \"X\", \"Y\", and \"Z\". It is best to use " |
166 |
|
|
"slightly different sizes here, to expose errors in interpreting " |
167 |
|
|
"axis ordering (e.g. \"-s 39 40 41\")"); |
168 |
|
9 |
hestOptAdd(&hopt, "ip", "image orientation", airTypeDouble, 3, 3, ip, |
169 |
|
|
"0 0 0", |
170 |
|
|
"quaternion quotient space orientation of image"); |
171 |
|
9 |
hestOptAdd(&hopt, "mp", "measurement orientation", airTypeDouble, 3, 3, mp, |
172 |
|
|
"0 0 0", |
173 |
|
|
"quaternion quotient space orientation of measurement frame"); |
174 |
|
9 |
hestOptAdd(&hopt, "b", "boundary", airTypeDouble, 1, 1, &bnd, "10", |
175 |
|
|
"parameter governing how fuzzy the boundary between high and " |
176 |
|
|
"low anisotropy is. Use \"-b 0\" for no fuzziness"); |
177 |
|
9 |
hestOptAdd(&hopt, "r", "little radius", airTypeDouble, 1, 1, &r, "30", |
178 |
|
|
"(minor) radius of cylinder tracing helix"); |
179 |
|
9 |
hestOptAdd(&hopt, "R", "big radius", airTypeDouble, 1, 1, &R, "50", |
180 |
|
|
"(major) radius of helical turns"); |
181 |
|
9 |
hestOptAdd(&hopt, "S", "spacing", airTypeDouble, 1, 1, &S, "100", |
182 |
|
|
"spacing between turns of helix (along its axis)"); |
183 |
|
9 |
hestOptAdd(&hopt, "a", "angle", airTypeDouble, 1, 1, &angle, "60", |
184 |
|
|
"maximal angle of twist of tensors along path. There is no " |
185 |
|
|
"twist at helical core of path, and twist increases linearly " |
186 |
|
|
"with radius around this path. Positive twist angle with " |
187 |
|
|
"positive spacing resulting in a right-handed twist around a " |
188 |
|
|
"right-handed helix. "); |
189 |
|
9 |
hestOptAdd(&hopt, "nit", NULL, airTypeInt, 0, 0, &nit, NULL, |
190 |
|
|
"changes behavior of twist angle as function of distance from " |
191 |
|
|
"center of helical core: instead of increasing linearly as " |
192 |
|
|
"describe above, be at a constant angle"); |
193 |
|
9 |
hestOptAdd(&hopt, "ev", "eigenvalues", airTypeDouble, 3, 3, ev, |
194 |
|
|
"0.006 0.002 0.001", |
195 |
|
|
"eigenvalues of tensors (in order) along direction of coil, " |
196 |
|
|
"circumferential around coil, and radial around coil. "); |
197 |
|
9 |
hestOptAdd(&hopt, "bg", "background", airTypeDouble, 1, 1, &bge, "0.5", |
198 |
|
|
"eigenvalue of isotropic background"); |
199 |
|
9 |
hestOptAdd(&hopt, "v", "verbose", airTypeInt, 1, 1, &verbose, "1", |
200 |
|
|
"verbose output"); |
201 |
|
9 |
hestOptAdd(&hopt, "o", "nout", airTypeString, 1, 1, &outS, "-", |
202 |
|
|
"output file"); |
203 |
|
|
|
204 |
|
9 |
mop = airMopNew(); |
205 |
|
9 |
airMopAdd(mop, hopt, (airMopper)hestOptFree, airMopAlways); |
206 |
✓✓ |
10 |
USAGE(_tend_helixInfoL); |
207 |
✗✓✗✗
|
8 |
JUSTPARSE(); |
208 |
|
8 |
airMopAdd(mop, hopt, (airMopper)hestParseFree, airMopAlways); |
209 |
|
|
|
210 |
|
8 |
nout = nrrdNew(); |
211 |
|
8 |
airMopAdd(mop, nout, (airMopper)nrrdNuke, airMopAlways); |
212 |
✗✓ |
8 |
if (nrrdMaybeAlloc_va(nout, nrrdTypeFloat, 4, |
213 |
|
|
AIR_CAST(size_t, 7), |
214 |
|
8 |
AIR_CAST(size_t, size[0]), |
215 |
|
8 |
AIR_CAST(size_t, size[1]), |
216 |
|
8 |
AIR_CAST(size_t, size[2]))) { |
217 |
|
|
airMopAdd(mop, err=biffGetDone(NRRD), airFree, airMopAlways); |
218 |
|
|
fprintf(stderr, "%s: trouble allocating output:\n%s\n", me, err); |
219 |
|
|
airMopError(mop); return 1; |
220 |
|
|
} |
221 |
|
|
|
222 |
|
8 |
ELL_4V_SET(iq, 1.0, ip[0], ip[1], ip[2]); |
223 |
|
8 |
ell_q_to_3m_d(rot, iq); |
224 |
|
8 |
ELL_3V_SET(orig, |
225 |
|
|
-2*R + 2*R/size[0], |
226 |
|
|
-2*R + 2*R/size[1], |
227 |
|
|
-2*R + 2*R/size[2]); |
228 |
|
8 |
ELL_3M_ZERO_SET(i2w); |
229 |
|
8 |
ELL_3M_DIAG_SET(i2w, 4*R/size[0], 4*R/size[1], 4*R/size[2]); |
230 |
|
8 |
ELL_3MV_MUL(tmp, rot, orig); |
231 |
|
8 |
ELL_3V_COPY(orig, tmp); |
232 |
|
8 |
ELL_3M_MUL(tmp, rot, i2w); |
233 |
|
8 |
ELL_3M_COPY(i2w, tmp); |
234 |
|
8 |
ELL_4V_SET(mq, 1.0, mp[0], mp[1], mp[2]); |
235 |
|
8 |
ell_q_to_3m_d(mf, mq); |
236 |
|
16 |
tend_helixDoit(nout, bnd, |
237 |
|
8 |
orig, i2w, mf, |
238 |
|
8 |
r, R, S, angle*AIR_PI/180, !nit, ev, bge, |
239 |
|
8 |
verbose); |
240 |
|
8 |
nrrdSpaceSet(nout, nrrdSpaceRightAnteriorSuperior); |
241 |
|
8 |
nrrdSpaceOriginSet(nout, orig); |
242 |
|
8 |
ELL_3V_SET(spd[0], AIR_NAN, AIR_NAN, AIR_NAN); |
243 |
|
8 |
ELL_3MV_COL0_GET(spd[1], i2w); |
244 |
|
8 |
ELL_3MV_COL1_GET(spd[2], i2w); |
245 |
|
8 |
ELL_3MV_COL2_GET(spd[3], i2w); |
246 |
|
8 |
nrrdAxisInfoSet_va(nout, nrrdAxisInfoSpaceDirection, |
247 |
|
8 |
spd[0], spd[1], spd[2], spd[3]); |
248 |
|
8 |
nrrdAxisInfoSet_va(nout, nrrdAxisInfoCenter, |
249 |
|
|
nrrdCenterUnknown, nrrdCenterCell, |
250 |
|
|
nrrdCenterCell, nrrdCenterCell); |
251 |
|
8 |
nrrdAxisInfoSet_va(nout, nrrdAxisInfoKind, |
252 |
|
|
nrrdKind3DMaskedSymMatrix, nrrdKindSpace, |
253 |
|
|
nrrdKindSpace, nrrdKindSpace); |
254 |
|
8 |
nout->measurementFrame[0][0] = mf[0]; |
255 |
|
8 |
nout->measurementFrame[1][0] = mf[1]; |
256 |
|
8 |
nout->measurementFrame[2][0] = mf[2]; |
257 |
|
8 |
nout->measurementFrame[0][1] = mf[3]; |
258 |
|
8 |
nout->measurementFrame[1][1] = mf[4]; |
259 |
|
8 |
nout->measurementFrame[2][1] = mf[5]; |
260 |
|
8 |
nout->measurementFrame[0][2] = mf[6]; |
261 |
|
8 |
nout->measurementFrame[1][2] = mf[7]; |
262 |
|
8 |
nout->measurementFrame[2][2] = mf[8]; |
263 |
|
|
|
264 |
✗✓ |
8 |
if (nrrdSave(outS, nout, NULL)) { |
265 |
|
|
airMopAdd(mop, err=biffGetDone(NRRD), airFree, airMopAlways); |
266 |
|
|
fprintf(stderr, "%s: trouble writing:\n%s\n", me, err); |
267 |
|
|
airMopError(mop); return 1; |
268 |
|
|
} |
269 |
|
|
|
270 |
|
8 |
airMopOkay(mop); |
271 |
|
8 |
return 0; |
272 |
|
9 |
} |
273 |
|
|
TEND_CMD(helix, INFO); |