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 "gage.h"
25 #include "privateGage.h"
26 
27 /*
28 ** does not use biff
29 */
30 gageStackBlurParm *
gageStackBlurParmNew()31 gageStackBlurParmNew() {
32   gageStackBlurParm *parm;
33 
34   parm = AIR_CALLOC(1, gageStackBlurParm);
35   if (parm) {
36     parm->scale = NULL;
37     parm->sigmaMax = gageDefStackBlurSigmaMax;
38     parm->padValue = AIR_NAN;
39     parm->kspec = NULL;
40     parm->dataCheck = AIR_TRUE;
41     parm->boundary = nrrdBoundaryUnknown;
42     parm->renormalize = AIR_FALSE;
43     parm->verbose = 0;
44   }
45   return parm;
46 }
47 
48 gageStackBlurParm *
gageStackBlurParmNix(gageStackBlurParm * sbp)49 gageStackBlurParmNix(gageStackBlurParm *sbp) {
50 
51   if (sbp) {
52     airFree(sbp->scale);
53     nrrdKernelSpecNix(sbp->kspec);
54     free(sbp);
55   }
56   return NULL;
57 }
58 
59 int
gageStackBlurParmScaleSet(gageStackBlurParm * sbp,unsigned int num,double scaleMin,double scaleMax,int uniform,int optim)60 gageStackBlurParmScaleSet(gageStackBlurParm *sbp, unsigned int num,
61                           double scaleMin, double scaleMax,
62                           int uniform, int optim) {
63   static const char me[]="gageStackBlurParmScaleSet";
64   unsigned int ii;
65 
66   if (!( sbp )) {
67     biffAddf(GAGE, "%s: got NULL pointer", me);
68     return 1;
69   }
70   airFree(sbp->scale);
71   sbp->scale = NULL;
72   if (!( scaleMin < scaleMax )) {
73     biffAddf(GAGE, "%s: scaleMin %g not < scaleMax %g", me,
74              scaleMin, scaleMax);
75     return 1;
76   }
77   sbp->scale = AIR_CALLOC(num, double);
78   if (!sbp->scale) {
79     biffAddf(GAGE, "%s: couldn't alloc scale for %u", me, num);
80     return 1;
81   }
82   sbp->num = num;
83 
84   if (uniform) {
85     for (ii=0; ii<num; ii++) {
86       sbp->scale[ii] = AIR_AFFINE(0, ii, num-1, scaleMin, scaleMax);
87     }
88   } else if (!optim) {
89     double tau0, tau1, tau;
90     tau0 = gageTauOfSig(scaleMin);
91     tau1 = gageTauOfSig(scaleMax);
92     for (ii=0; ii<num; ii++) {
93       tau = AIR_AFFINE(0, ii, num-1, tau0, tau1);
94       sbp->scale[ii] = gageSigOfTau(tau);
95     }
96   } else {
97     unsigned int sigmax;
98     sigmax = AIR_CAST(unsigned int, scaleMax);
99     if (!( 0 == scaleMin && sigmax == scaleMax )) {
100       biffAddf(GAGE, "%s: range [%g,%g] not [0,N] w/ integral N", me,
101                scaleMin, scaleMax);
102       return 1;
103     }
104     if (gageOptimSigSet(sbp->scale, num, sigmax)) {
105       biffAddf(GAGE, "%s: trouble w/ optimal sigmas", me);
106       return 1;
107     }
108   }
109 
110   return 0;
111 }
112 
113 int
gageStackBlurParmKernelSet(gageStackBlurParm * sbp,const NrrdKernelSpec * kspec,int renormalize)114 gageStackBlurParmKernelSet(gageStackBlurParm *sbp,
115                            const NrrdKernelSpec *kspec,
116                            int renormalize) {
117   static const char me[]="gageStackBlurParmKernelSet";
118 
119   if (!( sbp && kspec )) {
120     biffAddf(GAGE, "%s: got NULL pointer", me);
121     return 1;
122   }
123   nrrdKernelSpecNix(sbp->kspec);
124   sbp->kspec = nrrdKernelSpecCopy(kspec);
125   sbp->renormalize = renormalize;
126   return 0;
127 }
128 
129 int
gageStackBlurParmBoundarySet(gageStackBlurParm * sbp,int boundary,double padValue)130 gageStackBlurParmBoundarySet(gageStackBlurParm *sbp,
131                              int boundary, double padValue) {
132   static const char me[]="gageStackBlurParmBoundarySet";
133 
134   if (!sbp) {
135     biffAddf(GAGE, "%s: got NULL pointer", me);
136     return 1;
137   }
138   if (airEnumValCheck(nrrdBoundary, boundary)) {
139     biffAddf(GAGE, "%s: %d not a known %s", me,
140              boundary, nrrdBoundary->name);
141     return 1;
142   }
143   if (nrrdBoundaryPad == boundary && !AIR_EXISTS(padValue)) {
144     biffAddf(GAGE, "%s: want boundary %s but padValue %g doesn't exist", me,
145              airEnumStr(nrrdBoundary, boundary), padValue);
146     return 1;
147   }
148   sbp->boundary = boundary;
149   sbp->padValue = padValue;
150   return 0;
151 }
152 
153 int
gageStackBlurParmVerboseSet(gageStackBlurParm * sbp,int verbose)154 gageStackBlurParmVerboseSet(gageStackBlurParm *sbp, int verbose) {
155   static const char me[]="gageStackBlurParmVerboseSet";
156 
157   if (!sbp) {
158     biffAddf(GAGE, "%s: got NULL pointer", me);
159     return 1;
160   }
161   sbp->verbose = verbose;
162   return 0;
163 }
164 
165 int
gageStackBlurParmCheck(gageStackBlurParm * sbp)166 gageStackBlurParmCheck(gageStackBlurParm *sbp) {
167   static const char me[]="gageStackBlurParmCheck";
168   unsigned int ii;
169 
170   if (!sbp) {
171     biffAddf(GAGE, "%s: got NULL pointer", me);
172     return 1;
173   }
174   if (!( sbp->scale && sbp->kspec )) {
175     biffAddf(GAGE, "%s: scale and kernel aren't set", me);
176     return 1;
177   }
178   if (!( sbp->num >= 2)) {
179     biffAddf(GAGE, "%s: need num >= 2, not %u", me, sbp->num);
180     return 1;
181   }
182   for (ii=0; ii<sbp->num; ii++) {
183     if (!AIR_EXISTS(sbp->scale[ii])) {
184       biffAddf(GAGE, "%s: scale[%u] = %g doesn't exist", me, ii,
185                sbp->scale[ii]);
186       return 1;
187     }
188     if (ii) {
189       if (!( sbp->scale[ii-1] < sbp->scale[ii] )) {
190         biffAddf(GAGE, "%s: scale[%u] = %g not < scale[%u] = %g", me,
191                  ii, sbp->scale[ii-1], ii+1, sbp->scale[ii]);
192         return 1;
193       }
194     }
195   }
196   if (airEnumValCheck(nrrdBoundary, sbp->boundary)) {
197     biffAddf(GAGE, "%s: %d not a known %s", me,
198              sbp->boundary, nrrdBoundary->name);
199     return 1;
200   }
201   return 0;
202 }
203 
204 static int
_checkNrrd(Nrrd * const nblur[],const Nrrd * const ncheck[],unsigned int blNum,int checking,const Nrrd * nin,const gageKind * kind)205 _checkNrrd(Nrrd *const nblur[], const Nrrd *const ncheck[],
206            unsigned int blNum, int checking,
207            const Nrrd *nin, const gageKind *kind) {
208   static const char me[]="_checkNrrd";
209   unsigned int blIdx;
210 
211   for (blIdx=0; blIdx<blNum; blIdx++) {
212     if (checking) {
213       if (nrrdCheck(ncheck[blIdx])) {
214         biffMovef(GAGE, NRRD, "%s: bad ncheck[%u]", me, blIdx);
215         return 1;
216       }
217     } else {
218       if (!nblur[blIdx]) {
219         biffAddf(GAGE, "%s: NULL blur[%u]", me, blIdx);
220         return 1;
221       }
222     }
223   }
224   if (3 + kind->baseDim != nin->dim) {
225     biffAddf(GAGE, "%s: need nin->dim %u (not %u) with baseDim %u", me,
226              3 + kind->baseDim, nin->dim, kind->baseDim);
227     return 1;
228   }
229   return 0;
230 }
231 
232 #define KVP_NUM 5
233 
234 static const char                      /*    0             1         2          3            4     */
235 _blurKey[KVP_NUM][AIR_STRLEN_LARGE] = {"gageStackBlur", "scale", "kernel", "boundary", "renormalize"};
236 
237 typedef struct {
238   char val[KVP_NUM][AIR_STRLEN_LARGE];
239 } blurVal_t;
240 
241 static blurVal_t *
_blurValAlloc(airArray * mop,gageStackBlurParm * sbp)242 _blurValAlloc(airArray *mop, gageStackBlurParm *sbp) {
243   static const char me[]="_blurValAlloc";
244   blurVal_t *blurVal;
245   unsigned int blIdx;
246 
247   blurVal = AIR_CAST(blurVal_t *, calloc(sbp->num, sizeof(blurVal_t)));
248   if (!blurVal) {
249     biffAddf(GAGE, "%s: couldn't alloc blurVal for %u", me, sbp->num);
250     return NULL;
251   }
252   for (blIdx=0; blIdx<sbp->num; blIdx++) {
253     sbp->kspec->parm[0] = sbp->scale[blIdx];
254     sprintf(blurVal[blIdx].val[0], "true");
255     sprintf(blurVal[blIdx].val[1], "%g", sbp->scale[blIdx]);
256     nrrdKernelSpecSprint(blurVal[blIdx].val[2], sbp->kspec);
257     sprintf(blurVal[blIdx].val[3], "%s", sbp->renormalize ? "true" : "false");
258     sprintf(blurVal[blIdx].val[4], "%s",
259             airEnumStr(nrrdBoundary, sbp->boundary));
260   }
261   airMopAdd(mop, blurVal, airFree, airMopAlways);
262   return blurVal;
263 }
264 
265 /*
266 ** little helper function to do pre-blurring of a given nrrd
267 ** of the sort that might be useful for scale-space gage use
268 **
269 ** nblur has to already be allocated for "blNum" Nrrd*s, AND, they all
270 ** have to point to valid (possibly empty) Nrrds, so they can hold the
271 ** results of blurring
272 */
273 int
gageStackBlur(Nrrd * const nblur[],gageStackBlurParm * sbp,const Nrrd * nin,const gageKind * kind)274 gageStackBlur(Nrrd *const nblur[], gageStackBlurParm *sbp,
275               const Nrrd *nin, const gageKind *kind) {
276   static const char me[]="gageStackBlur";
277   unsigned int blIdx, axi;
278   NrrdResampleContext *rsmc;
279   blurVal_t *blurVal;
280   airArray *mop;
281   int E;
282   Nrrd *ntmp;
283 
284   if (!(nblur && sbp && nin && kind)) {
285     biffAddf(GAGE, "%s: got NULL pointer", me);
286     return 1;
287   }
288   mop = airMopNew();
289   if (gageStackBlurParmCheck(sbp)
290       || _checkNrrd(nblur, NULL, sbp->num, AIR_FALSE, nin, kind)
291       || (!( blurVal = _blurValAlloc(mop, sbp) )) ) {
292     biffAddf(GAGE, "%s: problem", me);
293     airMopError(mop); return 1;
294   }
295   rsmc = nrrdResampleContextNew();
296   airMopAdd(mop, rsmc, (airMopper)nrrdResampleContextNix, airMopAlways);
297   /* may or may not be needed for iterative diffusion */
298   ntmp = nrrdNew();
299   airMopAdd(mop, ntmp, (airMopper)nrrdNuke, airMopAlways);
300 
301   E = 0;
302   if (!E) E |= nrrdResampleDefaultCenterSet(rsmc, nrrdDefaultCenter);
303   if (!E) E |= nrrdResampleInputSet(rsmc, nin);
304   if (kind->baseDim) {
305     unsigned int bai;
306     for (bai=0; bai<kind->baseDim; bai++) {
307       if (!E) E |= nrrdResampleKernelSet(rsmc, bai, NULL, NULL);
308     }
309   }
310   for (axi=0; axi<3; axi++) {
311     if (!E) E |= nrrdResampleSamplesSet(rsmc, kind->baseDim + axi,
312                                         nin->axis[kind->baseDim + axi].size);
313     if (!E) E |= nrrdResampleRangeFullSet(rsmc, kind->baseDim + axi);
314   }
315   if (!E) E |= nrrdResampleBoundarySet(rsmc, sbp->boundary);
316   if (!E) E |= nrrdResampleTypeOutSet(rsmc, nrrdTypeDefault);
317   if (!E) E |= nrrdResampleRenormalizeSet(rsmc, sbp->renormalize);
318   if (E) {
319     biffAddf(GAGE, "%s: trouble setting up resampling", me);
320     airMopError(mop); return 1;
321   }
322 
323   for (blIdx=0; blIdx<sbp->num; blIdx++) {
324     unsigned int kvpIdx;
325     if (sbp->verbose) {
326       fprintf(stderr, "%s: blurring %u / %u (scale %g) ... ", me, blIdx,
327               sbp->num, sbp->scale[blIdx]);
328       fflush(stderr);
329     }
330     if (nrrdKernelDiscreteGaussian == sbp->kspec->kernel
331         && sbp->scale[blIdx] > sbp->sigmaMax) {
332       double timeLeft, /* amount of diffusion time left to do */
333         timeStep,      /* length of diffusion time per blur */
334         timeDo;        /* amount of diffusion time just applied */
335       unsigned int passIdx = 0;
336       timeLeft = sbp->scale[blIdx]*sbp->scale[blIdx];
337       timeStep = (sbp->sigmaMax)*(sbp->sigmaMax);
338       if (sbp->verbose) {
339         fprintf(stderr, "\n");
340         fprintf(stderr, "%s: scale %g > sigmaMax %g\n", me,
341                 sbp->scale[blIdx], sbp->sigmaMax);
342         fprintf(stderr, "%s: diffusing for time %g in steps of %g\n", me,
343                 timeLeft, timeStep);
344         fflush(stderr);
345       }
346       do {
347         if (!passIdx) {
348           if (!E) E |= nrrdResampleInputSet(rsmc, nin);
349         } else {
350           if (!E) E |= nrrdResampleInputSet(rsmc, ntmp);
351         }
352         timeDo = (timeLeft > timeStep
353                   ? timeStep
354                   : timeLeft);
355         sbp->kspec->parm[0] = sqrt(timeDo);
356         for (axi=0; axi<3; axi++) {
357           if (!E) E |= nrrdResampleKernelSet(rsmc, kind->baseDim + axi,
358                                              sbp->kspec->kernel,
359                                              sbp->kspec->parm);
360         }
361         if (sbp->verbose) {
362           fprintf(stderr, "  pass %u (timeLeft=%g => time=%g, sigma=%g) ...\n",
363                   passIdx, timeLeft, timeDo, sbp->kspec->parm[0]);
364         }
365         if (!E) E |= nrrdResampleExecute(rsmc, ntmp);
366         timeLeft -= timeDo;
367         passIdx++;
368       } while (!E && timeLeft > 0.0);
369       if (!E) E |= nrrdCopy(nblur[blIdx], ntmp);
370     } else {
371       /* do blurring in one shot as usual */
372       sbp->kspec->parm[0] = sbp->scale[blIdx];
373       for (axi=0; axi<3; axi++) {
374         if (!E) E |= nrrdResampleKernelSet(rsmc, kind->baseDim + axi,
375                                            sbp->kspec->kernel,
376                                            sbp->kspec->parm);
377       }
378       if (!E) E |= nrrdResampleExecute(rsmc, nblur[blIdx]);
379     }
380     if (E) {
381       if (sbp->verbose) {
382         fprintf(stderr, "problem!\n");
383       }
384       biffAddf(GAGE, "%s: trouble w/ %u of %u (scale %g)",
385                me, blIdx, sbp->num, sbp->scale[blIdx]);
386       airMopError(mop); return 1;
387     }
388     if (sbp->verbose) {
389       fprintf(stderr, "done.\n");
390     }
391     /* add the KVPs to document how these were blurred */
392     for (kvpIdx=0; kvpIdx<KVP_NUM; kvpIdx++) {
393       if (!E) E |= nrrdKeyValueAdd(nblur[blIdx], _blurKey[kvpIdx],
394                                    blurVal[blIdx].val[kvpIdx]);
395     }
396   }
397 
398   airMopOkay(mop);
399   return 0;
400 }
401 
402 /*
403 ******** gageStackBlurCheck
404 **
405 ** (docs)
406 **
407 ** on why sbp->dataCheck should be non-zero: really need to check that
408 ** the data values themselves are correct; its too dangerous to have
409 ** this unchecked, because it means that experimental changes in
410 ** volumes could mysteriously have no effect, because the cached
411 ** pre-blurred volumes from the old data are being re-used
412 */
413 int
gageStackBlurCheck(const Nrrd * const nblur[],gageStackBlurParm * sbp,const Nrrd * nin,const gageKind * kind)414 gageStackBlurCheck(const Nrrd *const nblur[],
415                    gageStackBlurParm *sbp,
416                    const Nrrd *nin, const gageKind *kind) {
417   static const char me[]="gageStackBlurCheck";
418   gageShape *shapeOld, *shapeNew;
419   blurVal_t *blurVal;
420   airArray *mop;
421   unsigned int blIdx, kvpIdx;
422 
423   if (!(nblur && sbp && nin && kind)) {
424     biffAddf(GAGE, "%s: got NULL pointer", me);
425     return 1;
426   }
427   mop = airMopNew();
428   if (gageStackBlurParmCheck(sbp)
429       || _checkNrrd(NULL, nblur, sbp->num, AIR_TRUE, nin, kind)
430       || (!( blurVal = _blurValAlloc(mop, sbp) )) ) {
431     biffAddf(GAGE, "%s: problem", me);
432     airMopError(mop); return 1;
433   }
434 
435   shapeNew = gageShapeNew();
436   airMopAdd(mop, shapeNew, (airMopper)gageShapeNix, airMopAlways);
437   if (gageShapeSet(shapeNew, nin, kind->baseDim)) {
438     biffAddf(GAGE, "%s: trouble setting up reference shape", me);
439     airMopError(mop); return 1;
440   }
441   shapeOld = gageShapeNew();
442   airMopAdd(mop, shapeOld, (airMopper)gageShapeNix, airMopAlways);
443 
444   for (blIdx=0; blIdx<sbp->num; blIdx++) {
445     if (nin->type != nblur[blIdx]->type) {
446       biffAddf(GAGE, "%s: nblur[%u]->type %s != nin type %s\n", me,
447                blIdx, airEnumStr(nrrdType, nblur[blIdx]->type),
448                airEnumStr(nrrdType, nin->type));
449       airMopError(mop); return 1;
450     }
451     /* check to see if nblur[blIdx] is as expected */
452     if (gageShapeSet(shapeOld, nblur[blIdx], kind->baseDim)
453         || !gageShapeEqual(shapeOld, "nblur", shapeNew, "nin")) {
454       biffAddf(GAGE, "%s: trouble, or nblur[%u] shape != nin shape",
455                me, blIdx);
456       airMopError(mop); return 1;
457     }
458     /* see if the KVPs are already there */
459     for (kvpIdx=0; kvpIdx<KVP_NUM; kvpIdx++) {
460       char *tmpval;
461       tmpval = nrrdKeyValueGet(nblur[blIdx], _blurKey[kvpIdx]);
462       if (!tmpval) {
463         biffAddf(GAGE, "%s: didn't see key \"%s\" in nblur[%u]", me,
464                  _blurKey[kvpIdx], blIdx);
465         airMopError(mop); return 1;
466       }
467       airMopAdd(mop, tmpval, airFree, airMopAlways);
468       if (strcmp(tmpval, blurVal[blIdx].val[kvpIdx])) {
469         biffAddf(GAGE, "%s: found key[%s] \"%s\" != wanted \"%s\"", me,
470                  _blurKey[kvpIdx], tmpval, blurVal[blIdx].val[kvpIdx]);
471         airMopError(mop); return 1;
472       }
473     }
474   }
475   if (sbp->dataCheck) {
476     double (*lup)(const void *, size_t), vin, vbl;
477     size_t II, NN;
478     if (!(0.0 == sbp->scale[0])) {
479       biffAddf(GAGE, "%s: sorry, dataCheck w/ scale[0] %g "
480                "!= 0.0 not implemented", me, sbp->scale[0]);
481       airMopError(mop); return 1;
482       /* so the non-zero return here will be acted upon as though there
483          was a difference between the desired and the current stack,
484          so things will be recomputed, which is conservative but costly */
485     }
486     lup = nrrdDLookup[nin->type];
487     NN = nrrdElementNumber(nin);
488     for (II=0; II<NN; II++) {
489       vin = lup(nin->data, II);
490       vbl = lup(nblur[0]->data, II);
491       if (vin != vbl) {
492         biffAddf(GAGE, "%s: value[%u] in nin %g != in nblur[0] %g\n", me,
493                  AIR_CAST(unsigned int, II), vin, vbl);
494         airMopError(mop); return 1;
495       }
496     }
497   }
498 
499   airMopOkay(mop);
500   return 0;
501 }
502 
503 int
gageStackBlurGet(Nrrd * const nblur[],int * recomputedP,gageStackBlurParm * sbp,const char * format,const Nrrd * nin,const gageKind * kind)504 gageStackBlurGet(Nrrd *const nblur[], int *recomputedP,
505                  gageStackBlurParm *sbp,
506                  const char *format,
507                  const Nrrd *nin, const gageKind *kind) {
508   static const char me[]="gageStackBlurGet";
509   airArray *mop;
510   int recompute;
511   unsigned int ii;
512 
513   if (!( nblur && sbp && nin && kind )) {
514     biffAddf(GAGE, "%s: got NULL pointer", me);
515     return 1;
516   }
517   for (ii=0; ii<sbp->num; ii++) {
518     if (!nblur[ii]) {
519       biffAddf(GAGE, "%s: nblur[%u] NULL", me, ii);
520       return 1;
521     }
522   }
523   if (gageStackBlurParmCheck(sbp)) {
524     biffAddf(GAGE, "%s: trouble with blur parms", me);
525     return 1;
526   }
527   mop = airMopNew();
528 
529   /* set recompute flag */
530   if (!airStrlen(format)) {
531     /* no info about files to load, obviously have to recompute */
532     if (sbp->verbose) {
533       fprintf(stderr, "%s: no file info, must recompute blurrings\n", me);
534     }
535     recompute = AIR_TRUE;
536   } else {
537     char *fname, *suberr;
538     int firstExists;
539     FILE *file;
540     /* do have info about files to load, but may fail in many ways */
541     fname = AIR_CALLOC(strlen(format) + AIR_STRLEN_SMALL, char);
542     if (!fname) {
543       biffAddf(GAGE, "%s: couldn't allocate fname", me);
544       airMopError(mop); return 1;
545     }
546     airMopAdd(mop, fname, airFree, airMopAlways);
547     /* see if we can get the first file (number 0) */
548     sprintf(fname, format, 0);
549     firstExists = !!(file = fopen(fname, "r"));
550     airFclose(file);
551     if (!firstExists) {
552       if (sbp->verbose) {
553         fprintf(stderr, "%s: no file \"%s\"; will recompute blurrings\n",
554                 me, fname);
555       }
556       recompute = AIR_TRUE;
557     } else if (nrrdLoadMulti(nblur, sbp->num, format, 0, NULL)) {
558       airMopAdd(mop, suberr = biffGetDone(NRRD), airFree, airMopAlways);
559       if (sbp->verbose) {
560         fprintf(stderr, "%s: will recompute blurrings that couldn't be "
561                 "read:\n%s\n", me, suberr);
562       }
563       recompute = AIR_TRUE;
564     } else if (gageStackBlurCheck(AIR_CAST(const Nrrd*const*, nblur),
565                                   sbp, nin, kind)) {
566       airMopAdd(mop, suberr = biffGetDone(GAGE), airFree, airMopAlways);
567       if (sbp->verbose) {
568         fprintf(stderr, "%s: will recompute blurrings (from \"%s\") "
569                 "that don't match:\n%s\n", me, format, suberr);
570       }
571       recompute = AIR_TRUE;
572     } else {
573       /* else precomputed blurrings could all be read, and did match */
574       if (sbp->verbose) {
575         fprintf(stderr, "%s: will reuse %u %s pre-blurrings.\n", me,
576                 sbp->num, format);
577       }
578       recompute = AIR_FALSE;
579     }
580   }
581   if (recompute) {
582     if (gageStackBlur(nblur, sbp, nin, kind)) {
583       biffAddf(GAGE, "%s: trouble computing blurrings", me);
584       airMopError(mop); return 1;
585     }
586   }
587   if (recomputedP) {
588     *recomputedP = recompute;
589   }
590 
591   airMopOkay(mop);
592   return 0;
593 }
594 
595 /*
596 ******** gageStackBlurManage
597 **
598 ** does the work of gageStackBlurGet and then some:
599 ** allocates the array of Nrrds, allocates an array of doubles for scale,
600 ** and saves output if recomputed
601 */
602 int
gageStackBlurManage(Nrrd *** nblurP,int * recomputedP,gageStackBlurParm * sbp,const char * format,int saveIfComputed,NrrdEncoding * enc,const Nrrd * nin,const gageKind * kind)603 gageStackBlurManage(Nrrd ***nblurP, int *recomputedP,
604                     gageStackBlurParm *sbp,
605                     const char *format,
606                     int saveIfComputed, NrrdEncoding *enc,
607                     const Nrrd *nin, const gageKind *kind) {
608   static const char me[]="gageStackBlurManage";
609   Nrrd **nblur;
610   unsigned int ii;
611   airArray *mop;
612   int recomputed;
613 
614   if (!( nblurP && sbp && nin && kind )) {
615     biffAddf(GAGE, "%s: got NULL pointer", me);
616     return 1;
617   }
618   nblur = *nblurP = AIR_CALLOC(sbp->num, Nrrd *);
619   if (!nblur) {
620     biffAddf(GAGE, "%s: couldn't alloc %u Nrrd*s", me, sbp->num);
621     return 1;
622   }
623 
624   mop = airMopNew();
625   airMopAdd(mop, nblurP, (airMopper)airSetNull, airMopOnError);
626   airMopAdd(mop, *nblurP, airFree, airMopOnError);
627   for (ii=0; ii<sbp->num; ii++) {
628     nblur[ii] = nrrdNew();
629     airMopAdd(mop, nblur[ii], (airMopper)nrrdNuke, airMopOnError);
630   }
631   if (gageStackBlurGet(nblur, &recomputed, sbp, format, nin, kind)) {
632     biffAddf(GAGE, "%s: trouble getting nblur", me);
633     airMopError(mop); return 1;
634   }
635   if (recomputedP) {
636     *recomputedP = recomputed;
637   }
638   if (recomputed && format && saveIfComputed) {
639     NrrdIoState *nio;
640     int E;
641     E = 0;
642     if (enc) {
643       if (!enc->available()) {
644         biffAddf(GAGE, "%s: requested %s encoding which is not "
645                  "available in this build", me, enc->name);
646         airMopError(mop); return 1;
647       }
648       nio = nrrdIoStateNew();
649       airMopAdd(mop, nio, (airMopper)nrrdIoStateNix, airMopAlways);
650       if (!E) E |= nrrdIoStateEncodingSet(nio, nrrdEncodingGzip);
651     } else {
652       nio = NULL;
653     }
654     if (!E) E |= nrrdSaveMulti(format, AIR_CAST(const Nrrd *const *,
655                                                 nblur),
656                                sbp->num, 0, nio);
657     if (E) {
658       biffMovef(GAGE, NRRD, "%s: trouble saving blurrings", me);
659       airMopError(mop); return 1;
660     }
661   }
662 
663   airMopOkay(mop);
664   return 0;
665 }
666