GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/ten/tensor.c Lines: 7 523 1.3 %
Date: 2017-05-26 Branches: 7 288 2.4 %

Line Branch Exec Source
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
25
#include "ten.h"
26
#include "privateTen.h"
27
28
int tenVerbose = 0;
29
30
void
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
59
tenTensorCheck(const Nrrd *nin, int wantType, int want4D, int useBiff) {
60
  static const char me[]="tenTensorCheck";
61
62
32
  if (!nin) {
63
    if (useBiff) biffAddf(TEN, "%s: got NULL pointer", me);
64
    return 1;
65
  }
66

32
  if (wantType) {
67
32
    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

32
  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
16
  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
16
  return 0;
94
16
}
95
96
int
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
}