1 /*
2 Teem: Tools to process and visualize scientific data and images .
3 Copyright (C) 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
25 #include "ten.h"
26 #include "privateTen.h"
27
28 int tenVerbose = 0;
29
30 void
tenRotateSingle_f(float tenOut[7],const float rot[9],const float tenIn[7])31 tenRotateSingle_f(float tenOut[7], const float rot[9], const float tenIn[7]) {
32 float rotT[9], matIn[9], tmp[9], matOut[9];
33
34 ELL_3M_TRANSPOSE(rotT, rot);
35 TEN_T2M(matIn, tenIn);
36 ELL_3M_MUL(tmp, matIn, rotT);
37 ELL_3M_MUL(matOut, rot, tmp);
38 TEN_M2T_TT(tenOut, float, matOut);
39 tenOut[0] = tenIn[0];
40 return;
41 }
42
43 /*
44 ******** tenTensorCheck()
45 **
46 ** describes if the given nrrd could be a diffusion tensor dataset,
47 ** either the measured DWI data or the calculated tensor data.
48 **
49 ** We've been using 7 floats for BOTH kinds of tensor data- both the
50 ** measured DWI and the calculated tensor matrices. The measured data
51 ** comes as one anatomical image and 6 DWIs. For the calculated tensors,
52 ** in addition to the 6 matrix components, we keep a "threshold" value
53 ** which is based on the sum of all the DWIs, which describes if the
54 ** calculated tensor means anything or not.
55 **
56 ** useBiff controls if biff is used to describe the problem
57 */
58 int
tenTensorCheck(const Nrrd * nin,int wantType,int want4D,int useBiff)59 tenTensorCheck(const Nrrd *nin, int wantType, int want4D, int useBiff) {
60 static const char me[]="tenTensorCheck";
61
62 if (!nin) {
63 if (useBiff) biffAddf(TEN, "%s: got NULL pointer", me);
64 return 1;
65 }
66 if (wantType) {
67 if (nin->type != wantType) {
68 if (useBiff) biffAddf(TEN, "%s: wanted type %s, got type %s", me,
69 airEnumStr(nrrdType, wantType),
70 airEnumStr(nrrdType, nin->type));
71 return 1;
72 }
73 }
74 else {
75 if (!(nin->type == nrrdTypeFloat || nin->type == nrrdTypeShort)) {
76 if (useBiff) biffAddf(TEN, "%s: need data of type float or short", me);
77 return 1;
78 }
79 }
80 if (want4D && !(4 == nin->dim)) {
81 if (useBiff)
82 biffAddf(TEN, "%s: given dimension is %d, not 4", me, nin->dim);
83 return 1;
84 }
85 if (!(7 == nin->axis[0].size)) {
86 if (useBiff) {
87 char stmp[AIR_STRLEN_SMALL];
88 biffAddf(TEN, "%s: axis 0 has size %s, not 7", me,
89 airSprintSize_t(stmp, nin->axis[0].size));
90 }
91 return 1;
92 }
93 return 0;
94 }
95
96 int
tenMeasurementFrameReduce(Nrrd * nout,const Nrrd * nin)97 tenMeasurementFrameReduce(Nrrd *nout, const Nrrd *nin) {
98 static const char me[]="tenMeasurementFrameReduce";
99 double MF[9], MFT[9], tenMeasr[9], tenWorld[9];
100 float *tdata;
101 size_t ii, nn;
102 unsigned int si, sj;
103
104 if (!(nout && nin)) {
105 biffAddf(TEN, "%s: got NULL pointer", me);
106 return 1;
107 }
108 if (tenTensorCheck(nin, nrrdTypeFloat, AIR_TRUE, AIR_TRUE)) {
109 biffAddf(TEN, "%s: ", me);
110 return 1;
111 }
112 if (3 != nin->spaceDim) {
113 biffAddf(TEN, "%s: input nrrd needs 3-D (not %u-D) space dimension",
114 me, nin->spaceDim);
115 return 1;
116 }
117 /*
118 [0] [1] [2] [0][0] [1][0] [2][0]
119 [3] [4] [5] = [0][1] [1][1] [2][1]
120 [6] [7] [8] [0][2] [1][2] [2][2]
121 */
122 MF[0] = nin->measurementFrame[0][0];
123 MF[1] = nin->measurementFrame[1][0];
124 MF[2] = nin->measurementFrame[2][0];
125 MF[3] = nin->measurementFrame[0][1];
126 MF[4] = nin->measurementFrame[1][1];
127 MF[5] = nin->measurementFrame[2][1];
128 MF[6] = nin->measurementFrame[0][2];
129 MF[7] = nin->measurementFrame[1][2];
130 MF[8] = nin->measurementFrame[2][2];
131 if (!ELL_3M_EXISTS(MF)) {
132 biffAddf(TEN, "%s: 3x3 measurement frame doesn't exist", me);
133 return 1;
134 }
135 ELL_3M_TRANSPOSE(MFT, MF);
136
137 if (nout != nin) {
138 if (nrrdCopy(nout, nin)) {
139 biffAddf(TEN, "%s: trouble with initial copy", me);
140 return 1;
141 }
142 }
143 nn = nrrdElementNumber(nout)/nout->axis[0].size;
144 tdata = (float*)(nout->data);
145 for (ii=0; ii<nn; ii++) {
146 TEN_T2M(tenMeasr, tdata);
147 ell_3m_mul_d(tenWorld, MF, tenMeasr);
148 ell_3m_mul_d(tenWorld, tenWorld, MFT);
149 TEN_M2T_TT(tdata, float, tenWorld);
150 tdata += 7;
151 }
152 for (si=0; si<NRRD_SPACE_DIM_MAX; si++) {
153 for (sj=0; sj<NRRD_SPACE_DIM_MAX; sj++) {
154 nout->measurementFrame[si][sj] = AIR_NAN;
155 }
156 }
157 for (si=0; si<3; si++) {
158 for (sj=0; sj<3; sj++) {
159 nout->measurementFrame[si][sj] = (si == sj);
160 }
161 }
162
163 return 0;
164 }
165
166 int
tenExpand2D(Nrrd * nout,const Nrrd * nin,double scale,double thresh)167 tenExpand2D(Nrrd *nout, const Nrrd *nin, double scale, double thresh) {
168 static const char me[]="tenExpand2D";
169 size_t N, I, sx, sy;
170 float *masked, *redund;
171
172 if (!( nout && nin && AIR_EXISTS(thresh) )) {
173 biffAddf(TEN, "%s: got NULL pointer or non-existent threshold", me);
174 return 1;
175 }
176 if (nout == nin) {
177 biffAddf(TEN, "%s: sorry, need different nrrds for input and output", me);
178 return 1;
179 }
180 /* HEY copy and paste and tweak of tenTensorCheck */
181 if (!nin) {
182 biffAddf(TEN, "%s: got NULL pointer", me);
183 return 1;
184 }
185 if (nin->type != nrrdTypeFloat) {
186 biffAddf(TEN, "%s: wanted type %s, got type %s", me,
187 airEnumStr(nrrdType, nrrdTypeFloat),
188 airEnumStr(nrrdType, nin->type));
189 return 1;
190 } else {
191 if (!(nin->type == nrrdTypeFloat || nin->type == nrrdTypeShort)) {
192 biffAddf(TEN, "%s: need data of type float or short", me);
193 return 1;
194 }
195 }
196 if (3 != nin->dim) {
197 biffAddf(TEN, "%s: given dimension is %u, not 3", me, nin->dim);
198 return 1;
199 }
200 if (!(4 == nin->axis[0].size)) {
201 char stmp[AIR_STRLEN_SMALL];
202 biffAddf(TEN, "%s: axis 0 has size %s, not 4", me,
203 airSprintSize_t(stmp, nin->axis[0].size));
204 return 1;
205 }
206
207 sx = nin->axis[1].size;
208 sy = nin->axis[2].size;
209 N = sx*sy;
210 if (nrrdMaybeAlloc_va(nout, nrrdTypeFloat, 3,
211 AIR_CAST(size_t, 4), sx, sy)) {
212 biffMovef(TEN, NRRD, "%s: trouble", me);
213 return 1;
214 }
215 for (I=0; I<=N-1; I++) {
216 masked = (float*)(nin->data) + I*4;
217 redund = (float*)(nout->data) + I*4;
218 if (masked[0] < thresh) {
219 ELL_4V_ZERO_SET(redund);
220 continue;
221 }
222 redund[0] = masked[1];
223 redund[1] = masked[2];
224 redund[2] = masked[2];
225 redund[3] = masked[3];
226 ELL_4V_SCALE(redund, AIR_CAST(float, scale), redund);
227 }
228 if (nrrdAxisInfoCopy(nout, nin, NULL,
229 NRRD_AXIS_INFO_SIZE_BIT)) {
230 biffMovef(TEN, NRRD, "%s: trouble", me);
231 return 1;
232 }
233 /* by call above we just copied axis-0 kind, which might be wrong;
234 we actually know the output kind now, so we might as well set it */
235 nout->axis[0].kind = nrrdKind2DMatrix;
236 if (nrrdBasicInfoCopy(nout, nin,
237 NRRD_BASIC_INFO_ALL ^ NRRD_BASIC_INFO_SPACE)) {
238 biffAddf(TEN, "%s:", me);
239 return 1;
240 }
241 return 0;
242 }
243
244 int
tenExpand(Nrrd * nout,const Nrrd * nin,double scale,double thresh)245 tenExpand(Nrrd *nout, const Nrrd *nin, double scale, double thresh) {
246 static const char me[]="tenExpand";
247 size_t N, I, sx, sy, sz;
248 float *seven, *nine;
249
250 if (!( nout && nin && AIR_EXISTS(thresh) )) {
251 biffAddf(TEN, "%s: got NULL pointer or non-existent threshold", me);
252 return 1;
253 }
254 if (nout == nin) {
255 biffAddf(TEN, "%s: sorry, need different nrrds for input and output", me);
256 return 1;
257 }
258 if (tenTensorCheck(nin, nrrdTypeFloat, AIR_TRUE, AIR_TRUE)) {
259 biffAddf(TEN, "%s: ", me);
260 return 1;
261 }
262
263 sx = nin->axis[1].size;
264 sy = nin->axis[2].size;
265 sz = nin->axis[3].size;
266 N = sx*sy*sz;
267 if (nrrdMaybeAlloc_va(nout, nrrdTypeFloat, 4,
268 AIR_CAST(size_t, 9), sx, sy, sz)) {
269 biffMovef(TEN, NRRD, "%s: trouble", me);
270 return 1;
271 }
272 for (I=0; I<=N-1; I++) {
273 seven = (float*)(nin->data) + I*7;
274 nine = (float*)(nout->data) + I*9;
275 if (seven[0] < thresh) {
276 ELL_3M_ZERO_SET(nine);
277 continue;
278 }
279 TEN_T2M(nine, seven);
280 ELL_3M_SCALE(nine, AIR_CAST(float, scale), nine);
281 }
282 if (nrrdAxisInfoCopy(nout, nin, NULL,
283 NRRD_AXIS_INFO_SIZE_BIT)) {
284 biffMovef(TEN, NRRD, "%s: trouble", me);
285 return 1;
286 }
287 /* by call above we just copied axis-0 kind, which might be wrong;
288 we actually know the output kind now, so we might as well set it */
289 nout->axis[0].kind = nrrdKind3DMatrix;
290 if (nrrdBasicInfoCopy(nout, nin,
291 NRRD_BASIC_INFO_ALL ^ NRRD_BASIC_INFO_SPACE)) {
292 biffAddf(TEN, "%s:", me);
293 return 1;
294 }
295 /* Tue Sep 13 18:36:45 EDT 2005: why did I do this?
296 nout->axis[0].label = (char *)airFree(nout->axis[0].label);
297 nout->axis[0].label = airStrdup("matrix");
298 */
299
300 return 0;
301 }
302
303 int
tenShrink(Nrrd * tseven,const Nrrd * nconf,const Nrrd * tnine)304 tenShrink(Nrrd *tseven, const Nrrd *nconf, const Nrrd *tnine) {
305 static const char me[]="tenShrink";
306 size_t I, N, sx, sy, sz;
307 float *seven, *conf, *nine;
308
309 if (!(tseven && tnine)) {
310 biffAddf(TEN, "%s: got NULL pointer", me);
311 return 1;
312 }
313 if (tseven == tnine) {
314 biffAddf(TEN, "%s: sorry, need different nrrds for input and output", me);
315 return 1;
316 }
317 if (!( nrrdTypeFloat == tnine->type &&
318 4 == tnine->dim &&
319 9 == tnine->axis[0].size )) {
320 char stmp[AIR_STRLEN_SMALL];
321 biffAddf(TEN, "%s: type not %s (was %s) or dim not 4 (was %d) "
322 "or first axis size not 9 (was %s)", me,
323 airEnumStr(nrrdType, nrrdTypeFloat),
324 airEnumStr(nrrdType, tnine->type), tnine->dim,
325 airSprintSize_t(stmp, tnine->axis[0].size));
326 return 1;
327 }
328 sx = tnine->axis[1].size;
329 sy = tnine->axis[2].size;
330 sz = tnine->axis[3].size;
331 if (nconf) {
332 if (!( nrrdTypeFloat == nconf->type &&
333 3 == nconf->dim &&
334 sx == nconf->axis[0].size &&
335 sy == nconf->axis[1].size &&
336 sz == nconf->axis[2].size )) {
337 biffAddf(TEN, "%s: confidence type not %s (was %s) or dim not 3 (was %d) "
338 "or dimensions didn't match tensor volume", me,
339 airEnumStr(nrrdType, nrrdTypeFloat),
340 airEnumStr(nrrdType, nconf->type),
341 nconf->dim);
342 return 1;
343 }
344 }
345 if (nrrdMaybeAlloc_va(tseven, nrrdTypeFloat, 4,
346 AIR_CAST(size_t, 7), sx, sy, sz)) {
347 biffMovef(TEN, NRRD, "%s: trouble allocating output", me);
348 return 1;
349 }
350 seven = (float *)tseven->data;
351 conf = nconf ? (float *)nconf->data : NULL;
352 nine = (float *)tnine->data;
353 N = sx*sy*sz;
354 for (I=0; I<N; I++) {
355 TEN_M2T_TT(seven, float, nine);
356 seven[0] = conf ? conf[I] : 1.0f;
357 seven += 7;
358 nine += 9;
359 }
360 if (nrrdAxisInfoCopy(tseven, tnine, NULL,
361 NRRD_AXIS_INFO_SIZE_BIT)) {
362 biffMovef(TEN, NRRD, "%s: trouble", me);
363 return 1;
364 }
365 /* by call above we just copied axis-0 kind, which might be wrong;
366 we actually know the output kind now, so we might as well set it */
367 tseven->axis[0].kind = nrrdKind3DMaskedSymMatrix;
368 if (nrrdBasicInfoCopy(tseven, tnine,
369 NRRD_BASIC_INFO_ALL ^ NRRD_BASIC_INFO_SPACE)) {
370 biffAddf(TEN, "%s:", me);
371 return 1;
372 }
373 /* Wed Dec 3 11:22:32 EST 2008: no real need to set label string */
374
375 return 0;
376 }
377
378 /*
379 ******** tenEigensolve_f
380 **
381 ** uses ell_3m_eigensolve_d to get the eigensystem of a single tensor
382 ** disregards the confidence value t[0]
383 **
384 ** return is same as ell_3m_eigensolve_d, which is same as ell_cubic
385 **
386 ** NOTE: Even in the post-Teem-1.7 switch from column-major to
387 ** row-major- its still the case that the eigenvectors are at
388 ** evec+0, evec+3, evec+6: this means that they USED to be the
389 ** "columns" of the matrix, and NOW they're the rows.
390 **
391 ** This does NOT use biff
392 */
393 int
tenEigensolve_f(float _eval[3],float _evec[9],const float t[7])394 tenEigensolve_f(float _eval[3], float _evec[9], const float t[7]) {
395 double m[9], eval[3], evec[9], trc, iso[9];
396 int ret;
397
398 TEN_T2M(m, t);
399 trc = ELL_3M_TRACE(m)/3.0;
400 ELL_3M_IDENTITY_SET(iso);
401 ELL_3M_SCALE_SET(iso, -trc, -trc, -trc);
402 ELL_3M_ADD2(m, m, iso);
403 if (_evec) {
404 ret = ell_3m_eigensolve_d(eval, evec, m, AIR_TRUE);
405 if (tenVerbose > 4) {
406 fprintf(stderr, "---- cubic ret = %d\n", ret);
407 fprintf(stderr, "tensor = {\n");
408 fprintf(stderr, " % 15.7f,\n", t[1]);
409 fprintf(stderr, " % 15.7f,\n", t[2]);
410 fprintf(stderr, " % 15.7f,\n", t[3]);
411 fprintf(stderr, " % 15.7f,\n", t[4]);
412 fprintf(stderr, " % 15.7f,\n", t[5]);
413 fprintf(stderr, " % 15.7f}\n", t[6]);
414 fprintf(stderr, "roots = %d:\n", ret);
415 fprintf(stderr, " % 31.15f\n", trc + eval[0]);
416 fprintf(stderr, " % 31.15f\n", trc + eval[1]);
417 fprintf(stderr, " % 31.15f\n", trc + eval[2]);
418 }
419 ELL_3V_SET_TT(_eval, float, eval[0] + trc, eval[1] + trc, eval[2] + trc);
420 ELL_3M_COPY_TT(_evec, float, evec);
421 if (ell_cubic_root_single_double == ret) {
422 /* this was added to fix a stupid problem with very nearly
423 isotropic glyphs, used for demonstration figures */
424 if (eval[0] == eval[1]) {
425 ELL_3V_CROSS(_evec+6, _evec+0, _evec+3);
426 } else {
427 ELL_3V_CROSS(_evec+0, _evec+3, _evec+6);
428 }
429 }
430 if ((tenVerbose > 1) && _eval[2] < 0) {
431 fprintf(stderr, "tenEigensolve_f -------------\n");
432 fprintf(stderr, "% 15.7f % 15.7f % 15.7f\n",
433 t[1], t[2], t[3]);
434 fprintf(stderr, "% 15.7f % 15.7f % 15.7f\n",
435 t[2], t[4], t[5]);
436 fprintf(stderr, "% 15.7f % 15.7f % 15.7f\n",
437 t[3], t[5], t[6]);
438 fprintf(stderr, " --> % 15.7f % 15.7f % 15.7f\n",
439 _eval[0], _eval[1], _eval[2]);
440 }
441 } else {
442 /* caller only wants eigenvalues */
443 ret = ell_3m_eigenvalues_d(eval, m, AIR_TRUE);
444 ELL_3V_SET_TT(_eval, float, eval[0] + trc, eval[1] + trc, eval[2] + trc);
445 }
446 return ret;
447 }
448
449 /* HEY: cut and paste !! */
450 int
tenEigensolve_d(double _eval[3],double evec[9],const double t[7])451 tenEigensolve_d(double _eval[3], double evec[9], const double t[7]) {
452 double m[9], eval[3], trc, iso[9];
453 int ret;
454
455 TEN_T2M(m, t);
456 trc = ELL_3M_TRACE(m)/3.0;
457 ELL_3M_SCALE_SET(iso, -trc, -trc, -trc);
458 ELL_3M_ADD2(m, m, iso);
459 /*
460 printf("!%s: t = %g %g %g; %g %g; %g\n", "tenEigensolve_f",
461 t[1], t[2], t[3], t[4], t[5], t[6]);
462 printf("!%s: m = %g %g %g; %g %g %g; %g %g %g\n", "tenEigensolve_f",
463 m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
464 */
465 if (evec) {
466 ret = ell_3m_eigensolve_d(eval, evec, m, AIR_TRUE);
467 ELL_3V_SET(_eval, eval[0] + trc, eval[1] + trc, eval[2] + trc);
468 if (ell_cubic_root_single_double == ret) {
469 /* this was added to fix a stupid problem with very nearly
470 isotropic glyphs, used for demonstration figures */
471 if (eval[0] == eval[1]) {
472 ELL_3V_CROSS(evec+6, evec+0, evec+3);
473 } else {
474 ELL_3V_CROSS(evec+0, evec+3, evec+6);
475 }
476 }
477 } else {
478 /* caller only wants eigenvalues */
479 ret = ell_3m_eigenvalues_d(eval, m, AIR_TRUE);
480 ELL_3V_SET(_eval, eval[0] + trc, eval[1] + trc, eval[2] + trc);
481 }
482 return ret;
483 }
484
485
486
487 /* lop A
488 fprintf(stderr, "################################### I = %d\n", (int)I);
489 tenEigensolve(teval, tevec, out);
490 fprintf(stderr, "evals: (%g %g %g) %g %g %g --> %g %g %g\n",
491 AIR_ABS(eval[0] - teval[0]) + 1,
492 AIR_ABS(eval[1] - teval[1]) + 1,
493 AIR_ABS(eval[2] - teval[2]) + 1,
494 eval[0], eval[1], eval[2],
495 teval[0], teval[1], teval[2]);
496 fprintf(stderr, " tevec lens: %g %g %g\n", ELL_3V_LEN(tevec+3*0),
497 ELL_3V_LEN(tevec+3*1), ELL_3V_LEN(tevec+3*2));
498 ELL_3V_CROSS(tmp1, evec+3*0, evec+3*1); tmp2[0] = ELL_3V_LEN(tmp1);
499 ELL_3V_CROSS(tmp1, evec+3*0, evec+3*2); tmp2[1] = ELL_3V_LEN(tmp1);
500 ELL_3V_CROSS(tmp1, evec+3*1, evec+3*2); tmp2[2] = ELL_3V_LEN(tmp1);
501 fprintf(stderr, " evec[0] = %g %g %g\n",
502 (evec+3*0)[0], (evec+3*0)[1], (evec+3*0)[2]);
503 fprintf(stderr, " evec[1] = %g %g %g\n",
504 (evec+3*1)[0], (evec+3*1)[1], (evec+3*1)[2]);
505 fprintf(stderr, " evec[2] = %g %g %g\n",
506 (evec+3*2)[0], (evec+3*2)[1], (evec+3*2)[2]);
507 fprintf(stderr, " evec crosses: %g %g %g\n",
508 tmp2[0], tmp2[1], tmp2[2]);
509 ELL_3V_CROSS(tmp1, tevec+3*0, tevec+3*1); tmp2[0] = ELL_3V_LEN(tmp1);
510 ELL_3V_CROSS(tmp1, tevec+3*0, tevec+3*2); tmp2[1] = ELL_3V_LEN(tmp1);
511 ELL_3V_CROSS(tmp1, tevec+3*1, tevec+3*2); tmp2[2] = ELL_3V_LEN(tmp1);
512 fprintf(stderr, " tevec[0] = %g %g %g\n",
513 (tevec+3*0)[0], (tevec+3*0)[1], (tevec+3*0)[2]);
514 fprintf(stderr, " tevec[1] = %g %g %g\n",
515 (tevec+3*1)[0], (tevec+3*1)[1], (tevec+3*1)[2]);
516 fprintf(stderr, " tevec[2] = %g %g %g\n",
517 (tevec+3*2)[0], (tevec+3*2)[1], (tevec+3*2)[2]);
518 fprintf(stderr, " tevec crosses: %g %g %g\n",
519 tmp2[0], tmp2[1], tmp2[2]);
520 if (tmp2[1] < 0.5) {
521 fprintf(stderr, "(panic)\n");
522 exit(0);
523 }
524 */
525
526 void
tenMakeSingle_f(float ten[7],float conf,const float eval[3],const float evec[9])527 tenMakeSingle_f(float ten[7], float conf, const float eval[3], const float evec[9]) {
528 double tmpMat1[9], tmpMat2[9], diag[9], evecT[9];
529
530 ELL_3M_ZERO_SET(diag);
531 ELL_3M_DIAG_SET(diag, eval[0], eval[1], eval[2]);
532 ELL_3M_TRANSPOSE(evecT, evec);
533 ELL_3M_MUL(tmpMat1, diag, evec);
534 ELL_3M_MUL(tmpMat2, evecT, tmpMat1);
535 ten[0] = conf;
536 TEN_M2T_TT(ten, float, tmpMat2);
537 return;
538 }
539
540 /* HEY: copy and paste! */
541 void
tenMakeSingle_d(double ten[7],double conf,const double eval[3],const double evec[9])542 tenMakeSingle_d(double ten[7], double conf, const double eval[3], const double evec[9]) {
543 double tmpMat1[9], tmpMat2[9], diag[9], evecT[9];
544
545 ELL_3M_ZERO_SET(diag);
546 ELL_3M_DIAG_SET(diag, eval[0], eval[1], eval[2]);
547 ELL_3M_TRANSPOSE(evecT, evec);
548 ELL_3M_MUL(tmpMat1, diag, evec);
549 ELL_3M_MUL(tmpMat2, evecT, tmpMat1);
550 ten[0] = conf;
551 TEN_M2T_TT(ten, float, tmpMat2);
552 return;
553 }
554
555 /*
556 ******** tenMake
557 **
558 ** create a tensor nrrd from nrrds of confidence, eigenvalues, and
559 ** eigenvectors
560 */
561 int
tenMake(Nrrd * nout,const Nrrd * nconf,const Nrrd * neval,const Nrrd * nevec)562 tenMake(Nrrd *nout, const Nrrd *nconf, const Nrrd *neval, const Nrrd *nevec) {
563 static const char me[]="tenTensorMake";
564 size_t I, N, sx, sy, sz;
565 float *out, *conf, *eval, *evec;
566 int map[4];
567 /* float teval[3], tevec[9], tmp1[3], tmp2[3]; */
568 char stmp[7][AIR_STRLEN_SMALL];
569
570 if (!(nout && nconf && neval && nevec)) {
571 biffAddf(TEN, "%s: got NULL pointer", me);
572 return 1;
573 }
574 if (nrrdCheck(nconf) || nrrdCheck(neval) || nrrdCheck(nevec)) {
575 biffMovef(TEN, NRRD, "%s: didn't get three valid nrrds", me);
576 return 1;
577 }
578 if (!( 3 == nconf->dim && nrrdTypeFloat == nconf->type )) {
579 biffAddf(TEN, "%s: first nrrd not a confidence volume "
580 "(dim = %d, not 3; type = %s, not %s)", me,
581 nconf->dim, airEnumStr(nrrdType, nconf->type),
582 airEnumStr(nrrdType, nrrdTypeFloat));
583 return 1;
584 }
585 sx = nconf->axis[0].size;
586 sy = nconf->axis[1].size;
587 sz = nconf->axis[2].size;
588 if (!( 4 == neval->dim && 4 == nevec->dim &&
589 nrrdTypeFloat == neval->type &&
590 nrrdTypeFloat == nevec->type )) {
591 biffAddf(TEN, "%s: second and third nrrd aren't both 4-D (%d and %d) "
592 "and type %s (%s and %s)",
593 me, neval->dim, nevec->dim,
594 airEnumStr(nrrdType, nrrdTypeFloat),
595 airEnumStr(nrrdType, neval->type),
596 airEnumStr(nrrdType, nevec->type));
597 return 1;
598 }
599 if (!( 3 == neval->axis[0].size &&
600 sx == neval->axis[1].size &&
601 sy == neval->axis[2].size &&
602 sz == neval->axis[3].size )) {
603 biffAddf(TEN, "%s: second nrrd sizes wrong: "
604 "(%s,%s,%s,%s) not (3,%s,%s,%s)", me,
605 airSprintSize_t(stmp[0], neval->axis[0].size),
606 airSprintSize_t(stmp[1], neval->axis[1].size),
607 airSprintSize_t(stmp[2], neval->axis[2].size),
608 airSprintSize_t(stmp[3], neval->axis[3].size),
609 airSprintSize_t(stmp[4], sx),
610 airSprintSize_t(stmp[5], sy),
611 airSprintSize_t(stmp[6], sz));
612 return 1;
613 }
614 if (!( 9 == nevec->axis[0].size &&
615 sx == nevec->axis[1].size &&
616 sy == nevec->axis[2].size &&
617 sz == nevec->axis[3].size )) {
618 biffAddf(TEN, "%s: third nrrd sizes wrong: "
619 "(%s,%s,%s,%s) not (9,%s,%s,%s)", me,
620 airSprintSize_t(stmp[0], nevec->axis[0].size),
621 airSprintSize_t(stmp[1], nevec->axis[1].size),
622 airSprintSize_t(stmp[2], nevec->axis[2].size),
623 airSprintSize_t(stmp[3], nevec->axis[3].size),
624 airSprintSize_t(stmp[4], sx),
625 airSprintSize_t(stmp[5], sy),
626 airSprintSize_t(stmp[6], sz));
627 return 1;
628 }
629
630 /* finally */
631 if (nrrdMaybeAlloc_va(nout, nrrdTypeFloat, 4,
632 AIR_CAST(size_t, 7), sx, sy, sz)) {
633 biffMovef(TEN, NRRD, "%s: couldn't allocate output", me);
634 return 1;
635 }
636 N = sx*sy*sz;
637 conf = (float *)(nconf->data);
638 eval = (float *)neval->data;
639 evec = (float *)nevec->data;
640 out = (float *)nout->data;
641 for (I=0; I<N; I++) {
642 tenMakeSingle_f(out, conf[I], eval, evec);
643 /* lop A */
644 out += 7;
645 eval += 3;
646 evec += 9;
647 }
648 ELL_4V_SET(map, -1, 0, 1, 2);
649 if (nrrdAxisInfoCopy(nout, nconf, map, NRRD_AXIS_INFO_SIZE_BIT)) {
650 biffMovef(TEN, NRRD, "%s: trouble", me);
651 return 1;
652 }
653 nout->axis[0].label = (char *)airFree(nout->axis[0].label);
654 nout->axis[0].label = airStrdup("tensor");
655 nout->axis[0].kind = nrrdKind3DMaskedSymMatrix;
656 if (nrrdBasicInfoCopy(nout, nconf,
657 NRRD_BASIC_INFO_DATA_BIT
658 | NRRD_BASIC_INFO_TYPE_BIT
659 | NRRD_BASIC_INFO_BLOCKSIZE_BIT
660 | NRRD_BASIC_INFO_DIMENSION_BIT
661 | NRRD_BASIC_INFO_CONTENT_BIT
662 | NRRD_BASIC_INFO_COMMENTS_BIT
663 | (nrrdStateKeyValuePairsPropagate
664 ? 0
665 : NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) {
666 biffMovef(TEN, NRRD, "%s:", me);
667 return 1;
668 }
669
670 return 0;
671 }
672
673 int
tenSlice(Nrrd * nout,const Nrrd * nten,unsigned int axis,size_t pos,unsigned int dim)674 tenSlice(Nrrd *nout, const Nrrd *nten, unsigned int axis,
675 size_t pos, unsigned int dim) {
676 static const char me[]="tenSlice";
677 Nrrd *nslice, **ncoeff=NULL;
678 int ci[4];
679 airArray *mop;
680 char stmp[2][AIR_STRLEN_SMALL];
681
682 if (!(nout && nten)) {
683 biffAddf(TEN, "%s: got NULL pointer", me);
684 return 1;
685 }
686 if (tenTensorCheck(nten, nrrdTypeDefault, AIR_TRUE, AIR_TRUE)) {
687 biffAddf(TEN, "%s: didn't get a valid tensor field", me);
688 return 1;
689 }
690 if (!(2 == dim || 3 == dim)) {
691 biffAddf(TEN, "%s: given dim (%d) not 2 or 3", me, dim);
692 return 1;
693 }
694 if (!( axis <= 2 )) {
695 biffAddf(TEN, "%s: axis %u not in valid range [0,1,2]", me, axis);
696 return 1;
697 }
698 if (!( pos < nten->axis[1+axis].size )) {
699 biffAddf(TEN, "%s: slice position %s not in valid range [0..%s]", me,
700 airSprintSize_t(stmp[0], pos),
701 airSprintSize_t(stmp[1], nten->axis[1+axis].size-1));
702 return 1;
703 }
704
705 /*
706 ** threshold 0
707 ** Dxx Dxy Dxz 1 2 3
708 ** Dxy Dyy Dyz = (2) 4 5
709 ** Dxz Dyz Dzz (3) (5) 6
710 */
711 mop = airMopNew();
712 airMopAdd(mop, nslice=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
713 if (3 == dim) {
714 if (nrrdSlice(nslice, nten, axis+1, pos)
715 || nrrdAxesInsert(nout, nslice, axis+1)) {
716 biffMovef(TEN, NRRD, "%s: trouble making slice", me);
717 airMopError(mop); return 1;
718 }
719 } else {
720 /* HEY: this used to be ncoeff[4], but its passing to nrrdJoin caused
721 "dereferencing type-punned pointer might break strict-aliasing rules"
722 warning; GLK not sure how else to fix it */
723 ncoeff = AIR_CALLOC(4, Nrrd*);
724 airMopAdd(mop, ncoeff, airFree, airMopAlways);
725 airMopAdd(mop, ncoeff[0]=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
726 airMopAdd(mop, ncoeff[1]=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
727 airMopAdd(mop, ncoeff[2]=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
728 airMopAdd(mop, ncoeff[3]=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
729 switch(axis) {
730 case 0:
731 ELL_4V_SET(ci, 0, 4, 5, 6);
732 break;
733 case 1:
734 ELL_4V_SET(ci, 0, 1, 3, 6);
735 break;
736 case 2:
737 ELL_4V_SET(ci, 0, 1, 2, 4);
738 break;
739 default:
740 biffAddf(TEN, "%s: axis %d bogus", me, axis);
741 airMopError(mop); return 1;
742 break;
743 }
744 if (nrrdSlice(nslice, nten, axis+1, pos)
745 || nrrdSlice(ncoeff[0], nslice, 0, ci[0])
746 || nrrdSlice(ncoeff[1], nslice, 0, ci[1])
747 || nrrdSlice(ncoeff[2], nslice, 0, ci[2])
748 || nrrdSlice(ncoeff[3], nslice, 0, ci[3])
749 || nrrdJoin(nout, (const Nrrd *const*)ncoeff, 4, 0, AIR_TRUE)) {
750 biffMovef(TEN, NRRD, "%s: trouble collecting coefficients", me);
751 airMopError(mop); return 1;
752 }
753 nout->axis[0].kind = nrrdKind2DMaskedSymMatrix;
754 }
755
756 airMopOkay(mop);
757 return 0;
758 }
759
760 #define Txx (ten[1])
761 #define Txy (ten[2])
762 #define Txz (ten[3])
763 #define Tyy (ten[4])
764 #define Tyz (ten[5])
765 #define Tzz (ten[6])
766
767 #define SQRT_1_OVER_2 0.70710678118654752440
768 #define SQRT_1_OVER_3 0.57735026918962576450
769 #define SQRT_2_OVER_3 0.81649658092772603272
770 #define SQRT_1_OVER_6 0.40824829046386301635
771
772 /*
773 ** very special purpose: compute tensor-valued gradient
774 ** of eigenvalue skewness, but have to be given two
775 ** other invariant gradients, NORMALIZED, to which
776 ** eigenvalue skewness should be perpendicular
777 */
778 void
_tenEvalSkewnessGradient_d(double skw[7],const double perp1[7],const double perp2[7],const double ten[7],const double minnorm)779 _tenEvalSkewnessGradient_d(double skw[7],
780 const double perp1[7],
781 const double perp2[7],
782 const double ten[7],
783 const double minnorm) {
784 /* static const char me[]="_tenEvalSkewnessGradient_d"; */
785 double dot, scl, norm;
786
787 /* start with gradient of determinant */
788 TEN_T_SET(skw, ten[0],
789 Tyy*Tzz - Tyz*Tyz, Txz*Tyz - Txy*Tzz, Txy*Tyz - Txz*Tyy,
790 Txx*Tzz - Txz*Txz, Txy*Txz - Tyz*Txx,
791 Txx*Tyy - Txy*Txy);
792 /* this normalization is so that minnorm comparison below
793 is meaningful regardless of scale of input */
794 /* HEY: should have better handling of case where determinant
795 gradient magnitude is near zero */
796 scl = 1.0/(DBL_EPSILON + TEN_T_NORM(skw));
797 TEN_T_SCALE(skw, scl, skw);
798 dot = TEN_T_DOT(skw, perp1);
799 TEN_T_SCALE_INCR(skw, -dot, perp1);
800 dot = TEN_T_DOT(skw, perp2);
801 TEN_T_SCALE_INCR(skw, -dot, perp2);
802 norm = TEN_T_NORM(skw);
803 if (norm < minnorm) {
804 /* skw is at an extremum, should diagonalize */
805 double eval[3], evec[9], matA[9], matB[9], matC[9], mev, third;
806
807 tenEigensolve_d(eval, evec, ten);
808 mev = (eval[0] + eval[1] + eval[2])/3;
809 eval[0] -= mev;
810 eval[1] -= mev;
811 eval[2] -= mev;
812 third = (eval[0]*eval[0]*eval[0]
813 + eval[1]*eval[1]*eval[1]
814 + eval[2]*eval[2]*eval[2])/3;
815 if (third > 0) {
816 /* skw is positive: linear: eval[1] = eval[2] */
817 ELL_3MV_OUTER(matA, evec + 1*3, evec + 1*3);
818 ELL_3MV_OUTER(matB, evec + 2*3, evec + 2*3);
819 } else {
820 /* skw is negative: planar: eval[0] = eval[1] */
821 ELL_3MV_OUTER(matA, evec + 0*3, evec + 0*3);
822 ELL_3MV_OUTER(matB, evec + 1*3, evec + 1*3);
823 }
824 ELL_3M_SCALE_ADD2(matC, SQRT_1_OVER_2, matA, -SQRT_1_OVER_2, matB);
825 TEN_M2T(skw, matC);
826 /* have to make sure that this contrived tensor
827 is indeed orthogonal to perp1 and perp2 */
828 dot = TEN_T_DOT(skw, perp1);
829 TEN_T_SCALE_INCR(skw, -dot, perp1);
830 dot = TEN_T_DOT(skw, perp2);
831 TEN_T_SCALE_INCR(skw, -dot, perp2);
832 norm = TEN_T_NORM(skw);
833 }
834 TEN_T_SCALE(skw, 1.0/norm, skw);
835 return;
836 }
837
838 void
tenInvariantGradientsK_d(double mu1[7],double mu2[7],double skw[7],const double ten[7],const double minnorm)839 tenInvariantGradientsK_d(double mu1[7], double mu2[7], double skw[7],
840 const double ten[7], const double minnorm) {
841 double dot, norm;
842
843 TEN_T_SET(mu1, ten[0],
844 SQRT_1_OVER_3, 0, 0,
845 SQRT_1_OVER_3, 0,
846 SQRT_1_OVER_3);
847
848 TEN_T_SET(mu2, ten[0],
849 2*Txx - Tyy - Tzz, 3*Txy, 3*Txz,
850 2*Tyy - Txx - Tzz, 3*Tyz,
851 2*Tzz - Txx - Tyy);
852 norm = TEN_T_NORM(mu2);
853 if (norm < minnorm) {
854 /* they gave us a diagonal matrix */
855 TEN_T_SET(mu2, ten[0],
856 SQRT_2_OVER_3, 0, 0,
857 -SQRT_1_OVER_6, 0,
858 -SQRT_1_OVER_6);
859 }
860 /* next two lines shouldn't really be necessary */
861 dot = TEN_T_DOT(mu2, mu1);
862 TEN_T_SCALE_INCR(mu2, -dot, mu1);
863 norm = TEN_T_NORM(mu2);
864 TEN_T_SCALE(mu2, 1.0/norm, mu2);
865 _tenEvalSkewnessGradient_d(skw, mu1, mu2, ten, minnorm);
866
867 return;
868 }
869
870 void
tenInvariantGradientsR_d(double R1[7],double R2[7],double R3[7],const double ten[7],const double minnorm)871 tenInvariantGradientsR_d(double R1[7], double R2[7], double R3[7],
872 const double ten[7], const double minnorm) {
873 double dot, dev[7], norm, tenNorm, devNorm;
874
875 TEN_T_COPY(R1, ten);
876 tenNorm = norm = TEN_T_NORM(R1);
877 if (norm < minnorm) {
878 TEN_T_SET(R1, ten[0],
879 SQRT_1_OVER_3, 0, 0,
880 SQRT_1_OVER_3, 0,
881 SQRT_1_OVER_3);
882 norm = TEN_T_NORM(R1);
883 }
884 TEN_T_SCALE(R1, 1.0/norm, R1);
885
886 TEN_T_SET(dev, ten[0],
887 (2*Txx - Tyy - Tzz)/3, Txy, Txz,
888 (2*Tyy - Txx - Tzz)/3, Tyz,
889 (2*Tzz - Txx - Tyy)/3);
890 devNorm = TEN_T_NORM(dev);
891 if (devNorm < minnorm) {
892 /* they gave us a diagonal matrix */
893 TEN_T_SET(R2, ten[0],
894 SQRT_2_OVER_3, 0, 0,
895 -SQRT_1_OVER_6, 0,
896 -SQRT_1_OVER_6);
897 } else {
898 TEN_T_SCALE_ADD2(R2, tenNorm/devNorm, dev, -devNorm/tenNorm, ten);
899 }
900 /* next two lines shouldn't really be necessary */
901 dot = TEN_T_DOT(R2, R1);
902 TEN_T_SCALE_INCR(R2, -dot, R1);
903 norm = TEN_T_NORM(R2);
904 if (norm < minnorm) {
905 /* Traceless tensor */
906 TEN_T_SET(R2, ten[0],
907 SQRT_2_OVER_3, 0, 0,
908 -SQRT_1_OVER_6, 0,
909 -SQRT_1_OVER_6);
910 } else {
911 TEN_T_SCALE(R2, 1.0/norm, R2);
912 }
913 _tenEvalSkewnessGradient_d(R3, R1, R2, ten, minnorm);
914
915 return;
916 }
917
918 /*
919 ** evec must be pre-computed (unit-length eigenvectors) and given to us
920 */
921 void
tenRotationTangents_d(double phi1[7],double phi2[7],double phi3[7],const double evec[9])922 tenRotationTangents_d(double phi1[7],
923 double phi2[7],
924 double phi3[7],
925 const double evec[9]) {
926 double outA[9], outB[9], mat[9];
927
928 if (phi1) {
929 phi1[0] = 1.0;
930 ELL_3MV_OUTER(outA, evec + 1*3, evec + 2*3);
931 ELL_3MV_OUTER(outB, evec + 2*3, evec + 1*3);
932 ELL_3M_SCALE_ADD2(mat, SQRT_1_OVER_2, outA, SQRT_1_OVER_2, outB);
933 TEN_M2T(phi1, mat);
934 }
935
936 if (phi2) {
937 phi2[0] = 1.0;
938 ELL_3MV_OUTER(outA, evec + 0*3, evec + 2*3);
939 ELL_3MV_OUTER(outB, evec + 2*3, evec + 0*3);
940 ELL_3M_SCALE_ADD2(mat, SQRT_1_OVER_2, outA, SQRT_1_OVER_2, outB);
941 TEN_M2T(phi2, mat);
942 }
943
944 if (phi3) {
945 phi3[0] = 1.0;
946 ELL_3MV_OUTER(outA, evec + 0*3, evec + 1*3);
947 ELL_3MV_OUTER(outB, evec + 1*3, evec + 0*3);
948 ELL_3M_SCALE_ADD2(mat, SQRT_1_OVER_2, outA, SQRT_1_OVER_2, outB);
949 TEN_M2T(phi3, mat);
950 }
951
952 return;
953 }
954
955 void
tenInv_f(float inv[7],const float ten[7])956 tenInv_f(float inv[7], const float ten[7]) {
957 float det;
958
959 TEN_T_INV(inv, ten, det);
960 }
961
962 void
tenInv_d(double inv[7],const double ten[7])963 tenInv_d(double inv[7], const double ten[7]) {
964 double det;
965
966 TEN_T_INV(inv, ten, det);
967 }
968
969 void
tenLogSingle_d(double logten[7],const double ten[7])970 tenLogSingle_d(double logten[7], const double ten[7]) {
971 double eval[3], evec[9];
972 unsigned int ii;
973
974 tenEigensolve_d(eval, evec, ten);
975 for (ii=0; ii<3; ii++) {
976 eval[ii] = log(eval[ii]);
977 if (!AIR_EXISTS(eval[ii])) {
978 eval[ii] = -FLT_MAX; /* making stuff up */
979 }
980 }
981 tenMakeSingle_d(logten, ten[0], eval, evec);
982 }
983
984 void
tenLogSingle_f(float logten[7],const float ten[7])985 tenLogSingle_f(float logten[7], const float ten[7]) {
986 float eval[3], evec[9];
987 unsigned int ii;
988
989 tenEigensolve_f(eval, evec, ten);
990 for (ii=0; ii<3; ii++) {
991 eval[ii] = AIR_CAST(float, log(eval[ii]));
992 if (!AIR_EXISTS(eval[ii])) {
993 eval[ii] = -FLT_MAX/10; /* still making stuff up */
994 }
995 }
996 tenMakeSingle_f(logten, ten[0], eval, evec);
997 }
998
999 void
tenExpSingle_d(double expten[7],const double ten[7])1000 tenExpSingle_d(double expten[7], const double ten[7]) {
1001 double eval[3], evec[9];
1002 unsigned int ii;
1003
1004 tenEigensolve_d(eval, evec, ten);
1005 for (ii=0; ii<3; ii++) {
1006 eval[ii] = exp(eval[ii]);
1007 }
1008 tenMakeSingle_d(expten, ten[0], eval, evec);
1009 }
1010
1011 void
tenExpSingle_f(float expten[7],const float ten[7])1012 tenExpSingle_f(float expten[7], const float ten[7]) {
1013 float eval[3], evec[9];
1014 unsigned int ii;
1015
1016 tenEigensolve_f(eval, evec, ten);
1017 for (ii=0; ii<3; ii++) {
1018 eval[ii] = AIR_CAST(float, exp(eval[ii]));
1019 }
1020 tenMakeSingle_f(expten, ten[0], eval, evec);
1021 }
1022
1023 void
tenSqrtSingle_d(double sqrtten[7],const double ten[7])1024 tenSqrtSingle_d(double sqrtten[7], const double ten[7]) {
1025 double eval[3], evec[9];
1026 unsigned int ii;
1027
1028 tenEigensolve_d(eval, evec, ten);
1029 for (ii=0; ii<3; ii++) {
1030 eval[ii] = eval[ii] > 0 ? sqrt(eval[ii]) : 0;
1031 }
1032 tenMakeSingle_d(sqrtten, ten[0], eval, evec);
1033 }
1034
1035 void
tenSqrtSingle_f(float sqrtten[7],const float ten[7])1036 tenSqrtSingle_f(float sqrtten[7], const float ten[7]) {
1037 float eval[3], evec[9];
1038 unsigned int ii;
1039
1040 tenEigensolve_f(eval, evec, ten);
1041 for (ii=0; ii<3; ii++) {
1042 eval[ii] = AIR_CAST(float, eval[ii] > 0 ? sqrt(eval[ii]) : 0);
1043 }
1044 tenMakeSingle_f(sqrtten, ten[0], eval, evec);
1045 }
1046
1047 void
tenPowSingle_d(double powten[7],const double ten[7],double power)1048 tenPowSingle_d(double powten[7], const double ten[7], double power) {
1049 double eval[3], _eval[3], evec[9];
1050 unsigned int ii;
1051
1052 tenEigensolve_d(_eval, evec, ten);
1053 for (ii=0; ii<3; ii++) {
1054 eval[ii] = pow(_eval[ii], power);
1055 }
1056 tenMakeSingle_d(powten, ten[0], eval, evec);
1057 }
1058
1059 void
tenPowSingle_f(float powten[7],const float ten[7],float power)1060 tenPowSingle_f(float powten[7], const float ten[7], float power) {
1061 float eval[3], evec[9];
1062 unsigned int ii;
1063
1064 tenEigensolve_f(eval, evec, ten);
1065 for (ii=0; ii<3; ii++) {
1066 eval[ii] = AIR_CAST(float, pow(eval[ii], power));
1067 }
1068 tenMakeSingle_f(powten, ten[0], eval, evec);
1069 }
1070
1071 double
tenDoubleContract_d(double a[7],double T[21],double b[7])1072 tenDoubleContract_d(double a[7], double T[21], double b[7]) {
1073 double ret;
1074
1075 ret = (+ 1*1*T[ 0]*a[1]*b[1] + 1*2*T[ 1]*a[2]*b[1] + 1*2*T[ 2]*a[3]*b[1] + 1*1*T[ 3]*a[4]*b[1] + 1*2*T[ 4]*a[5]*b[1] + 1*1*T[ 5]*a[6]*b[1] +
1076 + 2*1*T[ 1]*a[1]*b[2] + 2*2*T[ 6]*a[2]*b[2] + 2*2*T[ 7]*a[3]*b[2] + 2*1*T[ 8]*a[4]*b[2] + 2*2*T[ 9]*a[5]*b[2] + 2*1*T[10]*a[6]*b[2] +
1077 + 2*1*T[ 2]*a[1]*b[3] + 2*2*T[ 7]*a[2]*b[3] + 2*2*T[11]*a[3]*b[3] + 2*1*T[12]*a[4]*b[3] + 2*2*T[13]*a[5]*b[3] + 2*1*T[14]*a[6]*b[3] +
1078 + 1*1*T[ 3]*a[1]*b[4] + 1*2*T[ 8]*a[2]*b[4] + 1*2*T[12]*a[3]*b[4] + 1*1*T[15]*a[4]*b[4] + 1*2*T[16]*a[5]*b[4] + 1*1*T[17]*a[6]*b[4] +
1079 + 2*1*T[ 4]*a[1]*b[5] + 2*2*T[ 9]*a[2]*b[5] + 2*2*T[13]*a[3]*b[5] + 2*1*T[16]*a[4]*b[5] + 2*2*T[18]*a[5]*b[5] + 2*1*T[19]*a[6]*b[5] +
1080 + 1*1*T[ 5]*a[1]*b[6] + 1*2*T[10]*a[2]*b[6] + 1*2*T[14]*a[3]*b[6] + 1*1*T[17]*a[4]*b[6] + 1*2*T[19]*a[5]*b[6] + 1*1*T[20]*a[6]*b[6]);
1081
1082 return ret;
1083 }
1084