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 #include "ten.h"
25 #include "privateTen.h"
26 
27 const char *
28 tenDWMRIModalityKey = "modality";
29 
30 const char *
31 tenDWMRIModalityVal = "DWMRI";
32 
33 const char *
34 tenDWMRINAVal = "n/a";
35 
36 const char *
37 tenDWMRIBValueKey = "DWMRI_b-value";
38 
39 const char *
40 tenDWMRIGradKeyFmt = "DWMRI_gradient_%04u";
41 
42 const char *
43 tenDWMRIBmatKeyFmt = "DWMRI_B-matrix_%04u";
44 
45 const char *
46 tenDWMRINexKeyFmt = "DWMRI_NEX_%04u";
47 
48 const char *
49 tenDWMRISkipKeyFmt = "DWMRI_skip_%04u";
50 
51 /*
52 ******** tenDWMRIKeyValueParse
53 **
54 ** Parses through key-value pairs in the NRRD header to determine the
55 ** list of diffusion-sensitizing gradient directions, or B-matrices
56 ** (depending to what was found), according the NAMIC conventions.
57 ** This requires, among other things, that ndwi be have exactly one
58 ** axis with kind nrrdKindList (or nrrdKindVector), which is taken to
59 ** be the DWI axis.
60 **
61 ** Either *ngradP or *nbmatP is set to a newly- allocated nrrd
62 ** containing this information, and the other one is set to NULL
63 ** The (scalar) b-value is stored in *bP. The image values that are
64 ** to be skipped are stored in the *skipP array (allocated here),
65 ** the length of that array is stored in *skipNumP.  Unlike the skip
66 ** array used internally with tenEstimate, this is just a simple 1-D
67 ** array; it is not a list of pairs of (index,skipBool).
68 */
69 int
tenDWMRIKeyValueParse(Nrrd ** ngradP,Nrrd ** nbmatP,double * bP,unsigned int ** skipP,unsigned int * skipNumP,const Nrrd * ndwi)70 tenDWMRIKeyValueParse(Nrrd **ngradP, Nrrd **nbmatP, double *bP,
71                       unsigned int **skipP, unsigned int *skipNumP,
72                       const Nrrd *ndwi) {
73   static const char me[]="tenDWMRIKeyValueParse";
74   char tmpKey[AIR_STRLEN_MED],
75     key[AIR_STRLEN_MED], *val;
76   const char *keyFmt;
77   int dwiAxis;
78   unsigned int axi, dwiIdx, dwiNum, valNum, valIdx, parsedNum,
79     nexNum, nexIdx, skipIdx, *skipLut;
80   Nrrd *ninfo;
81   double *info, normMax, norm;
82   airArray *mop, *skipArr;
83 
84   if (!( ngradP && nbmatP && skipP && skipNumP && bP && ndwi )) {
85     biffAddf(TEN, "%s: got NULL pointer", me);
86     return 1;
87   }
88 
89   /* check modality */
90   val = nrrdKeyValueGet(ndwi, tenDWMRIModalityKey);
91   if (!val) {
92     biffAddf(TEN, "%s: didn't have \"%s\" key", me, tenDWMRIModalityKey);
93     return 1;
94   }
95   if (strncmp(tenDWMRIModalityVal, val + strspn(val, AIR_WHITESPACE),
96               strlen(tenDWMRIModalityVal))) {
97     biffAddf(TEN, "%s: \"%s\" value was \"%s\", not \"%s\"", me,
98              tenDWMRIModalityKey, val, tenDWMRIModalityVal);
99     return 1;
100   }
101   val = (char *)airFree(val);
102 
103   /* learn b-value */
104   val = nrrdKeyValueGet(ndwi, tenDWMRIBValueKey);
105   if (!val) {
106     biffAddf(TEN, "%s: didn't have \"%s\" key", me, tenDWMRIBValueKey);
107     return 1;
108   }
109   if (1 != sscanf(val, "%lg", bP)) {
110     biffAddf(TEN, "%s: couldn't parse float from value \"%s\" "
111              "for key \"%s\"", me,
112              val, tenDWMRIBValueKey);
113     return 1;
114   }
115   val = (char *)airFree(val);
116 
117   /* find single DWI axis, set dwiNum to its size */
118   dwiAxis = -1;
119   for (axi=0; axi<ndwi->dim; axi++) {
120     /* the use of nrrdKindVector here is out of deference to how ITK's
121        itkNrrdImageIO.cxx uses nrrdKindVector for VECTOR pixels */
122     if (nrrdKindList == ndwi->axis[axi].kind
123         || nrrdKindVector == ndwi->axis[axi].kind) {
124       if (-1 != dwiAxis) {
125         biffAddf(TEN, "%s: already saw %s or %s kind on axis %d", me,
126                  airEnumStr(nrrdKind, nrrdKindList),
127                  airEnumStr(nrrdKind, nrrdKindVector), dwiAxis);
128         return 1;
129       }
130       dwiAxis = axi;
131     }
132   }
133   if (-1 == dwiAxis) {
134     biffAddf(TEN, "%s: did not see \"%s\" kind on any axis", me,
135              airEnumStr(nrrdKind, nrrdKindList));
136     return 1;
137   }
138   dwiNum = ndwi->axis[dwiAxis].size;
139 
140   /* figure out if we're parsing gradients or b-matrices */
141   sprintf(tmpKey, tenDWMRIGradKeyFmt, 0);
142   val = nrrdKeyValueGet(ndwi, tmpKey);
143   if (val) {
144     valNum = 3;
145   } else {
146     valNum = 6;
147     sprintf(key, tenDWMRIBmatKeyFmt, 0);
148     val = nrrdKeyValueGet(ndwi, key);
149     if (!val) {
150       biffAddf(TEN, "%s: saw neither \"%s\" nor \"%s\" key", me,
151                tmpKey, key);
152       return 1;
153     }
154   }
155   val = (char *)airFree(val);
156 
157   /* set up parsing and allocate one of output nrrds */
158   if (3 == valNum) {
159     keyFmt = tenDWMRIGradKeyFmt;
160     ninfo = *ngradP = nrrdNew();
161     *nbmatP = NULL;
162   } else {
163     keyFmt = tenDWMRIBmatKeyFmt;
164     *ngradP = NULL;
165     ninfo = *nbmatP = nrrdNew();
166   }
167   if (nrrdMaybeAlloc_va(ninfo, nrrdTypeDouble, 2,
168                         AIR_CAST(size_t, valNum),
169                         AIR_CAST(size_t, dwiNum))) {
170     biffMovef(TEN, NRRD, "%s: couldn't allocate output", me);
171     return 1;
172   }
173   info = (double *)(ninfo->data);
174 
175   /* set up skip list recording */
176   mop = airMopNew();
177   skipArr = airArrayNew((void**)skipP, skipNumP, sizeof(unsigned int), 16);
178   airMopAdd(mop, skipArr, (airMopper)airArrayNix, airMopAlways);
179   skipLut = AIR_CALLOC(dwiNum, unsigned int);
180   airMopAdd(mop, skipLut, airFree, airMopAlways);
181   if (!skipLut) {
182     biffAddf(TEN, "%s: couldn't allocate skip LUT", me);
183     airMopError(mop); return 1;
184   }
185 
186   /* parse values in ninfo */
187   for (dwiIdx=0; dwiIdx<dwiNum; dwiIdx++) {
188     sprintf(key, keyFmt, dwiIdx);
189     val = nrrdKeyValueGet(ndwi, key);
190     if (!val) {
191       biffAddf(TEN, "%s: didn't see \"%s\" key", me, key);
192       airMopError(mop); return 1;
193     }
194     airToLower(val);
195     if (!strncmp(tenDWMRINAVal, val + strspn(val, AIR_WHITESPACE),
196                  strlen(tenDWMRINAVal))) {
197       /* have no sensible gradient or B-matrix info here, and must skip */
198       for (valIdx=0; valIdx<valNum; valIdx++) {
199         info[valIdx] = AIR_NAN;
200       }
201       skipIdx = airArrayLenIncr(skipArr, 1);
202       (*skipP)[skipIdx] = dwiIdx;
203       skipLut[dwiIdx] = AIR_TRUE;
204       /* can't have NEX on a skipped gradient or B-matrix */
205       val = (char *)airFree(val);
206       sprintf(key, tenDWMRINexKeyFmt, dwiIdx);
207       val = nrrdKeyValueGet(ndwi, key);
208       if (val) {
209         biffAddf(TEN, "%s: can't have NEX of skipped DWI %u", me, skipIdx);
210         airMopError(mop); return 1;
211       }
212       nexNum = 1; /* for "info +=" below */
213     } else {
214       /* we probably do have sensible gradient or B-matrix info */
215       parsedNum = airParseStrD(info, val, AIR_WHITESPACE, valNum);
216       if (valNum != parsedNum) {
217         biffAddf(TEN, "%s: couldn't parse %d floats in value \"%s\" "
218                  "for key \"%s\" (only got %d)",
219                  me, valNum, val, key, parsedNum);
220         airMopError(mop); return 1;
221       }
222       val = (char *)airFree(val);
223       sprintf(key, tenDWMRINexKeyFmt, dwiIdx);
224       val = nrrdKeyValueGet(ndwi, key);
225       if (!val) {
226         /* there is no NEX indicated */
227         nexNum = 1;
228       } else {
229         if (1 != sscanf(val, "%u", &nexNum)) {
230           biffAddf(TEN, "%s: couldn't parse integer in value \"%s\" "
231                    "for key \"%s\"", me, val, key);
232           airMopError(mop); return 1;
233         }
234         val = (char *)airFree(val);
235         if (!( nexNum >= 1 )) {
236           biffAddf(TEN, "%s: NEX (%d) for DWI %d not >= 1",
237                    me, nexNum, dwiIdx);
238           airMopError(mop); return 1;
239         }
240         if (!( dwiIdx + nexNum - 1 < dwiNum )) {
241           biffAddf(TEN, "%s: NEX %d for DWI %d implies %d DWI > real # DWI %d",
242                    me, nexNum, dwiIdx, dwiIdx + nexNum, dwiNum);
243           airMopError(mop); return 1;
244         }
245         for (nexIdx=1; nexIdx<nexNum; nexIdx++) {
246           sprintf(key, keyFmt, dwiIdx+nexIdx);
247           val = nrrdKeyValueGet(ndwi, key);
248           if (val) {
249             val = (char *)airFree(val);
250             biffAddf(TEN, "%s: shouldn't have key \"%s\" with "
251                      "NEX %d for DWI %d", me, key, nexNum, dwiIdx);
252             airMopError(mop); return 1;
253           }
254           for (valIdx=0; valIdx<valNum; valIdx++) {
255             info[valIdx + valNum*nexIdx] = info[valIdx];
256           }
257         }
258         dwiIdx += nexNum-1;
259       }
260     }
261     info += valNum*nexNum;
262   }
263 
264   /* perhaps too paranoid: see if there are extra keys,
265      which probably implies confusion/mismatch between number of
266      gradients and number of values */
267   sprintf(key, keyFmt, dwiIdx);
268   val = nrrdKeyValueGet(ndwi, key);
269   if (val) {
270     biffAddf(TEN, "%s: saw \"%s\" key, more than required %u keys, "
271              "likely mismatch between keys and actual gradients",
272              me, key, dwiIdx);
273     airMopError(mop); return 1;
274   }
275 
276   /* second pass: see which ones are skipped, even though gradient/B-matrix
277      information has been specified */
278   for (dwiIdx=0; dwiIdx<dwiNum; dwiIdx++) {
279     sprintf(key, tenDWMRISkipKeyFmt, dwiIdx);
280     val = nrrdKeyValueGet(ndwi, key);
281     if (val) {
282       airToLower(val);
283       if (!strncmp("true", val + strspn(val, AIR_WHITESPACE),
284                    strlen("true"))) {
285         skipIdx = airArrayLenIncr(skipArr, 1);
286         (*skipP)[skipIdx] = dwiIdx;
287         skipLut[dwiIdx] = AIR_TRUE;
288       }
289     }
290   }
291 
292   /* normalize so that maximal norm is 1.0 */
293   /* Thu Dec 20 03:25:20 CST 2012 this rescaling IS in fact what is
294      causing the small discrepency between ngrad before and after
295      saving to KVPs. The problem is not related to how the gradient
296      vector coefficients are recovered from the string-based
297      representation; that is likely bit-for-bit correct.  The problem
298      is when everything is rescaled by 1.0/normMax: a "normalized"
299      vector will not have *exactly* length 1.0. So what can be done
300      to prevent pointlessly altering the lengths of vectors that were
301      close enough to unit-length?  Is there some more 754-savvy
302      way of doing this normalization? */
303   normMax = 0;
304   info = (double *)(ninfo->data);
305   for (dwiIdx=0; dwiIdx<dwiNum; dwiIdx++) {
306     if (!skipLut[dwiIdx]) {
307       if (3 == valNum) {
308         norm = ELL_3V_LEN(info);
309       } else {
310         norm = sqrt(info[0]*info[0] + 2*info[1]*info[1] + 2*info[2]*info[2]
311                                     +   info[3]*info[3] + 2*info[4]*info[4]
312                                                         +   info[5]*info[5]);
313       }
314       normMax = AIR_MAX(normMax, norm);
315     }
316     info += valNum;
317   }
318   info = (double *)(ninfo->data);
319   for (dwiIdx=0; dwiIdx<dwiNum; dwiIdx++) {
320     if (!skipLut[dwiIdx]) {
321       if (3 == valNum) {
322         ELL_3V_SCALE(info, 1.0/normMax, info);
323       } else {
324         ELL_6V_SCALE(info, 1.0/normMax, info);
325       }
326     }
327     info += valNum;
328   }
329 
330   airMopOkay(mop);
331   return 0;
332 }
333 
334 /*
335 ******** tenBMatrixCalc
336 **
337 ** given a list of gradient directions (arbitrary type), contructs the
338 ** B-matrix that records how each coefficient of the diffusion tensor
339 ** is weighted in the diffusion weighted images.  Matrix will be a
340 ** 6-by-N 2D array of doubles.
341 **
342 ** NOTE 1: The ordering of the elements in each row is (like the ordering
343 ** of the tensor elements in all of ten):
344 **
345 **    Bxx  Bxy  Bxz   Byy  Byz   Bzz
346 **
347 ** NOTE 2: The off-diagonal elements are NOT pre-multiplied by two.
348 */
349 int
tenBMatrixCalc(Nrrd * nbmat,const Nrrd * _ngrad)350 tenBMatrixCalc(Nrrd *nbmat, const Nrrd *_ngrad) {
351   static const char me[]="tenBMatrixCalc";
352   Nrrd *ngrad;
353   double *bmat, *G;
354   int DD, dd;
355   airArray *mop;
356 
357   if (!(nbmat && _ngrad && !tenGradientCheck(_ngrad, nrrdTypeDefault, 1))) {
358     biffAddf(TEN, "%s: got NULL pointer or invalid arg", me);
359     return 1;
360   }
361   mop = airMopNew();
362   airMopAdd(mop, ngrad=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
363   if (nrrdConvert(ngrad, _ngrad, nrrdTypeDouble)
364       || nrrdMaybeAlloc_va(nbmat, nrrdTypeDouble, 2,
365                            AIR_CAST(size_t, 6), ngrad->axis[1].size)) {
366     biffMovef(TEN, NRRD, "%s: trouble", me);
367     airMopError(mop); return 1;
368   }
369 
370   DD = ngrad->axis[1].size;
371   G = (double*)(ngrad->data);
372   bmat = (double*)(nbmat->data);
373   for (dd=0; dd<DD; dd++) {
374     ELL_6V_SET(bmat,
375                G[0]*G[0], G[0]*G[1], G[0]*G[2],
376                           G[1]*G[1], G[1]*G[2],
377                                      G[2]*G[2]);
378     G += 3;
379     bmat += 6;
380   }
381   nbmat->axis[0].kind = nrrdKind3DSymMatrix;
382 
383   airMopOkay(mop);
384   return 0;
385 }
386 
387 /*
388 ******** tenEMatrixCalc
389 **
390 */
391 int
tenEMatrixCalc(Nrrd * nemat,const Nrrd * _nbmat,int knownB0)392 tenEMatrixCalc(Nrrd *nemat, const Nrrd *_nbmat, int knownB0) {
393   static const char me[]="tenEMatrixCalc";
394   Nrrd *nbmat, *ntmp;
395   airArray *mop;
396   ptrdiff_t padmin[2], padmax[2];
397   unsigned int ri;
398   double *bmat;
399 
400   if (!(nemat && _nbmat)) {
401     biffAddf(TEN, "%s: got NULL pointer", me);
402     return 1;
403   }
404   if (tenBMatrixCheck(_nbmat, nrrdTypeDefault, 6)) {
405     biffAddf(TEN, "%s: problem with B matrix", me);
406     return 1;
407   }
408   mop = airMopNew();
409   airMopAdd(mop, nbmat=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
410   if (knownB0) {
411     if (nrrdConvert(nbmat, _nbmat, nrrdTypeDouble)) {
412       biffMovef(TEN, NRRD, "%s: couldn't convert given bmat to doubles", me);
413       airMopError(mop); return 1;
414     }
415   } else {
416     airMopAdd(mop, ntmp=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
417     if (nrrdConvert(ntmp, _nbmat, nrrdTypeDouble)) {
418       biffMovef(TEN, NRRD, "%s: couldn't convert given bmat to doubles", me);
419       airMopError(mop); return 1;
420     }
421     ELL_2V_SET(padmin, 0, 0);
422     ELL_2V_SET(padmax, 6, _nbmat->axis[1].size-1);
423     if (nrrdPad_nva(nbmat, ntmp, padmin, padmax, nrrdBoundaryPad, -1)) {
424       biffMovef(TEN, NRRD, "%s: couldn't pad given bmat", me);
425       airMopError(mop); return 1;
426     }
427   }
428   bmat = (double*)(nbmat->data);
429   /* HERE is where the off-diagonal elements get multiplied by 2 */
430   for (ri=0; ri<nbmat->axis[1].size; ri++) {
431     bmat[1] *= 2;
432     bmat[2] *= 2;
433     bmat[4] *= 2;
434     bmat += nbmat->axis[0].size;
435   }
436   if (ell_Nm_pseudo_inv(nemat, nbmat)) {
437     biffMovef(TEN, ELL, "%s: trouble pseudo-inverting B-matrix", me);
438     airMopError(mop); return 1;
439   }
440   airMopOkay(mop);
441   return 0;
442 }
443 
444 /*
445 ******** tenEstimateLinearSingle_d
446 **
447 ** estimate one single tensor
448 **
449 ** !! requires being passed a pre-allocated double array "vbuf" which is
450 ** !! used for intermediate calculations (details below)
451 **
452 ** DD is always the length of the dwi[] array
453 **
454 ** -------------- IF knownB0 -------------------------
455 ** input:
456 ** dwi[0] is the B0 image, dwi[1]..dwi[DD-1] are the (DD-1) DWI values,
457 ** emat is the (DD-1)-by-6 estimation matrix, which is the pseudo-inverse
458 ** of the B-matrix (after the off-diagonals have been multiplied by 2).
459 ** vbuf[] is allocated for (at least) DD-1 doubles (DD is fine)
460 **
461 ** output:
462 ** ten[0]..ten[6] will be the confidence value followed by the tensor
463 ** if B0P, then *B0P is set to the B0 value used in calcs: max(b0,1)
464 ** -------------- IF !knownB0 -------------------------
465 ** input:
466 ** dwi[0]..dwi[DD-1] are the DD DWI values, emat is the DD-by-7 estimation
467 ** matrix.  The 7th column is for estimating the B0 image.
468 ** vbuf[] is allocated for DD doubles
469 **
470 ** output:
471 ** ten[0]..ten[6] will be the confidence value followed by the tensor
472 ** if B0P, then *B0P is set to estimated B0 value.
473 ** ----------------------------------------------------
474 */
475 void
tenEstimateLinearSingle_d(double * ten,double * B0P,const double * dwi,const double * emat,double * vbuf,unsigned int DD,int knownB0,double thresh,double soft,double b)476 tenEstimateLinearSingle_d(double *ten, double *B0P,              /* output */
477                           const double *dwi, const double *emat, /* input .. */
478                           double *vbuf, unsigned int DD, int knownB0,
479                           double thresh, double soft, double b) {
480   double logB0, tmp, mean;
481   unsigned int ii, jj;
482   /* static const char me[]="tenEstimateLinearSingle_d"; */
483 
484   if (knownB0) {
485     if (B0P) {
486       /* we save this as a courtesy */
487       *B0P = AIR_MAX(dwi[0], 1);
488     }
489     logB0 = log(AIR_MAX(dwi[0], 1));
490     mean = 0;
491     for (ii=1; ii<DD; ii++) {
492       tmp = AIR_MAX(dwi[ii], 1);
493       mean += tmp;
494       vbuf[ii-1] = (logB0 - log(tmp))/b;
495       /* if (tenVerbose) {
496         fprintf(stderr, "vbuf[%d] = %f\n", ii-1, vbuf[ii-1]);
497       } */
498     }
499     mean /= DD-1;
500     if (soft) {
501       ten[0] = AIR_AFFINE(-1, airErf((mean - thresh)/(soft + 0.000001)), 1,
502                           0, 1);
503     } else {
504       ten[0] = mean > thresh;
505     }
506     for (jj=0; jj<6; jj++) {
507       tmp = 0;
508       for (ii=0; ii<DD-1; ii++) {
509         tmp += emat[ii + (DD-1)*jj]*vbuf[ii];
510       }
511       ten[jj+1] = tmp;
512     }
513   } else {
514     /* !knownB0 */
515     mean = 0;
516     for (ii=0; ii<DD; ii++) {
517       tmp = AIR_MAX(dwi[ii], 1);
518       mean += tmp;
519       vbuf[ii] = -log(tmp)/b;
520     }
521     mean /= DD;
522     if (soft) {
523       ten[0] = AIR_AFFINE(-1, airErf((mean - thresh)/(soft + 0.000001)), 1,
524                           0, 1);
525     } else {
526       ten[0] = mean > thresh;
527     }
528     for (jj=0; jj<7; jj++) {
529       tmp = 0;
530       for (ii=0; ii<DD; ii++) {
531         tmp += emat[ii + DD*jj]*vbuf[ii];
532       }
533       if (jj < 6) {
534         ten[jj+1] = tmp;
535       } else {
536         /* we're on seventh row, for finding B0 */
537         if (B0P) {
538           *B0P = exp(b*tmp);
539         }
540       }
541     }
542   }
543   return;
544 }
545 
546 void
tenEstimateLinearSingle_f(float * _ten,float * _B0P,const float * _dwi,const double * emat,double * vbuf,unsigned int DD,int knownB0,float thresh,float soft,float b)547 tenEstimateLinearSingle_f(float *_ten, float *_B0P,              /* output */
548                           const float *_dwi, const double *emat, /* input .. */
549                           double *vbuf, unsigned int DD, int knownB0,
550                           float thresh, float soft, float b) {
551   static const char me[]="tenEstimateLinearSingle_f";
552 #define DWI_NUM_MAX 256
553   double dwi[DWI_NUM_MAX], ten[7], B0;
554   unsigned int dwiIdx;
555 
556   /* HEY: this is somewhat inelegant .. */
557   if (DD > DWI_NUM_MAX) {
558     fprintf(stderr, "%s: PANIC: sorry, DD=%u > compile-time DWI_NUM_MAX=%u\n",
559             me, DD, DWI_NUM_MAX);
560     exit(1);
561   }
562   for (dwiIdx=0; dwiIdx<DD; dwiIdx++) {
563     dwi[dwiIdx] = _dwi[dwiIdx];
564   }
565   tenEstimateLinearSingle_d(ten, _B0P ? &B0 : NULL,
566                             dwi, emat,
567                             vbuf, DD, knownB0,
568                             thresh, soft, b);
569   TEN_T_COPY_TT(_ten, float, ten);
570   if (_B0P) {
571     *_B0P = AIR_CAST(float, B0);
572   }
573   return;
574 }
575 
576 /*
577 ******** tenEstimateLinear3D
578 **
579 ** takes an array of DWIs (starting with the B=0 image), joins them up,
580 ** and passes it all off to tenEstimateLinear4D
581 **
582 ** Note: this will copy per-axis peripheral information from _ndwi[0]
583 */
584 int
tenEstimateLinear3D(Nrrd * nten,Nrrd ** nterrP,Nrrd ** nB0P,const Nrrd * const * _ndwi,unsigned int dwiLen,const Nrrd * _nbmat,int knownB0,double thresh,double soft,double b)585 tenEstimateLinear3D(Nrrd *nten, Nrrd **nterrP, Nrrd **nB0P,
586                     const Nrrd *const *_ndwi, unsigned int dwiLen,
587                     const Nrrd *_nbmat, int knownB0,
588                     double thresh, double soft, double b) {
589   static const char me[]="tenEstimateLinear3D";
590   Nrrd *ndwi;
591   airArray *mop;
592   int amap[4] = {-1, 0, 1, 2};
593 
594   if (!(_ndwi)) {
595     biffAddf(TEN, "%s: got NULL pointer", me);
596     return 1;
597   }
598   mop = airMopNew();
599   ndwi = nrrdNew();
600   airMopAdd(mop, ndwi, (airMopper)nrrdNuke, airMopAlways);
601   if (nrrdJoin(ndwi, (const Nrrd*const*)_ndwi, dwiLen, 0, AIR_TRUE)) {
602     biffMovef(TEN, NRRD, "%s: trouble joining inputs", me);
603     airMopError(mop); return 1;
604   }
605 
606   nrrdAxisInfoCopy(ndwi, _ndwi[0], amap, NRRD_AXIS_INFO_NONE);
607   if (tenEstimateLinear4D(nten, nterrP, nB0P,
608                           ndwi, _nbmat, knownB0, thresh, soft, b)) {
609     biffAddf(TEN, "%s: trouble", me);
610     airMopError(mop); return 1;
611   }
612 
613   airMopOkay(mop);
614   return 0;
615 }
616 
617 /*
618 ******** tenEstimateLinear4D
619 **
620 ** given a stack of DWI volumes (ndwi) and the imaging B-matrix used
621 ** for acquisiton (_nbmat), computes and stores diffusion tensors in
622 ** nten.
623 **
624 ** The mean of the diffusion-weighted images is thresholded at "thresh" with
625 ** softness parameter "soft".
626 **
627 ** This takes the B-matrix (weighting matrix), such as formed by tenBMatrix,
628 ** or from a more complete account of the gradients present in an imaging
629 ** sequence, and then does the pseudo inverse to get the estimation matrix
630 */
631 int
tenEstimateLinear4D(Nrrd * nten,Nrrd ** nterrP,Nrrd ** nB0P,const Nrrd * ndwi,const Nrrd * _nbmat,int knownB0,double thresh,double soft,double b)632 tenEstimateLinear4D(Nrrd *nten, Nrrd **nterrP, Nrrd **nB0P,
633                     const Nrrd *ndwi, const Nrrd *_nbmat, int knownB0,
634                     double thresh, double soft, double b) {
635   static const char me[]="tenEstimateLinear4D";
636   Nrrd *nemat, *nbmat, *ncrop, *nhist;
637   airArray *mop;
638   size_t cmin[4], cmax[4], sx, sy, sz, II, d, DD;
639   int E, amap[4];
640   float *ten, *dwi1, *dwi2, *terr,
641     _B0, *B0, (*lup)(const void *, size_t);
642   double *emat, *bmat, *vbuf;
643   NrrdRange *range;
644   float te, d1, d2;
645   char stmp[2][AIR_STRLEN_SMALL];
646 
647   if (!(nten && ndwi && _nbmat)) {
648     /* nerrP and _NB0P can be NULL */
649     biffAddf(TEN, "%s: got NULL pointer", me);
650     return 1;
651   }
652   if (!( 4 == ndwi->dim && 7 <= ndwi->axis[0].size )) {
653     biffAddf(TEN, "%s: dwi should be 4-D array with axis 0 size >= 7", me);
654     return 1;
655   }
656   if (tenBMatrixCheck(_nbmat, nrrdTypeDefault, 6)) {
657     biffAddf(TEN, "%s: problem with B matrix", me);
658     return 1;
659   }
660   if (knownB0) {
661     if (!( ndwi->axis[0].size == 1 + _nbmat->axis[1].size )) {
662       biffAddf(TEN, "%s: (knownB0 == true) # input images (%s) "
663                "!= 1 + # B matrix rows (1+%s)", me,
664                airSprintSize_t(stmp[0], ndwi->axis[0].size),
665                airSprintSize_t(stmp[1], _nbmat->axis[1].size));
666       return 1;
667     }
668   } else {
669     if (!( ndwi->axis[0].size == _nbmat->axis[1].size )) {
670       biffAddf(TEN, "%s: (knownB0 == false) # dwi (%s) "
671                "!= # B matrix rows (%s)", me,
672                airSprintSize_t(stmp[0], ndwi->axis[0].size),
673                airSprintSize_t(stmp[1], _nbmat->axis[1].size));
674       return 1;
675     }
676   }
677 
678   DD = ndwi->axis[0].size;
679   sx = ndwi->axis[1].size;
680   sy = ndwi->axis[2].size;
681   sz = ndwi->axis[3].size;
682   mop = airMopNew();
683   airMopAdd(mop, nbmat=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
684   if (nrrdConvert(nbmat, _nbmat, nrrdTypeDouble)) {
685     biffMovef(TEN, NRRD, "%s: trouble converting to doubles", me);
686     airMopError(mop); return 1;
687   }
688   airMopAdd(mop, nemat=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
689   if (tenEMatrixCalc(nemat, nbmat, knownB0)) {
690     biffAddf(TEN, "%s: trouble computing estimation matrix", me);
691     airMopError(mop); return 1;
692   }
693   vbuf = AIR_CALLOC(knownB0 ? DD-1 : DD, double);
694   dwi1 = AIR_CALLOC(DD, float);
695   dwi2 = AIR_CALLOC(knownB0 ? DD : DD+1, float);
696   airMopAdd(mop, vbuf, airFree, airMopAlways);
697   airMopAdd(mop, dwi1, airFree, airMopAlways);
698   airMopAdd(mop, dwi2, airFree, airMopAlways);
699   if (!(vbuf && dwi1 && dwi2)) {
700     biffAddf(TEN, "%s: couldn't allocate temp buffers", me);
701     airMopError(mop); return 1;
702   }
703   if (!AIR_EXISTS(thresh)) {
704     airMopAdd(mop, ncrop=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
705     airMopAdd(mop, nhist=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
706     ELL_4V_SET(cmin, knownB0 ? 1 : 0, 0, 0, 0);
707     ELL_4V_SET(cmax, DD-1, sx-1, sy-1, sz-1);
708     E = 0;
709     if (!E) E |= nrrdCrop(ncrop, ndwi, cmin, cmax);
710     if (!E) range = nrrdRangeNewSet(ncrop, nrrdBlind8BitRangeState);
711     if (!E) airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways);
712     if (!E) E |= nrrdHisto(nhist, ncrop, range, NULL,
713                            (int)AIR_MIN(1024, range->max - range->min + 1),
714                            nrrdTypeFloat);
715     if (E) {
716       biffMovef(TEN, NRRD,
717                 "%s: trouble histograming to find DW threshold", me);
718       airMopError(mop); return 1;
719     }
720     if (_tenFindValley(&thresh, nhist, 0.75, AIR_FALSE)) {
721       biffAddf(TEN, "%s: problem finding DW histogram valley", me);
722       airMopError(mop); return 1;
723     }
724     fprintf(stderr, "%s: using %g for DW confidence threshold\n", me, thresh);
725   }
726   if (nrrdMaybeAlloc_va(nten, nrrdTypeFloat, 4,
727                         AIR_CAST(size_t, 7), sx, sy, sz)) {
728     biffMovef(TEN, NRRD, "%s: couldn't allocate output", me);
729     airMopError(mop); return 1;
730   }
731   if (nterrP) {
732     if (!(*nterrP)) {
733       *nterrP = nrrdNew();
734     }
735     if (nrrdMaybeAlloc_va(*nterrP, nrrdTypeFloat, 3,
736                           sx, sy, sz)) {
737       biffAddf(NRRD, "%s: couldn't allocate error output", me);
738       airMopError(mop); return 1;
739     }
740     airMopAdd(mop, nterrP, (airMopper)airSetNull, airMopOnError);
741     airMopAdd(mop, *nterrP, (airMopper)nrrdNuke, airMopOnError);
742     terr = (float*)((*nterrP)->data);
743   } else {
744     terr = NULL;
745   }
746   if (nB0P) {
747     if (!(*nB0P)) {
748       *nB0P = nrrdNew();
749     }
750     if (nrrdMaybeAlloc_va(*nB0P, nrrdTypeFloat, 3,
751                           sx, sy, sz)) {
752       biffAddf(NRRD, "%s: couldn't allocate error output", me);
753       airMopError(mop); return 1;
754     }
755     airMopAdd(mop, nB0P, (airMopper)airSetNull, airMopOnError);
756     airMopAdd(mop, *nB0P, (airMopper)nrrdNuke, airMopOnError);
757     B0 = (float*)((*nB0P)->data);
758   } else {
759     B0 = NULL;
760   }
761   bmat = (double*)(nbmat->data);
762   emat = (double*)(nemat->data);
763   ten = (float*)(nten->data);
764   lup = nrrdFLookup[ndwi->type];
765   for (II=0; II<sx*sy*sz; II++) {
766     /* tenVerbose = (II == 42 + 190*(96 + 196*0)); */
767     for (d=0; d<DD; d++) {
768       dwi1[d] = lup(ndwi->data, d + DD*II);
769       /* if (tenVerbose) {
770         fprintf(stderr, "%s: input dwi1[%d] = %g\n", me, d, dwi1[d]);
771       } */
772     }
773     tenEstimateLinearSingle_f(ten, &_B0, dwi1, emat,
774                               vbuf, DD, knownB0,
775                               AIR_CAST(float, thresh),
776                               AIR_CAST(float, soft),
777                               AIR_CAST(float, b));
778     if (nB0P) {
779       *B0 = _B0;
780     }
781     /* if (tenVerbose) {
782       fprintf(stderr, "%s: output ten = (%g) %g,%g,%g  %g,%g  %g\n", me,
783               ten[0], ten[1], ten[2], ten[3], ten[4], ten[5], ten[6]);
784     } */
785     if (nterrP) {
786       te = 0;
787       if (knownB0) {
788         tenSimulateSingle_f(dwi2, _B0, ten, bmat, DD, AIR_CAST(float, b));
789         for (d=1; d<DD; d++) {
790           d1 = AIR_MAX(dwi1[d], 1);
791           d2 = AIR_MAX(dwi2[d], 1);
792           te += (d1 - d2)*(d1 - d2);
793         }
794         te /= (DD-1);
795       } else {
796         tenSimulateSingle_f(dwi2, _B0, ten, bmat, DD+1, AIR_CAST(float, b));
797         for (d=0; d<DD; d++) {
798           d1 = AIR_MAX(dwi1[d], 1);
799           /* tenSimulateSingle_f always puts the B0 in the beginning of
800              the dwi vector, but in this case we didn't have it in
801              the input dwi vector */
802           d2 = AIR_MAX(dwi2[d+1], 1);
803           te += (d1 - d2)*(d1 - d2);
804         }
805         te /= DD;
806       }
807       *terr = AIR_CAST(float, sqrt(te));
808       terr += 1;
809     }
810     ten += 7;
811     if (B0) {
812       B0 += 1;
813     }
814   }
815   /* not our job: tenEigenvalueClamp(nten, nten, 0, AIR_NAN); */
816 
817   ELL_4V_SET(amap, -1, 1, 2, 3);
818   nrrdAxisInfoCopy(nten, ndwi, amap, NRRD_AXIS_INFO_NONE);
819   nten->axis[0].kind = nrrdKind3DMaskedSymMatrix;
820   if (nrrdBasicInfoCopy(nten, ndwi,
821                         NRRD_BASIC_INFO_ALL ^ NRRD_BASIC_INFO_SPACE)) {
822     biffAddf(NRRD, "%s:", me);
823     return 1;
824   }
825 
826   airMopOkay(mop);
827   return 0;
828 }
829 
830 /*
831 ******** tenSimulateSingle_f
832 **
833 ** given a tensor, simulate the set of diffusion weighted measurements
834 ** represented by the given B matrix
835 **
836 ** NOTE: the mindset of this function is very much "knownB0==true":
837 ** B0 is required as an argument (and its always copied to dwi[0]),
838 ** and the given bmat is assumed to have DD-1 rows (similar to how
839 ** tenEstimateLinearSingle_f() is set up), and dwi[1] through dwi[DD-1]
840 ** are set to the calculated DWIs.
841 **
842 ** So: dwi must be allocated for DD values total
843 */
844 void
tenSimulateSingle_f(float * dwi,float B0,const float * ten,const double * bmat,unsigned int DD,float b)845 tenSimulateSingle_f(float *dwi,
846                     float B0, const float *ten, const double *bmat,
847                     unsigned int DD, float b) {
848   double vv;
849   /* this is how we multiply the off-diagonal entries by 2 */
850   double matwght[6] = {1, 2, 2, 1, 2, 1};
851   unsigned int ii, jj;
852 
853   dwi[0] = B0;
854   /* if (tenVerbose) {
855     fprintf(stderr, "ten = %g,%g,%g  %g,%g  %g\n",
856             ten[1], ten[2], ten[3], ten[4], ten[5], ten[6]);
857   } */
858   for (ii=0; ii<DD-1; ii++) {
859     vv = 0;
860     for (jj=0; jj<6; jj++) {
861       vv += matwght[jj]*bmat[jj + 6*ii]*ten[jj+1];
862     }
863     dwi[ii+1] = AIR_CAST(float, AIR_MAX(B0, 1)*exp(-b*vv));
864     /* if (tenVerbose) {
865       fprintf(stderr, "v[%d] = %g --> dwi = %g\n", ii, vv, dwi[ii+1]);
866     } */
867   }
868 
869   return;
870 }
871 
872 int
tenSimulate(Nrrd * ndwi,const Nrrd * nT2,const Nrrd * nten,const Nrrd * _nbmat,double b)873 tenSimulate(Nrrd *ndwi, const Nrrd *nT2, const Nrrd *nten,
874             const Nrrd *_nbmat, double b) {
875   static const char me[]="tenSimulate";
876   size_t II;
877   Nrrd *nbmat;
878   size_t DD, sx, sy, sz;
879   airArray *mop;
880   double *bmat;
881   float *dwi, *ten, (*lup)(const void *, size_t I);
882   char stmp[6][AIR_STRLEN_SMALL];
883 
884   if (!ndwi || !nT2 || !nten || !_nbmat
885       || tenTensorCheck(nten, nrrdTypeFloat, AIR_TRUE, AIR_TRUE)
886       || tenBMatrixCheck(_nbmat, nrrdTypeDefault, 6)) {
887     biffAddf(TEN, "%s: got NULL pointer or invalid args", me);
888     return 1;
889   }
890   mop = airMopNew();
891   airMopAdd(mop, nbmat=nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
892   if (nrrdConvert(nbmat, _nbmat, nrrdTypeDouble)) {
893     biffMovef(TEN, NRRD, "%s: couldn't convert B matrix", me);
894     return 1;
895   }
896 
897   DD = nbmat->axis[1].size+1;
898   sx = nT2->axis[0].size;
899   sy = nT2->axis[1].size;
900   sz = nT2->axis[2].size;
901   if (!(3 == nT2->dim
902         && sx == nten->axis[1].size
903         && sy == nten->axis[2].size
904         && sz == nten->axis[3].size)) {
905     biffAddf(TEN, "%s: dimensions of %u-D T2 volume (%s,%s,%s) "
906              "don't match tensor volume (%s,%s,%s)", me, nT2->dim,
907              airSprintSize_t(stmp[0], sx),
908              airSprintSize_t(stmp[1], sy),
909              airSprintSize_t(stmp[2], sz),
910              airSprintSize_t(stmp[3], nten->axis[1].size),
911              airSprintSize_t(stmp[4], nten->axis[2].size),
912              airSprintSize_t(stmp[5], nten->axis[3].size));
913     return 1;
914   }
915   if (nrrdMaybeAlloc_va(ndwi, nrrdTypeFloat, 4,
916                         AIR_CAST(size_t, DD), sx, sy, sz)) {
917     biffMovef(TEN, NRRD, "%s: couldn't allocate output", me);
918     return 1;
919   }
920   dwi = (float*)(ndwi->data);
921   ten = (float*)(nten->data);
922   bmat = (double*)(nbmat->data);
923   lup = nrrdFLookup[nT2->type];
924   for (II=0; II<(size_t)(sx*sy*sz); II++) {
925     /* tenVerbose = (II == 42 + 190*(96 + 196*0)); */
926     tenSimulateSingle_f(dwi, lup(nT2->data, II), ten, bmat, DD,
927                         AIR_CAST(float, b));
928     dwi += DD;
929     ten += 7;
930   }
931 
932   airMopOkay(mop);
933   return 0;
934 }
935 
936 
937 
938 
939 
940 
941 
942 
943 
944 
945 
946 
947 
948 
949 
950 
951 
952 
953 /* old stuff, prior to tenEstimationMatrix .. */
954 
955 
956 /*
957 ******** tenCalcOneTensor1
958 **
959 ** make one diffusion tensor from the measurements at one voxel, based
960 ** on the gradient directions used by Andy Alexander
961 */
962 void
tenCalcOneTensor1(float tens[7],float chan[7],float thresh,float slope,float b)963 tenCalcOneTensor1(float tens[7], float chan[7],
964                   float thresh, float slope, float b) {
965   double c[7], sum, d1, d2, d3, d4, d5, d6;
966 
967   c[0] = AIR_MAX(chan[0], 1);
968   c[1] = AIR_MAX(chan[1], 1);
969   c[2] = AIR_MAX(chan[2], 1);
970   c[3] = AIR_MAX(chan[3], 1);
971   c[4] = AIR_MAX(chan[4], 1);
972   c[5] = AIR_MAX(chan[5], 1);
973   c[6] = AIR_MAX(chan[6], 1);
974   sum = c[1] + c[2] + c[3] + c[4] + c[5] + c[6];
975   tens[0] = AIR_CAST(float, (1 + airErf(slope*(sum - thresh)))/2.0);
976   d1 = (log(c[0]) - log(c[1]))/b;
977   d2 = (log(c[0]) - log(c[2]))/b;
978   d3 = (log(c[0]) - log(c[3]))/b;
979   d4 = (log(c[0]) - log(c[4]))/b;
980   d5 = (log(c[0]) - log(c[5]))/b;
981   d6 = (log(c[0]) - log(c[6]))/b;
982   tens[1] = AIR_CAST(float,  d1 + d2 - d3 - d4 + d5 + d6);    /* Dxx */
983   tens[2] = AIR_CAST(float,  d5 - d6);                        /* Dxy */
984   tens[3] = AIR_CAST(float,  d1 - d2);                        /* Dxz */
985   tens[4] = AIR_CAST(float, -d1 - d2 + d3 + d4 + d5 + d6);    /* Dyy */
986   tens[5] = AIR_CAST(float,  d3 - d4);                        /* Dyz */
987   tens[6] = AIR_CAST(float,  d1 + d2 + d3 + d4 - d5 - d6);    /* Dzz */
988   return;
989 }
990 
991 /*
992 ******** tenCalcOneTensor2
993 **
994 ** using gradient directions used by EK
995 */
996 void
tenCalcOneTensor2(float tens[7],float chan[7],float thresh,float slope,float b)997 tenCalcOneTensor2(float tens[7], float chan[7],
998                   float thresh, float slope, float b) {
999   double c[7], sum, d1, d2, d3, d4, d5, d6;
1000 
1001   c[0] = AIR_MAX(chan[0], 1);
1002   c[1] = AIR_MAX(chan[1], 1);
1003   c[2] = AIR_MAX(chan[2], 1);
1004   c[3] = AIR_MAX(chan[3], 1);
1005   c[4] = AIR_MAX(chan[4], 1);
1006   c[5] = AIR_MAX(chan[5], 1);
1007   c[6] = AIR_MAX(chan[6], 1);
1008   sum = c[1] + c[2] + c[3] + c[4] + c[5] + c[6];
1009   tens[0] = AIR_CAST(float, (1 + airErf(slope*(sum - thresh)))/2.0);
1010   d1 = (log(c[0]) - log(c[1]))/b;
1011   d2 = (log(c[0]) - log(c[2]))/b;
1012   d3 = (log(c[0]) - log(c[3]))/b;
1013   d4 = (log(c[0]) - log(c[4]))/b;
1014   d5 = (log(c[0]) - log(c[5]))/b;
1015   d6 = (log(c[0]) - log(c[6]))/b;
1016   tens[1] =  AIR_CAST(float, d1);                 /* Dxx */
1017   tens[2] =  AIR_CAST(float, d6 - (d1 + d2)/2);   /* Dxy */
1018   tens[3] =  AIR_CAST(float, d5 - (d1 + d3)/2);   /* Dxz */
1019   tens[4] =  AIR_CAST(float, d2);                 /* Dyy */
1020   tens[5] =  AIR_CAST(float, d4 - (d2 + d3)/2);   /* Dyz */
1021   tens[6] =  AIR_CAST(float, d3);                 /* Dzz */
1022   return;
1023 }
1024 
1025 /*
1026 ******** tenCalcTensor
1027 **
1028 ** Calculate a volume of tensors from measured data
1029 */
1030 int
tenCalcTensor(Nrrd * nout,Nrrd * nin,int version,float thresh,float slope,float b)1031 tenCalcTensor(Nrrd *nout, Nrrd *nin, int version,
1032               float thresh, float slope, float b) {
1033   static const char me[] = "tenCalcTensor";
1034   char cmt[128];
1035   float *out, tens[7], chan[7];
1036   size_t I, sx, sy, sz;
1037   void (*calcten)(float tens[7], float chan[7],
1038                   float thresh, float slope, float b);
1039 
1040   if (!(nout && nin)) {
1041     biffAddf(TEN, "%s: got NULL pointer", me);
1042     return 1;
1043   }
1044   if (!( 1 == version || 2 == version )) {
1045     biffAddf(TEN, "%s: version should be 1 or 2, not %d", me, version);
1046     return 1;
1047   }
1048   switch (version) {
1049   case 1:
1050     calcten = tenCalcOneTensor1;
1051     break;
1052   case 2:
1053     calcten = tenCalcOneTensor2;
1054     break;
1055   default:
1056     biffAddf(TEN, "%s: PANIC, version = %d not handled", me, version);
1057     return 1;
1058     break;
1059   }
1060   if (tenTensorCheck(nin, nrrdTypeDefault, AIR_TRUE, AIR_TRUE)) {
1061     biffAddf(TEN, "%s: wasn't given valid tensor nrrd", me);
1062     return 1;
1063   }
1064   sx = nin->axis[1].size;
1065   sy = nin->axis[2].size;
1066   sz = nin->axis[3].size;
1067   if (nrrdMaybeAlloc_va(nout, nrrdTypeFloat, 4,
1068                         AIR_CAST(size_t, 7), sx, sy, sz)) {
1069     biffMovef(TEN, NRRD, "%s: couldn't alloc output", me);
1070     return 1;
1071   }
1072   nout->axis[0].label = airStrdup("c,Dxx,Dxy,Dxz,Dyy,Dyz,Dzz");
1073   nout->axis[1].label = airStrdup("x");
1074   nout->axis[2].label = airStrdup("y");
1075   nout->axis[3].label = airStrdup("z");
1076   nout->axis[0].spacing = AIR_NAN;
1077   if (AIR_EXISTS(nin->axis[1].spacing) &&
1078       AIR_EXISTS(nin->axis[2].spacing) &&
1079       AIR_EXISTS(nin->axis[3].spacing)) {
1080     nout->axis[1].spacing = nin->axis[1].spacing;
1081     nout->axis[2].spacing = nin->axis[2].spacing;
1082     nout->axis[3].spacing = nin->axis[3].spacing;
1083   }
1084   else {
1085     nout->axis[1].spacing = 1.0;
1086     nout->axis[2].spacing = 1.0;
1087     nout->axis[3].spacing = 1.0;
1088   }
1089   sprintf(cmt, "%s: using thresh = %g, slope = %g, b = %g\n",
1090           me, thresh, slope, b);
1091   nrrdCommentAdd(nout, cmt);
1092   out = (float *)nout->data;
1093   for (I=0; I<(size_t)(sx*sy*sz); I++) {
1094     if (tenVerbose && !(I % (sx*sy))) {
1095       fprintf(stderr, "%s: z = %d of %d\n", me, (int)(I/(sx*sy)), (int)sz-1);
1096     }
1097     chan[0] = nrrdFLookup[nin->type](nin->data, 0 + 7*I);
1098     chan[1] = nrrdFLookup[nin->type](nin->data, 1 + 7*I);
1099     chan[2] = nrrdFLookup[nin->type](nin->data, 2 + 7*I);
1100     chan[3] = nrrdFLookup[nin->type](nin->data, 3 + 7*I);
1101     chan[4] = nrrdFLookup[nin->type](nin->data, 4 + 7*I);
1102     chan[5] = nrrdFLookup[nin->type](nin->data, 5 + 7*I);
1103     chan[6] = nrrdFLookup[nin->type](nin->data, 6 + 7*I);
1104     calcten(tens, chan, thresh, slope, b);
1105     out[0 + 7*I] = tens[0];
1106     out[1 + 7*I] = tens[1];
1107     out[2 + 7*I] = tens[2];
1108     out[3 + 7*I] = tens[3];
1109     out[4 + 7*I] = tens[4];
1110     out[5 + 7*I] = tens[5];
1111     out[6 + 7*I] = tens[6];
1112   }
1113   return 0;
1114 }
1115 
1116