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