GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/gage/stackBlur.c Lines: 288 853 33.8 %
Date: 2017-05-26 Branches: 194 628 30.9 %

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
#include "gage.h"
25
#include "privateGage.h"
26
27
const char *
28
_gageSigmaSamplingStr[] = {
29
  "(unknown_sampling)",
30
  "unisig", /* "uniform-sigma", */
31
  "unitau", /* "uniform-tau", */
32
  "optil2" /* "optimal-3d-l2l2" */
33
};
34
35
const char *
36
_gageSigmaSamplingDesc[] = {
37
  "unknown sampling",
38
  "uniform samples along sigma",
39
  "uniform samples along Lindeberg's tau",
40
  "optimal sampling (3D L2 image error and L2 error across scales)"
41
};
42
43
const char *
44
_gageSigmaSamplingStrEqv[] = {
45
  "uniform-sigma", "unisigma", "unisig",
46
  "uniform-tau", "unitau",
47
  "optimal-3d-l2l2", "optimal-l2l2", "optil2",
48
  ""
49
};
50
51
const int
52
_gageSigmaSamplingValEqv[] = {
53
  gageSigmaSamplingUniformSigma, gageSigmaSamplingUniformSigma,
54
  /* */ gageSigmaSamplingUniformSigma,
55
  gageSigmaSamplingUniformTau, gageSigmaSamplingUniformTau,
56
  gageSigmaSamplingOptimal3DL2L2, gageSigmaSamplingOptimal3DL2L2,
57
  /* */ gageSigmaSamplingOptimal3DL2L2
58
};
59
60
const airEnum
61
_gageSigmaSampling_enum = {
62
  "sigma sampling strategy",
63
  GAGE_SIGMA_SAMPLING_MAX,
64
  _gageSigmaSamplingStr, NULL,
65
  _gageSigmaSamplingDesc,
66
  _gageSigmaSamplingStrEqv, _gageSigmaSamplingValEqv,
67
  AIR_FALSE
68
};
69
const airEnum *const
70
gageSigmaSampling = &_gageSigmaSampling_enum;
71
72
73
void
74
gageStackBlurParmInit(gageStackBlurParm *parm) {
75
76
26
  if (parm) {
77
13
    parm->num = 0;
78
13
    parm->sigmaRange[0] = AIR_NAN;
79
13
    parm->sigmaRange[1] = AIR_NAN;
80
13
    parm->sigmaSampling = gageSigmaSamplingUnknown;
81
13
    parm->sigma = airFree(parm->sigma);
82
13
    parm->kspec = nrrdKernelSpecNix(parm->kspec);
83
    /* this will be effectively moot when nrrdKernelDiscreteGaussian is used
84
       with a bit cut-off, and will only help with smaller cut-offs and with
85
       any other kernel, and will be moot for FFT-based blurring */
86
13
    parm->renormalize = AIR_TRUE;
87
13
    parm->bspec = nrrdBoundarySpecNix(parm->bspec);
88
13
    parm->oneDim = AIR_FALSE;
89
    /* the cautious application of the FFT--based blurring justifies enables
90
       it by default */
91
13
    parm->needSpatialBlur = AIR_FALSE;
92
13
    parm->verbose = 1; /* HEY: this may be revisited */
93
13
    parm->dgGoodSigmaMax = nrrdKernelDiscreteGaussianGoodSigmaMax;
94
13
  }
95
13
  return;
96
}
97
98
/*
99
** does not use biff
100
*/
101
gageStackBlurParm *
102
gageStackBlurParmNew() {
103
  gageStackBlurParm *parm;
104
105
6
  parm = AIR_CALLOC(1, gageStackBlurParm);
106
3
  gageStackBlurParmInit(parm);
107
3
  return parm;
108
}
109
110
gageStackBlurParm *
111
gageStackBlurParmNix(gageStackBlurParm *sbp) {
112
113
6
  if (sbp) {
114
3
    airFree(sbp->sigma);
115
3
    nrrdKernelSpecNix(sbp->kspec);
116
3
    nrrdBoundarySpecNix(sbp->bspec);
117
3
    free(sbp);
118
3
  }
119
3
  return NULL;
120
}
121
122
/*
123
** *differ is set to 0 or 1; not useful for sorting
124
*/
125
int
126
gageStackBlurParmCompare(const gageStackBlurParm *aa, const char *_nameA,
127
                         const gageStackBlurParm *bb, const char *_nameB,
128
                         int *differ, char explain[AIR_STRLEN_LARGE]) {
129
  static const char me[]="gageStackBlurParmCompare",
130
    baseA[]="A", baseB[]="B";
131
  const char *nameA, *nameB;
132
  unsigned int si, warnLen = AIR_STRLEN_LARGE/4;
133
10
  char stmp[2][AIR_STRLEN_LARGE], subexplain[AIR_STRLEN_LARGE];
134
135
5
  if (!(aa && bb && differ)) {
136
    biffAddf(GAGE, "%s: got NULL pointer (%p %p %p)", me,
137
             AIR_VOIDP(aa), AIR_VOIDP(bb), AIR_VOIDP(differ));
138
    return 1;
139
  }
140
5
  nameA = _nameA ? _nameA : baseA;
141
5
  nameB = _nameB ? _nameB : baseB;
142
5
  if (strlen(nameA) + strlen(nameB) > warnLen) {
143
    biffAddf(GAGE, "%s: names (len %s, %s) might lead to overflow", me,
144
             airSprintSize_t(stmp[0], strlen(nameA)),
145
             airSprintSize_t(stmp[1], strlen(nameB)));
146
    return 1;
147
  }
148
  /*
149
  ** HEY: really ambivalent about not doing this check:
150
  ** its unusual in Teem to not take an opportunity to do this kind
151
  ** of sanity check when its available, but we don't really know the
152
  ** circumstances of when this will be called, and if that includes
153
  ** some interaction with hest, there may not yet have been the chance
154
  ** to complete the sbp.
155
  if (gageStackBlurParmCheck(aa)) {
156
    biffAddf(GAGE, "%s: problem with sbp %s", me, nameA);
157
    return 1;
158
  }
159
  if (gageStackBlurParmCheck(bb)) {
160
    biffAddf(GAGE, "%s: problem with sbp %s", me, nameB);
161
    return 1;
162
  }
163
  */
164
#define CHECK(VAR, FMT)                                                 \
165
  if (aa->VAR != bb->VAR) {                                             \
166
    if (explain) {                                                      \
167
      sprintf(explain, "%s->" #VAR "=" #FMT " != %s->" #VAR "=" #FMT,   \
168
              nameA, aa->VAR, nameB,  bb->VAR);                         \
169
    }                                                                   \
170
    *differ = 1;                                                        \
171
    return 0;                                                           \
172
  }
173

5
  CHECK(num, %u);
174

5
  CHECK(sigmaRange[0], %.17g);
175

5
  CHECK(sigmaRange[1], %.17g);
176

5
  CHECK(renormalize, %d);
177

5
  CHECK(oneDim, %d);
178

5
  CHECK(needSpatialBlur, %d);
179
  /* This is sketchy: the apparent point of the function is to see if two
180
     sbp's are different.  But a big role of the function is to enable
181
     leeching in meet.  And for leeching, a difference in verbose is moot */
182
  /* CHECK(verbose, %d); */
183

5
  CHECK(dgGoodSigmaMax, %.17g);
184
#undef CHECK
185
5
  if (aa->sigmaSampling != bb->sigmaSampling) {
186
    if (explain) {
187
      sprintf(explain, "%s->sigmaSampling=%s != %s->sigmaSampling=%s",
188
              nameA, airEnumStr(gageSigmaSampling, aa->sigmaSampling),
189
              nameB, airEnumStr(gageSigmaSampling, bb->sigmaSampling));
190
    }
191
    *differ = 1; return 0;
192
  }
193
50
  for (si=0; si<aa->num; si++) {
194
20
    if (aa->sigma[si] != bb->sigma[si]) {
195
      if (explain) {
196
        sprintf(explain, "%s->sigma[%u]=%.17g != %s->sigma[%u]=%.17g",
197
                nameA, si, aa->sigma[si], nameB, si, bb->sigma[si]);
198
      }
199
      *differ = 1; return 0;
200
    }
201
  }
202

10
  if (nrrdKernelSpecCompare(aa->kspec, bb->kspec,
203
5
                            differ, subexplain)) {
204
    biffMovef(GAGE, NRRD, "%s: trouble comparing kernel specs", me);
205
    return 1;
206
  }
207
5
  if (*differ) {
208
    if (explain) {
209
      sprintf(explain, "kernel specs different: %s", subexplain);
210
    }
211
    *differ = 1; return 0;
212
  }
213
5
  if (nrrdBoundarySpecCompare(aa->bspec, bb->bspec,
214
                              differ, subexplain)) {
215
    biffMovef(GAGE, NRRD, "%s: trouble comparing boundary specs", me);
216
    return 1;
217
  }
218
5
  if (*differ) {
219
    if (explain) {
220
      sprintf(explain, "boundary specs different: %s", subexplain);
221
    }
222
    *differ = 1; return 0;
223
  }
224
  /* no differences so far */
225
5
  *differ = 0;
226
5
  return 0;
227
5
}
228
229
int
230
gageStackBlurParmCopy(gageStackBlurParm *dst,
231
                      const gageStackBlurParm *src) {
232
  static const char me[]="gageStackBlurParmCopy";
233
  int differ;
234
  char explain[AIR_STRLEN_LARGE];
235
236
  if (!(dst && src)) {
237
    biffAddf(GAGE, "%s: got NULL pointer", me);
238
    return 1;
239
  }
240
  if (gageStackBlurParmCheck(src)) {
241
    biffAddf(GAGE, "%s: given src parm has problems", me);
242
    return 1;
243
  }
244
  if (gageStackBlurParmSigmaSet(dst, src->num,
245
                                src->sigmaRange[0], src->sigmaRange[1],
246
                                src->sigmaSampling)
247
      || gageStackBlurParmKernelSet(dst, src->kspec)
248
      || gageStackBlurParmRenormalizeSet(dst, src->renormalize)
249
      || gageStackBlurParmDgGoodSigmaMaxSet(dst, src->dgGoodSigmaMax)
250
      || gageStackBlurParmBoundarySpecSet(dst, src->bspec)
251
      || gageStackBlurParmNeedSpatialBlurSet(dst, src->needSpatialBlur)
252
      || gageStackBlurParmVerboseSet(dst, src->verbose)
253
      || gageStackBlurParmOneDimSet(dst, src->oneDim)) {
254
    biffAddf(GAGE, "%s: problem setting dst parm", me);
255
    return 1;
256
  }
257
  if (gageStackBlurParmCompare(dst, "copy", src, "original",
258
                               &differ, explain)) {
259
    biffAddf(GAGE, "%s: trouble assessing correctness of copy", me);
260
    return 1;
261
  }
262
  if (differ) {
263
    biffAddf(GAGE, "%s: problem: copy not equal: %s", me, explain);
264
    return 1;
265
  }
266
  return 0;
267
}
268
269
int
270
gageStackBlurParmSigmaSet(gageStackBlurParm *sbp, unsigned int num,
271
                          double sigmaMin, double sigmaMax,
272
                          int sigmaSampling) {
273
  static const char me[]="gageStackBlurParmSigmaSet";
274
  unsigned int ii;
275
276
22
  if (!( sbp )) {
277
    biffAddf(GAGE, "%s: got NULL pointer", me);
278
    return 1;
279
  }
280
11
  airFree(sbp->sigma);
281
11
  sbp->sigma = NULL;
282
11
  if (!( 0 <= sigmaMin )) {
283
    biffAddf(GAGE, "%s: need sigmaMin >= 0 (not %g)", me, sigmaMin);
284
    return 1;
285
  }
286
11
  if (!( sigmaMin < sigmaMax )) {
287
    biffAddf(GAGE, "%s: need sigmaMax %g > sigmaMin %g",
288
             me, sigmaMax, sigmaMin);
289
    return 1;
290
  }
291
11
  if (airEnumValCheck(gageSigmaSampling, sigmaSampling)) {
292
    biffAddf(GAGE, "%s: %d is not a valid %s", me,
293
             sigmaSampling, gageSigmaSampling->name);
294
    return 1;
295
  }
296
11
  if (!( num >= 2 )) {
297
    biffAddf(GAGE, "%s: need # scale samples >= 2 (not %u)", me, num);
298
    return 1;
299
  }
300
11
  sbp->sigma = AIR_CALLOC(num, double);
301
11
  if (!sbp->sigma) {
302
    biffAddf(GAGE, "%s: couldn't alloc scale for %u", me, num);
303
    return 1;
304
  }
305
11
  sbp->num = num;
306
11
  sbp->sigmaRange[0] = sigmaMin;
307
11
  sbp->sigmaRange[1] = sigmaMax;
308
11
  sbp->sigmaSampling = sigmaSampling;
309
310

11
  switch (sigmaSampling) {
311
    double tau0, tau1, tau;
312
    unsigned int sigmax;
313
  case gageSigmaSamplingUniformSigma:
314
60
    for (ii=0; ii<num; ii++) {
315
24
      sbp->sigma[ii] = AIR_AFFINE(0, ii, num-1, sigmaMin, sigmaMax);
316
    }
317
    break;
318
  case gageSigmaSamplingUniformTau:
319
3
    tau0 = gageTauOfSig(sigmaMin);
320
3
    tau1 = gageTauOfSig(sigmaMax);
321
32
    for (ii=0; ii<num; ii++) {
322
13
      tau = AIR_AFFINE(0, ii, num-1, tau0, tau1);
323
13
      sbp->sigma[ii] = gageSigOfTau(tau);
324
    }
325
    break;
326
  case gageSigmaSamplingOptimal3DL2L2:
327
2
    sigmax = AIR_CAST(unsigned int, sigmaMax);
328
2
    if (0 != sigmaMin) {
329
      biffAddf(GAGE, "%s: sigmaMin %g != 0", me, sigmaMin);
330
      return 1;
331
    }
332
2
    if (sigmax != sigmaMax) {
333
      biffAddf(GAGE, "%s: sigmaMax %g not an integer", me, sigmaMax);
334
      return 1;
335
    }
336
2
    if (gageOptimSigSet(sbp->sigma, num, sigmax)) {
337
      biffAddf(GAGE, "%s: trouble setting optimal sigmas", me);
338
      return 1;
339
    }
340
    break;
341
  default:
342
    biffAddf(GAGE, "%s: sorry, sigmaSampling %s (%d) not implemented", me,
343
             airEnumStr(gageSigmaSampling, sigmaSampling), sigmaSampling);
344
    return 1;
345
  }
346
11
  if (sbp->verbose > 1) {
347
    fprintf(stderr, "%s: %u samples in [%g,%g] via %s:\n", me,
348
            num, sigmaMin, sigmaMax,
349
            airEnumStr(gageSigmaSampling, sigmaSampling));
350
    for (ii=0; ii<num; ii++) {
351
      if (ii) {
352
        fprintf(stderr, "%s:           "
353
                "| deltas: %g\t               %g\n", me,
354
                sbp->sigma[ii] - sbp->sigma[ii-1],
355
                gageTauOfSig(sbp->sigma[ii])
356
                - gageTauOfSig(sbp->sigma[ii-1]));
357
      }
358
      fprintf(stderr, "%s: sigma[%02u]=%g%s\t         tau=%g\n", me, ii,
359
              sbp->sigma[ii], !sbp->sigma[ii] ? "     " : "",
360
              gageTauOfSig(sbp->sigma[ii]));
361
    }
362
  }
363
364
11
  return 0;
365
11
}
366
367
int
368
gageStackBlurParmScaleSet(gageStackBlurParm *sbp,
369
                          unsigned int num,
370
                          double smin, double smax,
371
                          int uniform, int optimal) {
372
  static const char me[]="gageStackBlurParmScaleSet";
373
  int sampling;
374
375
  fprintf(stderr, "\n%s: !!! This function is deprecated; use "
376
          "gageStackBlurParmSigmaSet instead !!!\n\n", me);
377
  if (uniform && optimal) {
378
    biffAddf(GAGE, "%s: can't have both uniform and optimal sigma sampling",
379
             me);
380
    return 1;
381
  }
382
  sampling = (uniform
383
              ? gageSigmaSamplingUniformSigma
384
              : (optimal
385
                 ? gageSigmaSamplingOptimal3DL2L2
386
                 : gageSigmaSamplingUniformTau));
387
  if (gageStackBlurParmSigmaSet(sbp, num, smin, smax, sampling)) {
388
    biffAddf(GAGE, "%s: trouble", me);
389
    return 1;
390
  }
391
  return 0;
392
}
393
394
int
395
gageStackBlurParmKernelSet(gageStackBlurParm *sbp,
396
                           const NrrdKernelSpec *kspec) {
397
  static const char me[]="gageStackBlurParmKernelSet";
398
399
8
  if (!( sbp && kspec )) {
400
    biffAddf(GAGE, "%s: got NULL pointer", me);
401
    return 1;
402
  }
403
4
  nrrdKernelSpecNix(sbp->kspec);
404
4
  sbp->kspec = nrrdKernelSpecCopy(kspec);
405
4
  return 0;
406
4
}
407
408
int
409
gageStackBlurParmRenormalizeSet(gageStackBlurParm *sbp,
410
                                int renormalize) {
411
  static const char me[]="gageStackBlurParmRenormalizeSet";
412
413
8
  if (!sbp) {
414
    biffAddf(GAGE, "%s: got NULL pointer", me);
415
    return 1;
416
  }
417
4
  sbp->renormalize = renormalize;
418
4
  return 0;
419
4
}
420
421
int
422
gageStackBlurParmBoundarySet(gageStackBlurParm *sbp,
423
                             int boundary, double padValue) {
424
  static const char me[]="gageStackBlurParmBoundarySet";
425
426
  if (!sbp) {
427
    biffAddf(GAGE, "%s: got NULL pointer", me);
428
    return 1;
429
  }
430
  nrrdBoundarySpecNix(sbp->bspec);
431
  sbp->bspec = nrrdBoundarySpecNew();
432
  sbp->bspec->boundary = boundary;
433
  sbp->bspec->padValue = padValue;
434
  if (nrrdBoundarySpecCheck(sbp->bspec)) {
435
    biffMovef(GAGE, NRRD, "%s: problem", me);
436
    return 1;
437
  }
438
  return 0;
439
}
440
441
int
442
gageStackBlurParmBoundarySpecSet(gageStackBlurParm *sbp,
443
                                 const NrrdBoundarySpec *bspec) {
444
  static const char me[]="gageStackBlurParmBoundarySet";
445
446
4
  if (!sbp) {
447
    biffAddf(GAGE, "%s: got NULL pointer", me);
448
    return 1;
449
  }
450
2
  nrrdBoundarySpecNix(sbp->bspec);
451
2
  sbp->bspec = nrrdBoundarySpecCopy(bspec);
452
2
  if (nrrdBoundarySpecCheck(sbp->bspec)) {
453
    biffMovef(GAGE, NRRD, "%s: problem", me);
454
    return 1;
455
  }
456
2
  return 0;
457
2
}
458
459
int
460
gageStackBlurParmOneDimSet(gageStackBlurParm *sbp, int oneDim) {
461
  static const char me[]="gageStackBlurParmOneDimSet";
462
463
8
  if (!sbp) {
464
    biffAddf(GAGE, "%s: got NULL pointer", me);
465
    return 1;
466
  }
467
4
  sbp->oneDim = oneDim;
468
4
  return 0;
469
4
}
470
471
int
472
gageStackBlurParmNeedSpatialBlurSet(gageStackBlurParm *sbp,
473
                                    int needSpatialBlur) {
474
  static const char me[]="gageStackBlurParmNeedSpatialBlurSet";
475
476
8
  if (!sbp) {
477
    biffAddf(GAGE, "%s: got NULL pointer", me);
478
    return 1;
479
  }
480
4
  sbp->needSpatialBlur = needSpatialBlur;
481
4
  return 0;
482
4
}
483
484
int
485
gageStackBlurParmVerboseSet(gageStackBlurParm *sbp, int verbose) {
486
  static const char me[]="gageStackBlurParmVerboseSet";
487
488
4
  if (!sbp) {
489
    biffAddf(GAGE, "%s: got NULL pointer", me);
490
    return 1;
491
  }
492
2
  sbp->verbose = verbose;
493
2
  return 0;
494
2
}
495
496
int
497
gageStackBlurParmDgGoodSigmaMaxSet(gageStackBlurParm *sbp,
498
                                 double dgGoodSigmaMax) {
499
  static const char me[]="gageStackBlurParmDgGoodSigmaMaxSet";
500
501
4
  if (!sbp) {
502
    biffAddf(GAGE, "%s: got NULL pointer", me);
503
    return 1;
504
  }
505
2
  if (!dgGoodSigmaMax > 0) {
506
    biffAddf(GAGE, "%s: given dgGoodSigmaMax %g not > 0", me, dgGoodSigmaMax);
507
    return 1;
508
  }
509
2
  sbp->dgGoodSigmaMax = dgGoodSigmaMax;
510
2
  return 0;
511
2
}
512
513
int
514
gageStackBlurParmCheck(const gageStackBlurParm *sbp) {
515
  static const char me[]="gageStackBlurParmCheck";
516
  unsigned int ii;
517
518
  if (!sbp) {
519
    biffAddf(GAGE, "%s: got NULL pointer", me);
520
    return 1;
521
  }
522
  if (!( sbp->num >= 2)) {
523
    biffAddf(GAGE, "%s: need num >= 2, not %u", me, sbp->num);
524
    return 1;
525
  }
526
  if (!sbp->sigma) {
527
    biffAddf(GAGE, "%s: sigma vector not allocated", me);
528
    return 1;
529
  }
530
  if (!sbp->kspec) {
531
    biffAddf(GAGE, "%s: blurring kernel not set", me);
532
    return 1;
533
  }
534
  if (!sbp->bspec) {
535
    biffAddf(GAGE, "%s: boundary specification not set", me);
536
    return 1;
537
  }
538
  for (ii=0; ii<sbp->num; ii++) {
539
    if (!AIR_EXISTS(sbp->sigma[ii])) {
540
      biffAddf(GAGE, "%s: sigma[%u] = %g doesn't exist", me, ii,
541
               sbp->sigma[ii]);
542
      return 1;
543
    }
544
    if (ii) {
545
      if (!( sbp->sigma[ii-1] < sbp->sigma[ii] )) {
546
        biffAddf(GAGE, "%s: sigma[%u] = %g not < sigma[%u] = %g", me,
547
                 ii, sbp->sigma[ii-1], ii+1, sbp->sigma[ii]);
548
        return 1;
549
      }
550
    }
551
  }
552
  /* HEY: no sanity check on kernel because there is no
553
     nrrdKernelSpecCheck(), but there should be! */
554
  if (nrrdBoundarySpecCheck(sbp->bspec)) {
555
    biffMovef(GAGE, NRRD, "%s: problem with boundary", me);
556
    return 1;
557
  }
558
  return 0;
559
}
560
561
int
562
gageStackBlurParmParse(gageStackBlurParm *sbp,
563
                       int extraFlags[256],
564
                       char **extraParmsP,
565
                       const char *_str) {
566
  static const char me[]="gageStackBlurParmParse";
567
46
  char *str, *mnmfS, *stok, *slast=NULL, *parmS, *eps;
568
23
  int flagSeen[256];
569
23
  double sigmaMin, sigmaMax, dggsm;
570
23
  unsigned int sigmaNum, parmNum;
571
23
  int haveFlags, verbose, verboseGot=AIR_FALSE, dggsmGot=AIR_FALSE,
572
    sampling = AIR_FALSE, samplingGot=AIR_FALSE, E;
573
  airArray *mop, *epsArr;
574
  NrrdKernelSpec *kspec=NULL;
575
  NrrdBoundarySpec *bspec=NULL;
576
577
23
  if (!( sbp && _str )) {
578
    biffAddf(GAGE, "%s: got NULL pointer", me);
579
    return 1;
580
  }
581
23
  if (!( str = airStrdup(_str) )) {
582
    biffAddf(GAGE, "%s: couldn't copy input", me);
583
    return 1;
584
  }
585
23
  mop = airMopNew();
586
23
  airMopAdd(mop, str, airFree, airMopAlways);
587
23
  if (extraParmsP) {
588
    /* start with empty string */
589
22
    epsArr = airArrayNew(AIR_CAST(void **, &eps), NULL, sizeof(char), 42);
590
22
    airMopAdd(mop, epsArr, (airMopper)airArrayNuke, airMopAlways);
591
22
    airArrayLenIncr(epsArr, 1);
592
22
    *eps = '\0';
593
22
  } else {
594
    epsArr = NULL;
595
  }
596
597
  /* working with assumption that '/' does not appear
598
     in mnmfS <minScl>-<#smp>-<maxScl>[-<flags>] */
599
23
  if ( (parmS = strchr(str, '/')) ) {
600
    /* there are in fact parms */
601
10
    *parmS = '\0';
602
10
    parmS++;
603
10
  } else {
604
    parmS = NULL;
605
  }
606
  mnmfS = str;
607

40
  if (!( 3 == airStrntok(mnmfS, "-") || 4 == airStrntok(mnmfS, "-") )) {
608
1
    biffAddf(GAGE, "%s: didn't get 3 or 4 \"-\"-separated tokens in \"%s\"",
609
             me, mnmfS);
610
1
    airMopError(mop); return 1;
611
  }
612
22
  haveFlags = (4 == airStrntok(mnmfS, "-"));
613
22
  stok = airStrtok(mnmfS, "-", &slast);
614
22
  if (1 != sscanf(stok, "%lg", &sigmaMin)) {
615
    biffAddf(GAGE, "%s: couldn't parse \"%s\" as max sigma", me, stok);
616
    airMopError(mop); return 1;
617
  }
618
22
  stok = airStrtok(NULL, "-", &slast);
619
22
  if (1 != sscanf(stok, "%u", &sigmaNum)) {
620
2
    biffAddf(GAGE, "%s: couldn't parse \"%s\" as # scale samples", me, stok);
621
2
    airMopError(mop); return 1;
622
  }
623
20
  stok = airStrtok(NULL, "-", &slast);
624
20
  if (1 != sscanf(stok, "%lg", &sigmaMax)) {
625
1
    biffAddf(GAGE, "%s: couldn't parse \"%s\" as max scale", me, stok);
626
1
    airMopError(mop); return 1;
627
  }
628
19
  memset(flagSeen, 0, sizeof(flagSeen));
629
19
  if (extraFlags) {
630
    /* not sizeof(extraFlags) == sizeof(int*) */
631
18
    memset(extraFlags, 0, sizeof(flagSeen));
632
18
  }
633
19
  if (haveFlags) {
634
    char *flags, *ff;
635
    /* look for various things in flags */
636
15
    flags = airToLower(airStrdup(airStrtok(NULL, "-", &slast)));
637
15
    airMopAdd(mop, flags, airFree, airMopAlways);
638
    ff = flags;
639

127
    while (*ff && '+' != *ff) {
640
      /* '1': oneDim
641
         'r': turn OFF spatial kernel renormalize
642
         'u': uniform (in sigma) sampling
643
         'o': optimized (3d l2l2) sampling
644
         'p': need spatial blur
645
      */
646
32
      if (strchr("1ruop", *ff)) {
647
28
        flagSeen[AIR_CAST(unsigned char, *ff)] = AIR_TRUE;
648
28
      } else {
649
4
        if (extraFlags) {
650
4
          extraFlags[AIR_CAST(unsigned char, *ff)] = AIR_TRUE;
651
        } else {
652
          biffAddf(GAGE, "%s: got extra flag '%c' but NULL extraFlag",
653
                   me, *ff);
654
          airMopError(mop); return 1;
655
        }
656
      }
657
32
      ff++;
658
    }
659

28
    if (flagSeen['u'] && flagSeen['o']) {
660
1
      biffAddf(GAGE, "%s: can't have both optimal ('o') and uniform ('u') "
661
               "flags set in \"%s\"", me, flags);
662
1
      airMopError(mop); return 1;
663
    }
664

28
    if (ff && '+' == *ff) {
665
1
      biffAddf(GAGE, "%s: sorry, can no longer indicate a derivative "
666
               "normalization bias via '+' in \"%s\" in flags \"%s\"; "
667
               "use \"dnbias=\" parm instead", me, ff, flags);
668
1
      airMopError(mop); return 1;
669
    }
670
13
  }
671
17
  if (parmS) {
672
    unsigned int parmIdx;
673
10
    char *pval, xeq[AIR_STRLEN_SMALL];
674
10
    parmNum = airStrntok(parmS, "/");
675
64
    for (parmIdx=0; parmIdx<parmNum; parmIdx++) {
676
27
      if (!parmIdx) {
677
10
        stok = airStrtok(parmS, "/", &slast);
678
10
      } else {
679
17
        stok = airStrtok(NULL, "/", &slast);
680
      }
681

54
      if (strcpy(xeq, "k=") && stok == strstr(stok, xeq)) {
682
10
        pval = stok + strlen(xeq);
683
10
        kspec = nrrdKernelSpecNew();
684
10
        airMopAdd(mop, kspec, (airMopper)nrrdKernelSpecNix, airMopAlways);
685
10
        if (nrrdKernelSpecParse(kspec, pval)) {
686
1
          biffMovef(GAGE, NRRD, "%s: couldn't parse \"%s\" as blurring kernel",
687
                    me, pval);
688
1
          airMopError(mop); return 1;
689
        }
690

34
      } else if (strcpy(xeq, "b=") && strstr(stok, xeq) == stok) {
691
7
        pval = stok + strlen(xeq);
692
7
        bspec = nrrdBoundarySpecNew();
693
7
        airMopAdd(mop, bspec, (airMopper)nrrdBoundarySpecNix, airMopAlways);
694
7
        if (nrrdBoundarySpecParse(bspec, pval)) {
695
2
          biffMovef(GAGE, NRRD, "%s: couldn't parse \"%s\" as boundary",
696
                    me, pval);
697
2
          airMopError(mop); return 1;
698
        }
699

20
      } else if (strcpy(xeq, "v=") && strstr(stok, xeq) == stok) {
700
5
        pval = stok + strlen(xeq);
701
5
        if (1 != sscanf(pval, "%d", &verbose)) {
702
1
          biffAddf(GAGE, "%s: couldn't parse \"%s\" as verbose int", me, pval);
703
1
          airMopError(mop); return 1;
704
        }
705
        verboseGot = AIR_TRUE;
706

14
      } else if (strcpy(xeq, "s=") && strstr(stok, xeq) == stok) {
707
2
        pval = stok + strlen(xeq);
708
2
        sampling = airEnumVal(gageSigmaSampling, pval);
709
2
        if (gageSigmaSamplingUnknown == sampling) {
710
          biffAddf(GAGE, "%s: couldn't parse \"%s\" as %s", me, pval,
711
                   gageSigmaSampling->name);
712
          airMopError(mop); return 1;
713
        }
714
        samplingGot = AIR_TRUE;
715

8
      } else if (strcpy(xeq, "dggsm=") && strstr(stok, xeq) == stok) {
716
3
        pval = stok + strlen(xeq);
717
3
        if (1 != sscanf(pval, "%lg", &dggsm)) {
718
1
          biffAddf(GAGE, "%s: couldn't parse \"%s\" as dgGoodSigmaMax double",
719
                   me, pval);
720
1
          airMopError(mop); return 1;
721
        }
722
        dggsmGot = AIR_TRUE;
723
2
      } else {
724
        /* doesn't match any of the parms we know how to parse */
725
        if (extraParmsP) {
726
          airArrayLenIncr(epsArr, AIR_CAST(int, 2 + strlen(stok)));
727
          if (strlen(eps)) {
728
            strcat(eps, "/");
729
          }
730
          strcat(eps, stok);
731
        } else {
732
          biffAddf(GAGE, "%s: got extra parm \"%s\" but NULL extraParmsP",
733
                   me, stok);
734
          airMopError(mop); return 1;
735
        }
736
      }
737
    }
738
15
  }
739
  /* have parsed everything, now error checking and making sense */
740

19
  if (flagSeen['u'] && flagSeen['o']) {
741
    biffAddf(GAGE, "%s: can't use flags 'u' and 'o' at same time", me);
742
    airMopError(mop); return 1;
743
  }
744

24
  if ((flagSeen['u'] || flagSeen['o']) && samplingGot) {
745
1
    biffAddf(GAGE, "%s: can't use both 'u','o' flags and parms to "
746
             "specify sigma sampling", me);
747
1
    airMopError(mop); return 1;
748
  }
749
11
  if (!samplingGot) {
750
    /* have to set sampling from flags */
751
11
    if (flagSeen['u']) {
752
      sampling = gageSigmaSamplingUniformSigma;
753
11
    } else if (flagSeen['o']) {
754
      sampling = gageSigmaSamplingOptimal3DL2L2;
755
2
    } else {
756
      sampling = gageSigmaSamplingUniformTau;
757
    }
758
  }
759
  /* setting sbp fields */
760
  E = 0;
761
33
  if (!E) E |= gageStackBlurParmSigmaSet(sbp, sigmaNum,
762
11
                                         sigmaMin, sigmaMax, sampling);
763

22
  if (kspec) {
764
15
    if (!E) E |= gageStackBlurParmKernelSet(sbp, kspec);
765
  }
766

22
  if (flagSeen['r']) {
767
15
    if (!E) E |= gageStackBlurParmRenormalizeSet(sbp, AIR_FALSE);
768
  }
769

22
  if (dggsmGot) {
770
13
    if (!E) E |= gageStackBlurParmDgGoodSigmaMaxSet(sbp, dggsm);
771
  }
772

22
  if (bspec) {
773
13
    if (!E) E |= gageStackBlurParmBoundarySpecSet(sbp, bspec);
774
  }
775

22
  if (flagSeen['p']) {
776
15
    if (!E) E |= gageStackBlurParmNeedSpatialBlurSet(sbp, AIR_TRUE);
777
  }
778

22
  if (verboseGot) {
779
13
    if (!E) E |= gageStackBlurParmVerboseSet(sbp, verbose);
780
  }
781

22
  if (flagSeen['1']) {
782
15
    if (!E) E |= gageStackBlurParmOneDimSet(sbp, AIR_TRUE);
783
  }
784
  /* NOT doing the final check, because if this is being called from
785
     hest, the caller won't have had time to set the default info in
786
     the sbp (like the default kernel), so it will probably look
787
     incomplete.
788
     if (!E) E |= gageStackBlurParmCheck(sbp); */
789
11
  if (E) {
790
    biffAddf(GAGE, "%s: problem with blur parm specification", me);
791
    airMopError(mop); return 1;
792
  }
793
11
  if (extraParmsP) {
794
10
    if (airStrlen(eps)) {
795
      *extraParmsP = airStrdup(eps);
796
    } else {
797
10
      *extraParmsP = NULL;
798
    }
799
  }
800
11
  airMopOkay(mop);
801
11
  return 0;
802
23
}
803
804
int
805
gageStackBlurParmSprint(char str[AIR_STRLEN_LARGE],
806
                        const gageStackBlurParm *sbp,
807
                        int extraFlag[256],
808
                        char *extraParm) {
809
  static const char me[]="gageStackBlurParmSprint";
810
12
  char *out, stmp[AIR_STRLEN_LARGE];
811
  int needFlags, hef;
812
  unsigned int fi;
813
814
6
  if (!(str && sbp)) {
815
    biffAddf(GAGE, "%s: got NULL pointer", me);
816
    return 1;
817
  }
818
819
  out = str;
820
6
  sprintf(out, "%.17g-%u-%.17g",
821
          sbp->sigmaRange[0], sbp->num, sbp->sigmaRange[1]);
822
6
  out += strlen(out);
823
  hef = AIR_FALSE;
824
6
  if (extraFlag) {
825
2570
    for (fi=0; fi<256; fi++) {
826
1280
      hef |= extraFlag[fi];
827
    }
828
  }
829
6
  needFlags = (sbp->oneDim
830
10
               || sbp->renormalize
831
4
               || sbp->needSpatialBlur
832
6
               || hef);
833
6
  if (needFlags) {
834
6
    strcat(out, "-");
835
8
    if (sbp->oneDim)          { strcat(out, "1"); }
836
10
    if (sbp->renormalize)     { strcat(out, "r"); }
837
8
    if (sbp->needSpatialBlur) { strcat(out, "p"); }
838
6
    if (hef) {
839
1028
      for (fi=0; fi<256; fi++) {
840
512
        if (extraFlag[fi]) {
841
2
          sprintf(stmp, "%c", AIR_CAST(char, fi));
842
2
          strcat(out, stmp);
843
2
        }
844
      }
845
    }
846
  }
847
848
6
  if (sbp->kspec) {
849
2
    strcat(out, "/");
850
2
    if (nrrdKernelSpecSprint(stmp, sbp->kspec)) {
851
      biffMovef(GAGE, NRRD, "%s: problem with kernel", me);
852
      return 1;
853
    }
854
2
    strcat(out, "k="); strcat(out, stmp);
855
2
  }
856
857
6
  if (sbp->bspec) {
858
1
    strcat(out, "/");
859
1
    if (nrrdBoundarySpecSprint(stmp, sbp->bspec)) {
860
      biffMovef(GAGE, NRRD, "%s: problem with boundary", me);
861
      return 1;
862
    }
863
1
    strcat(out, "b="); strcat(out, stmp);
864
1
  }
865
866
6
  if (!airEnumValCheck(gageSigmaSampling, sbp->sigmaSampling)) {
867
6
    strcat(out, "/s=");
868
6
    strcat(out, airEnumStr(gageSigmaSampling, sbp->sigmaSampling));
869
6
  }
870
871
6
  if (sbp->verbose) {
872
6
    sprintf(stmp, "/v=%d", sbp->verbose);
873
6
    strcat(out, stmp);
874
6
  }
875
876
8
  if (sbp->kspec
877
8
      && nrrdKernelDiscreteGaussian == sbp->kspec->kernel
878
4
      && nrrdKernelDiscreteGaussianGoodSigmaMax != sbp->dgGoodSigmaMax) {
879
1
    sprintf(stmp, "/dggsm=%.17g", sbp->dgGoodSigmaMax);
880
1
    strcat(out, stmp);
881
1
  }
882
883
6
  if (extraParm) {
884
    strcat(out, "/");
885
    strcat(out, extraParm);
886
  }
887
888
6
  return 0;
889
6
}
890
891
int
892
_gageHestStackBlurParmParse(void *ptr, char *str, char err[AIR_STRLEN_HUGE]) {
893
  gageStackBlurParm **sbp;
894
2
  char me[]="_gageHestStackBlurParmParse", *nerr;
895
896
1
  if (!(ptr && str)) {
897
    sprintf(err, "%s: got NULL pointer", me);
898
    return 1;
899
  }
900
1
  sbp = (gageStackBlurParm **)ptr;
901
1
  if (!strlen(str)) {
902
    /* got an empty string; we trying to emulate an "optional"
903
       command-line option, in a program that likely still has
904
       the older -ssn, -ssr, -kssb, and which may or may not
905
       need any scale-space functionality */
906
    *sbp = NULL;
907
  } else {
908
1
    *sbp = gageStackBlurParmNew();
909
    /* NOTE: no way to retrieve extraFlags or extraParms from hest */
910
1
    if (gageStackBlurParmParse(*sbp, NULL, NULL, str)) {
911
      nerr = biffGetDone(GAGE);
912
      airStrcpy(err, AIR_STRLEN_HUGE, nerr);
913
      gageStackBlurParmNix(*sbp);
914
      free(nerr);
915
      return 1;
916
    }
917
  }
918
1
  return 0;
919
1
}
920
921
hestCB
922
_gageHestStackBlurParm = {
923
  sizeof(gageStackBlurParm*),
924
  "stack blur specification",
925
  _gageHestStackBlurParmParse,
926
  (airMopper)gageStackBlurParmNix
927
};
928
929
hestCB *
930
gageHestStackBlurParm = &_gageHestStackBlurParm;
931
932
static int
933
_checkNrrd(Nrrd *const nblur[], const Nrrd *const ncheck[],
934
           unsigned int blNum, int checking,
935
           const Nrrd *nin, const gageKind *kind) {
936
  static const char me[]="_checkNrrd";
937
  unsigned int blIdx;
938
939
  for (blIdx=0; blIdx<blNum; blIdx++) {
940
    if (checking) {
941
      if (nrrdCheck(ncheck[blIdx])) {
942
        biffMovef(GAGE, NRRD, "%s: bad ncheck[%u]", me, blIdx);
943
        return 1;
944
      }
945
    } else {
946
      if (!nblur[blIdx]) {
947
        biffAddf(GAGE, "%s: NULL blur[%u]", me, blIdx);
948
        return 1;
949
      }
950
    }
951
  }
952
  if (3 + kind->baseDim != nin->dim) {
953
    biffAddf(GAGE, "%s: need nin->dim %u (not %u) with baseDim %u", me,
954
             3 + kind->baseDim, nin->dim, kind->baseDim);
955
    return 1;
956
  }
957
  return 0;
958
}
959
960
#define KVP_NUM 9
961
962
static const char
963
_blurKey[KVP_NUM][AIR_STRLEN_LARGE] = {/*  0  */ "gageStackBlur",
964
                                       /*  1 */  "cksum",
965
                                       /*  2  */ "scale",
966
                                       /*  3  */ "kernel",
967
                                       /*  4  */ "renormalize",
968
                                       /*  5  */ "boundary",
969
                                       /*  6  */ "onedim",
970
                                       /*  7  */ "spatialblurred",
971
#define KVP_SBLUR_IDX                      7
972
                                       /*  8  */ "dgGoodSigmaMax"
973
#define KVP_DGGSM_IDX                      8
974
                                       /* (9 == KVP_NUM, above) */};
975
976
typedef struct {
977
  char val[KVP_NUM][AIR_STRLEN_LARGE];
978
} blurVal_t;
979
980
static blurVal_t *
981
_blurValAlloc(airArray *mop, gageStackBlurParm *sbp, NrrdKernelSpec *kssb,
982
              const Nrrd *nin, int spatialBlurred) {
983
  static const char me[]="_blurValAlloc";
984
  blurVal_t *blurVal;
985
  unsigned int blIdx, cksum;
986
987
  blurVal = AIR_CAST(blurVal_t *, calloc(sbp->num, sizeof(blurVal_t)));
988
  if (!blurVal) {
989
    biffAddf(GAGE, "%s: couldn't alloc blurVal for %u", me, sbp->num);
990
    return NULL;
991
  }
992
  cksum = nrrdCRC32(nin, airEndianLittle);
993
994
  for (blIdx=0; blIdx<sbp->num; blIdx++) {
995
    kssb->parm[0] = sbp->sigma[blIdx];
996
    sprintf(blurVal[blIdx].val[0], "true");
997
    sprintf(blurVal[blIdx].val[1], "%u", cksum);
998
    sprintf(blurVal[blIdx].val[2], "%.17g", sbp->sigma[blIdx]);
999
    nrrdKernelSpecSprint(blurVal[blIdx].val[3], kssb);
1000
    sprintf(blurVal[blIdx].val[4], "%s", sbp->renormalize ? "true" : "false");
1001
    nrrdBoundarySpecSprint(blurVal[blIdx].val[5], sbp->bspec);
1002
    sprintf(blurVal[blIdx].val[6], "%s",
1003
            sbp->oneDim ? "true" : "false");
1004
    sprintf(blurVal[blIdx].val[7], "%s",
1005
            spatialBlurred ? "true" : "false");
1006
    sprintf(blurVal[blIdx].val[8], "%.17g", sbp->dgGoodSigmaMax);
1007
  }
1008
  airMopAdd(mop, blurVal, airFree, airMopAlways);
1009
  return blurVal;
1010
}
1011
1012
/*
1013
** some spot checks suggest that where the PSF of lindeberg-gaussian blurring
1014
** should be significantly non-zero, this is more accurate than the current
1015
** "discrete gauss" kernel, but for small values near zero, the spatial
1016
** blurring is more accurate.  This is due to how with limited numerical
1017
** precision, the FFT can produce very low amplitude noise.
1018
*/
1019
static int
1020
_stackBlurDiscreteGaussFFT(Nrrd *const nblur[], gageStackBlurParm *sbp,
1021
                           const Nrrd *nin, const gageKind *kind) {
1022
  static const char me[]="_stackBlurDiscreteGaussFFT";
1023
  size_t sizeAll[NRRD_DIM_MAX], *size, ii, xi, yi, zi, nn;
1024
  Nrrd *ninC, /* complex version of input, same as input type */
1025
    *ninFT,   /* FT of input, type double */
1026
    *noutFT,  /* FT of output, values set manually from ninFT, as double */
1027
    *noutCd,  /* complex version of output, still as double */
1028
    *noutC;   /* complex version of output, as input type;
1029
                 will convert/clamp this to get nblur[i] */
1030
  double (*lup)(const void *, size_t), (*ins)(void *, size_t, double),
1031
    *ww[3], tblur, theta, *inFT, *outFT;
1032
  unsigned int blIdx, axi, ftaxes[3] = {1,2,3};
1033
  airArray *mop;
1034
  int axmap[NRRD_DIM_MAX];
1035
1036
  mop = airMopNew();
1037
  ninC = nrrdNew();
1038
  airMopAdd(mop, ninC, (airMopper)nrrdNuke, airMopAlways);
1039
  ninFT = nrrdNew();
1040
  airMopAdd(mop, ninFT, (airMopper)nrrdNuke, airMopAlways);
1041
  noutFT = nrrdNew();
1042
  airMopAdd(mop, noutFT, (airMopper)nrrdNuke, airMopAlways);
1043
  noutCd = nrrdNew();
1044
  airMopAdd(mop, noutCd, (airMopper)nrrdNuke, airMopAlways);
1045
  noutC = nrrdNew();
1046
  airMopAdd(mop, noutC, (airMopper)nrrdNuke, airMopAlways);
1047
1048
  if (gageKindScl != kind) {
1049
    biffAddf(GAGE, "%s: sorry, non-scalar kind not yet implemented", me);
1050
    /* but it really wouldn't be that hard ... */
1051
    airMopError(mop); return 1;
1052
  }
1053
  if (3 != nin->dim) {
1054
    biffAddf(GAGE, "%s: sanity check fail: nin->dim %u != 3", me, nin->dim);
1055
    airMopError(mop); return 1;
1056
  }
1057
  lup = nrrdDLookup[nin->type];
1058
  ins = nrrdDInsert[nin->type];
1059
  /* unurrdu/fft.c handles real input by doing an axis insert and then
1060
     padding, but that is overkill; this is a more direct way, which perhaps
1061
     should be migrated to unurrdu/fft.c */
1062
  sizeAll[0] = 2;
1063
  size = sizeAll + 1;
1064
  nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, size);
1065
  if (nrrdMaybeAlloc_nva(ninC, nin->type, nin->dim+1, sizeAll)) {
1066
    biffMovef(GAGE, NRRD, "%s: couldn't allocate complex-valued input", me);
1067
    airMopError(mop); return 1;
1068
  }
1069
  for (axi=0; axi<3; axi++) {
1070
    if (!( ww[axi] = AIR_CALLOC(size[axi], double) )) {
1071
      biffAddf(GAGE, "%s: couldn't allocate axis %u buffer", me, axi);
1072
      airMopError(mop); return 1;
1073
    }
1074
    airMopAdd(mop, ww[axi], airFree, airMopAlways);
1075
  }
1076
  nn = size[0]*size[1]*size[2];
1077
  for (ii=0; ii<nn; ii++) {
1078
    ins(ninC->data, 0 + 2*ii, lup(nin->data, ii));
1079
    ins(ninC->data, 1 + 2*ii, 0.0);
1080
  }
1081
  for (axi=0; axi<4; axi++) {
1082
    if (!axi) {
1083
      axmap[axi] = -1;
1084
    } else {
1085
      axmap[axi] = axi-1;
1086
    }
1087
  }
1088
  if (nrrdAxisInfoCopy(ninC, nin, axmap, NRRD_AXIS_INFO_NONE)
1089
      || nrrdBasicInfoCopy(ninC, nin,
1090
                           (NRRD_BASIC_INFO_DATA_BIT |
1091
                            NRRD_BASIC_INFO_DIMENSION_BIT |
1092
                            NRRD_BASIC_INFO_CONTENT_BIT |
1093
                            NRRD_BASIC_INFO_COMMENTS_BIT |
1094
                            NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) {
1095
    biffMovef(GAGE, NRRD, "%s: couldn't set complex-valued axinfo", me);
1096
    airMopError(mop); return 1;
1097
  }
1098
  ninC->axis[0].kind = nrrdKindComplex; /* should use API */
1099
  /*
1100
  nrrdSave("ninC.nrrd", ninC, NULL);
1101
  */
1102
  /* the copy to noutFT is just to allocate it; the values
1103
     there will be re-written over and over in the loop below */
1104
  if (nrrdFFT(ninFT, ninC, ftaxes, 3,
1105
              +1 /* forward */,
1106
              AIR_TRUE /* rescale */,
1107
              nrrdFFTWPlanRigorEstimate /* should generalize! */)
1108
      || nrrdCopy(noutFT, ninFT)) {
1109
    biffMovef(GAGE, NRRD, "%s: trouble with initial transforms", me);
1110
    airMopError(mop); return 1;
1111
  }
1112
  /*
1113
  nrrdSave("ninFT.nrrd", ninFT, NULL);
1114
  */
1115
  inFT = AIR_CAST(double *, ninFT->data);
1116
  outFT = AIR_CAST(double *, noutFT->data);
1117
  for (blIdx=0; blIdx<sbp->num; blIdx++) {
1118
    if (sbp->verbose) {
1119
      fprintf(stderr, "%s: . . . %u/%u (scale %g, tau %g) . . . ", me,
1120
              blIdx, sbp->num, sbp->sigma[blIdx],
1121
              gageTauOfSig(sbp->sigma[blIdx]));
1122
      fflush(stderr);
1123
    }
1124
    tblur = sbp->sigma[blIdx]*sbp->sigma[blIdx];
1125
    for (axi=0; axi<3; axi++) {
1126
      for (ii=0; ii<size[axi]; ii++) {
1127
        theta = AIR_AFFINE(0, ii, size[axi], 0.0, 2*AIR_PI);
1128
        /* from eq (22) of T. Lindeberg "Scale-Space for Discrete
1129
           Signals", IEEE PAMI 12(234-254); 1990 */
1130
        ww[axi][ii] = exp(tblur*(cos(theta)-1.0));
1131
        /*
1132
        fprintf(stderr, "!%s: ww[%u][%u] = %g\n", me, axi,
1133
                AIR_CAST(unsigned int, ii), ww[axi][ii]);
1134
        */
1135
      }
1136
    }
1137
    ii=0;
1138
    /*
1139
    for (axi=0; axi<3; axi++) {
1140
      fprintf(stderr, "!%s: size[%u] = %u\n", me, axi,
1141
              AIR_CAST(unsigned int, size[axi]));
1142
    }
1143
    */
1144
    for (zi=0; zi<size[2]; zi++) {
1145
      for (yi=0; yi<size[1]; yi++) {
1146
        for (xi=0; xi<size[0]; xi++) {
1147
          double wght;
1148
          wght = sbp->oneDim ? 1.0 : ww[1][yi]*ww[2][zi];
1149
          wght *= ww[0][xi];
1150
          outFT[0 + 2*ii] = wght*inFT[0 + 2*ii];
1151
          outFT[1 + 2*ii] = wght*inFT[1 + 2*ii];
1152
          /*
1153
          fprintf(stderr, "!%s: out[%u] = (%g,%g) = %g * (%g,%g)\n", me,
1154
                  AIR_CAST(unsigned int, ii),
1155
                  outFT[0 + 2*ii], outFT[1 + 2*ii],
1156
                  wght,
1157
                  inFT[0 + 2*ii], inFT[1 + 2*ii]);
1158
          */
1159
          ii++;
1160
        }
1161
      }
1162
    }
1163
    if (nrrdFFT(noutCd, noutFT, ftaxes, 3,
1164
                -1 /* backward */,
1165
                AIR_TRUE /* rescale */,
1166
                nrrdFFTWPlanRigorEstimate /* should generalize! */)
1167
        || (nrrdTypeDouble == nin->type
1168
            ? nrrdCopy(noutC, noutCd)
1169
            : nrrdCastClampRound(noutC, noutCd, nin->type,
1170
                                 AIR_TRUE /* clamp */,
1171
                                 +1 /* roundDir, when needed */))
1172
        || nrrdSlice(nblur[blIdx], noutC, 0, 0)
1173
        || nrrdContentSet_va(nblur[blIdx], "blur", nin, "")) {
1174
      biffMovef(GAGE, NRRD, "%s: trouble with back transform %u", me, blIdx);
1175
      airMopError(mop); return 1;
1176
    }
1177
    if (sbp->verbose) {
1178
      fprintf(stderr, "done\n");
1179
    }
1180
    /*
1181
    if (0) {
1182
      char fname[AIR_STRLEN_SMALL];
1183
      sprintf(fname, "noutFT-%03u.nrrd", blIdx);
1184
      nrrdSave(fname, noutFT, NULL);
1185
      sprintf(fname, "noutCd-%03u.nrrd", blIdx);
1186
      nrrdSave(fname, noutCd, NULL);
1187
      sprintf(fname, "noutC-%03u.nrrd", blIdx);
1188
      nrrdSave(fname, noutC, NULL);
1189
      sprintf(fname, "nblur-%03u.nrrd", blIdx);
1190
      nrrdSave(fname, nblur[blIdx], NULL);
1191
    }
1192
    */
1193
  }
1194
1195
  airMopOkay(mop);
1196
  return 0;
1197
}
1198
1199
static int
1200
_stackBlurSpatial(Nrrd *const nblur[], gageStackBlurParm *sbp,
1201
                  NrrdKernelSpec *kssb,
1202
                  const Nrrd *nin, const gageKind *kind) {
1203
  static const char me[]="_stackBlurSpatial";
1204
  NrrdResampleContext *rsmc;
1205
  Nrrd *niter;
1206
  unsigned int axi, blIdx;
1207
  int E, iterative, rsmpType;
1208
  double timeStepMax, /* max length of diffusion time allowed per blur,
1209
                         as determined by sbp->dgGoodSigmaMax */
1210
    timeDone,         /* amount of diffusion time just applied */
1211
    timeLeft;         /* amount of diffusion time left to do */
1212
  airArray *mop;
1213
1214
  mop = airMopNew();
1215
  rsmc = nrrdResampleContextNew();
1216
  airMopAdd(mop, rsmc, (airMopper)nrrdResampleContextNix, airMopAlways);
1217
  if (nrrdKernelDiscreteGaussian == kssb->kernel) {
1218
    iterative = AIR_TRUE;
1219
    /* we don't want to lose precision when iterating */
1220
    rsmpType = nrrdResample_nt;
1221
    /* may be used with iterative diffusion */
1222
    niter = nrrdNew();
1223
    airMopAdd(mop, niter, (airMopper)nrrdNuke, airMopAlways);
1224
  } else {
1225
    iterative = AIR_FALSE;
1226
    rsmpType = nrrdTypeDefault;
1227
    niter = NULL;
1228
  }
1229
1230
  E = 0;
1231
  if (!E) E |= nrrdResampleDefaultCenterSet(rsmc, nrrdDefaultCenter);
1232
  /* the input for the first scale is indeed nin, regardless
1233
     of iterative */
1234
  if (!E) E |= nrrdResampleInputSet(rsmc, nin);
1235
  if (kind->baseDim) {
1236
    unsigned int bai;
1237
    for (bai=0; bai<kind->baseDim; bai++) {
1238
      if (!E) E |= nrrdResampleKernelSet(rsmc, bai, NULL, NULL);
1239
    }
1240
  }
1241
  for (axi=0; axi<3; axi++) {
1242
    if (!E) E |= nrrdResampleSamplesSet(rsmc, kind->baseDim + axi,
1243
                                        nin->axis[kind->baseDim + axi].size);
1244
    if (!E) E |= nrrdResampleRangeFullSet(rsmc, kind->baseDim + axi);
1245
  }
1246
  if (!E) E |= nrrdResampleBoundarySpecSet(rsmc, sbp->bspec);
1247
  if (!E) E |= nrrdResampleTypeOutSet(rsmc, rsmpType);
1248
  if (!E) E |= nrrdResampleClampSet(rsmc, AIR_TRUE); /* probably moot */
1249
  if (!E) E |= nrrdResampleRenormalizeSet(rsmc, sbp->renormalize);
1250
  if (E) {
1251
    biffAddf(GAGE, "%s: trouble setting up resampling", me);
1252
    airMopError(mop); return 1;
1253
  }
1254
1255
  timeDone = 0;
1256
  timeStepMax = (sbp->dgGoodSigmaMax)*(sbp->dgGoodSigmaMax);
1257
  for (blIdx=0; blIdx<sbp->num; blIdx++) {
1258
    if (sbp->verbose) {
1259
      fprintf(stderr, "%s: . . . blurring %u / %u (scale %g) . . . ",
1260
              me, blIdx, sbp->num, sbp->sigma[blIdx]);
1261
      fflush(stderr);
1262
    }
1263
    if (iterative) {
1264
      double timeNow = sbp->sigma[blIdx]*sbp->sigma[blIdx];
1265
      unsigned int passIdx = 0;
1266
      timeLeft = timeNow - timeDone;
1267
      if (sbp->verbose) {
1268
        fprintf(stderr, "\n");
1269
        fprintf(stderr, "%s: scale %g == time %g (tau %g);\n"
1270
                "               timeLeft %g = %g - %g\n",
1271
                me, sbp->sigma[blIdx], timeNow, gageTauOfTee(timeNow),
1272
                timeLeft, timeNow, timeDone);
1273
        if (timeLeft > timeStepMax) {
1274
          fprintf(stderr, "%s: diffusing for time %g in steps of %g\n", me,
1275
                  timeLeft, timeStepMax);
1276
        }
1277
        fflush(stderr);
1278
      }
1279
      do {
1280
        double timeDo;
1281
        if (blIdx || passIdx) {
1282
          /* either we're past the first scale (blIdx >= 1), or
1283
             (unlikely) we're on the first scale but after the first
1284
             pass of a multi-pass blurring, so we have to feed the
1285
             previous result back in as input.
1286
             AND: the way that niter is being used is very sneaky,
1287
             and probably too clever: the resampling happens in
1288
             multiple passes, among buffers internal to nrrdResample;
1289
             so its okay have the output and input nrrds be the same:
1290
             they're never used at the same time. */
1291
          if (!E) E |= nrrdResampleInputSet(rsmc, niter);
1292
        }
1293
        timeDo = (timeLeft > timeStepMax
1294
                  ? timeStepMax
1295
                  : timeLeft);
1296
        /* it is the repeated re-setting of this parm[0] which motivated
1297
           copying to our own kernel spec, so that the given one in the
1298
           gageStackBlurParm can stay untouched */
1299
        kssb->parm[0] = sqrt(timeDo);
1300
        for (axi=0; axi<3; axi++) {
1301
          if (!sbp->oneDim || !axi) {
1302
            /* we set the blurring kernel on this axis if
1303
               we are NOT doing oneDim, or, we are,
1304
               but this is axi == 0 */
1305
            if (!E) E |= nrrdResampleKernelSet(rsmc, kind->baseDim + axi,
1306
                                               kssb->kernel,
1307
                                               kssb->parm);
1308
          } else {
1309
            /* what to do with oneDom on axi 1, 2 */
1310
            /* you might think that we should just do no resampling at all
1311
               on this axis, but that would undermine the in==out==niter
1312
               trick described above; and produce the mysterious behavior
1313
               that the second scale-space volume is all 0.0 */
1314
            double boxparm[NRRD_KERNEL_PARMS_NUM] = {1.0};
1315
            if (!E) E |= nrrdResampleKernelSet(rsmc, kind->baseDim + axi,
1316
                                               nrrdKernelBox, boxparm);
1317
          }
1318
        }
1319
        if (sbp->verbose) {
1320
          fprintf(stderr, "  pass %u (timeLeft=%g => "
1321
                  "time=%g, sigma=%g) ...\n",
1322
                  passIdx, timeLeft, timeDo, kssb->parm[0]);
1323
        }
1324
        if (!E) E |= nrrdResampleExecute(rsmc, niter);
1325
        /* for debugging problem with getting zero output
1326
        if (!E) {
1327
          NrrdRange *nrange;
1328
          nrange = nrrdRangeNewSet(niter, AIR_FALSE);
1329
          fprintf(stderr, "%s: min/max = %g/%g\n", me,
1330
                  nrange->min, nrange->max);
1331
          if (!nrange->min || !nrange->max) {
1332
            fprintf(stderr, "%s: what? zero zero\n", me);
1333
            biffAddf(GAGE, "%s: no good", me);
1334
            airMopError(mop); return 1;
1335
          }
1336
        }
1337
        */
1338
        timeLeft -= timeDo;
1339
        passIdx++;
1340
      } while (!E && timeLeft > 0.0);
1341
      /* at this point we have to replicate the behavior of the
1342
         last stage of resampling (e.g. _nrrdResampleOutputUpdate
1343
         in nrrd/resampleContext.c), since we've gently hijacked
1344
         the resampling to access the nrrdResample_t blurring
1345
         result (for further blurring) */
1346
      if (!E) E |= nrrdCastClampRound(nblur[blIdx], niter, nin->type,
1347
                                      AIR_TRUE,
1348
                                      nrrdTypeIsIntegral[nin->type]);
1349
      if (!E) E |= nrrdContentSet_va(nblur[blIdx], "blur", nin, "");
1350
      timeDone = timeNow;
1351
    } else { /* do blurring in one shot */
1352
      kssb->parm[0] = sbp->sigma[blIdx];
1353
      for (axi=0; axi<(sbp->oneDim ? 1u : 3u); axi++) {
1354
        if (!E) E |= nrrdResampleKernelSet(rsmc, kind->baseDim + axi,
1355
                                           kssb->kernel,
1356
                                           kssb->parm);
1357
      }
1358
      if (!E) E |= nrrdResampleExecute(rsmc, nblur[blIdx]);
1359
    }
1360
    if (E) {
1361
      if (sbp->verbose) {
1362
        fprintf(stderr, "problem!\n");
1363
      }
1364
      biffMovef(GAGE, NRRD, "%s: trouble w/ %u of %u (scale %g)",
1365
                me, blIdx, sbp->num, sbp->sigma[blIdx]);
1366
      airMopError(mop); return 1;
1367
    }
1368
    if (sbp->verbose) {
1369
      fprintf(stderr, "  done.\n");
1370
    }
1371
  } /* for blIdx */
1372
1373
  airMopOkay(mop);
1374
  return 0;
1375
}
1376
1377
/*
1378
** little helper function to do pre-blurring of a given nrrd
1379
** of the sort that might be useful for scale-space gage use
1380
**
1381
** nblur has to already be allocated for "blNum" Nrrd*s, AND, they all
1382
** have to point to valid (possibly empty) Nrrds, so they can hold the
1383
** results of blurring
1384
*/
1385
int
1386
gageStackBlur(Nrrd *const nblur[], gageStackBlurParm *sbp,
1387
              const Nrrd *nin, const gageKind *kind) {
1388
  static const char me[]="gageStackBlur";
1389
  unsigned int blIdx, kvpIdx;
1390
  NrrdKernelSpec *kssb;
1391
  blurVal_t *blurVal;
1392
  airArray *mop;
1393
  int E, fftable, spatialBlurred;
1394
1395
  if (!(nblur && sbp && nin && kind)) {
1396
    biffAddf(GAGE, "%s: got NULL pointer", me);
1397
    return 1;
1398
  }
1399
  if (gageStackBlurParmCheck(sbp)) {
1400
    biffAddf(GAGE, "%s: problem with parms", me);
1401
    return 1;
1402
  }
1403
  if (_checkNrrd(nblur, NULL, sbp->num, AIR_FALSE, nin, kind)) {
1404
    biffAddf(GAGE, "%s: problem with input ", me);
1405
    return 1;
1406
  }
1407
  mop = airMopNew();
1408
  kssb = nrrdKernelSpecCopy(sbp->kspec);
1409
  airMopAdd(mop, kssb, (airMopper)nrrdKernelSpecNix, airMopAlways);
1410
  /* see if we can use FFT-based implementation */
1411
  fftable = (!sbp->needSpatialBlur
1412
             && nrrdBoundaryWrap == sbp->bspec->boundary
1413
             && nrrdKernelDiscreteGaussian == sbp->kspec->kernel);
1414
  if (fftable && nrrdFFTWEnabled) {
1415
    /* go directly to FFT-based blurring */
1416
    if (_stackBlurDiscreteGaussFFT(nblur, sbp, nin, kind)) {
1417
      biffAddf(GAGE, "%s: trouble with frequency-space blurring", me);
1418
      airMopError(mop); return 1;
1419
    }
1420
    spatialBlurred = AIR_FALSE;
1421
  } else { /* else either not fft-able, or not it was, but not available;
1422
              in either case we have to do spatial blurring */
1423
    if (fftable && !nrrdFFTWEnabled) {
1424
      if (sbp->verbose) {
1425
        fprintf(stderr, "%s: NOTE: FFT-based blurring applicable but not "
1426
                "available in this Teem build (not built with FFTW)\n", me);
1427
      }
1428
    } else {
1429
      if (sbp->verbose) {
1430
        char kstr[AIR_STRLEN_LARGE], bstr[AIR_STRLEN_LARGE];
1431
        nrrdKernelSpecSprint(kstr, kssb);
1432
        nrrdBoundarySpecSprint(bstr, sbp->bspec);
1433
        fprintf(stderr, "%s: (FFT-based blurring not applicable: "
1434
                "need spatial blur=%s, boundary=%s, kernel=%s)\n", me,
1435
                sbp->needSpatialBlur ? "yes" : "no", bstr, kstr);
1436
      }
1437
    }
1438
    if (_stackBlurSpatial(nblur, sbp, kssb, nin, kind)) {
1439
      biffAddf(GAGE, "%s: trouble with spatial-domain blurring", me);
1440
      airMopError(mop); return 1;
1441
    }
1442
    spatialBlurred = AIR_TRUE;
1443
  }
1444
  /* add the KVPs to document how these were blurred */
1445
  if (!( blurVal = _blurValAlloc(mop, sbp, kssb, nin, spatialBlurred) )) {
1446
    biffAddf(GAGE, "%s: problem getting KVP buffer", me);
1447
    airMopError(mop); return 1;
1448
  }
1449
  E = 0;
1450
  for (blIdx=0; blIdx<sbp->num; blIdx++) {
1451
    for (kvpIdx=0; kvpIdx<KVP_NUM; kvpIdx++) {
1452
      if (KVP_DGGSM_IDX != kvpIdx) {
1453
        if (!E) E |= nrrdKeyValueAdd(nblur[blIdx], _blurKey[kvpIdx],
1454
                                     blurVal[blIdx].val[kvpIdx]);
1455
      } else {
1456
        /* only need to save dgGoodSigmaMax if it was spatially blurred
1457
           with the discrete gaussian kernel */
1458
        if (spatialBlurred
1459
            && nrrdKernelDiscreteGaussian == kssb->kernel) {
1460
          if (!E) E |= nrrdKeyValueAdd(nblur[blIdx], _blurKey[kvpIdx],
1461
                                       blurVal[blIdx].val[kvpIdx]);
1462
        }
1463
      }
1464
    }
1465
  }
1466
  if (E) {
1467
    biffMovef(GAGE, NRRD, "%s: trouble adding KVPs", me);
1468
    airMopError(mop); return 1;
1469
  }
1470
1471
  airMopOkay(mop);
1472
  return 0;
1473
}
1474
1475
/*
1476
******** gageStackBlurCheck
1477
**
1478
** (docs)
1479
**
1480
*/
1481
int
1482
gageStackBlurCheck(const Nrrd *const nblur[],
1483
                   gageStackBlurParm *sbp,
1484
                   const Nrrd *nin, const gageKind *kind) {
1485
  static const char me[]="gageStackBlurCheck";
1486
  gageShape *shapeOld, *shapeNew;
1487
  blurVal_t *blurVal;
1488
  airArray *mop;
1489
  unsigned int blIdx, kvpIdx;
1490
  NrrdKernelSpec *kssb;
1491
1492
  if (!(nblur && sbp && nin && kind)) {
1493
    biffAddf(GAGE, "%s: got NULL pointer", me);
1494
    return 1;
1495
  }
1496
  mop = airMopNew();
1497
  kssb = nrrdKernelSpecCopy(sbp->kspec);
1498
  airMopAdd(mop, kssb, (airMopper)nrrdKernelSpecNix, airMopAlways);
1499
  if (gageStackBlurParmCheck(sbp)
1500
      || _checkNrrd(NULL, nblur, sbp->num, AIR_TRUE, nin, kind)
1501
      || (!( blurVal = _blurValAlloc(mop, sbp, kssb, nin,
1502
                                     (sbp->needSpatialBlur
1503
                                      ? AIR_TRUE
1504
                                      : AIR_FALSE)) )) ) {
1505
    biffAddf(GAGE, "%s: problem", me);
1506
    airMopError(mop); return 1;
1507
  }
1508
1509
  shapeNew = gageShapeNew();
1510
  airMopAdd(mop, shapeNew, (airMopper)gageShapeNix, airMopAlways);
1511
  if (gageShapeSet(shapeNew, nin, kind->baseDim)) {
1512
    biffAddf(GAGE, "%s: trouble setting up reference shape", me);
1513
    airMopError(mop); return 1;
1514
  }
1515
  shapeOld = gageShapeNew();
1516
  airMopAdd(mop, shapeOld, (airMopper)gageShapeNix, airMopAlways);
1517
1518
  for (blIdx=0; blIdx<sbp->num; blIdx++) {
1519
    if (nin->type != nblur[blIdx]->type) {
1520
      biffAddf(GAGE, "%s: nblur[%u]->type %s != nin type %s\n", me,
1521
               blIdx, airEnumStr(nrrdType, nblur[blIdx]->type),
1522
               airEnumStr(nrrdType, nin->type));
1523
      airMopError(mop); return 1;
1524
    }
1525
    /* check to see if nblur[blIdx] is as expected */
1526
    if (gageShapeSet(shapeOld, nblur[blIdx], kind->baseDim)
1527
        || !gageShapeEqual(shapeOld, "nblur", shapeNew, "nin")) {
1528
      biffAddf(GAGE, "%s: trouble, or nblur[%u] shape != nin shape",
1529
               me, blIdx);
1530
      airMopError(mop); return 1;
1531
    }
1532
    /* see if the KVPs are there with the required values */
1533
    for (kvpIdx=0; kvpIdx<KVP_NUM; kvpIdx++) {
1534
      char *tmpval;
1535
      tmpval = nrrdKeyValueGet(nblur[blIdx], _blurKey[kvpIdx]);
1536
      airMopAdd(mop, tmpval, airFree, airMopAlways);
1537
      if (KVP_DGGSM_IDX != kvpIdx) {
1538
        if (!tmpval) {
1539
          biffAddf(GAGE, "%s: didn't see key \"%s\" in nblur[%u]", me,
1540
                   _blurKey[kvpIdx], blIdx);
1541
          airMopError(mop); return 1;
1542
        }
1543
        if (KVP_SBLUR_IDX == kvpIdx) {
1544
          /* this KVP is handled differently */
1545
          if (!sbp->needSpatialBlur) {
1546
            /* we don't care if it was frequency-domain or spatial-domain
1547
               blurring, so there's no right answer */
1548
            continue;
1549
          }
1550
          /* else we do need spatial domain blurring; so do the check below */
1551
        }
1552
        if (strcmp(tmpval, blurVal[blIdx].val[kvpIdx])) {
1553
          biffAddf(GAGE, "%s: found key[%s] \"%s\" != wanted \"%s\"", me,
1554
                   _blurKey[kvpIdx], tmpval, blurVal[blIdx].val[kvpIdx]);
1555
          airMopError(mop); return 1;
1556
        }
1557
      } else {
1558
        /* KVP_DGGSM_IDX == kvpIdx; handled differently since KVP isn't saved
1559
           when not needed */
1560
        if (!sbp->needSpatialBlur) {
1561
          /* if you don't care about needing spatial blurring, you
1562
             lose the right to care about a difference in dggsm */
1563
          continue;
1564
        }
1565
        if (tmpval) {
1566
          /* therefore it was spatially blurred, so there's a saved DGGSM,
1567
             and we do need spatial blurring, so we compare them */
1568
          if (strcmp(tmpval, blurVal[blIdx].val[kvpIdx])) {
1569
            biffAddf(GAGE, "%s: found key[%s] \"%s\" != wanted \"%s\"", me,
1570
                     _blurKey[kvpIdx], tmpval, blurVal[blIdx].val[kvpIdx]);
1571
            /* HEY: a change in the discrete Gaussian cut-off will result
1572
               in a recomputation of the blurrings, even with FFT-based
1573
               blurring, though cut-off is completely moot then */
1574
            airMopError(mop); return 1;
1575
          }
1576
        }
1577
        continue;
1578
      }
1579
    }
1580
  }
1581
1582
  airMopOkay(mop);
1583
  return 0;
1584
}
1585
1586
int
1587
gageStackBlurGet(Nrrd *const nblur[], int *recomputedP,
1588
                 gageStackBlurParm *sbp,
1589
                 const char *format,
1590
                 const Nrrd *nin, const gageKind *kind) {
1591
  static const char me[]="gageStackBlurGet";
1592
  airArray *mop;
1593
  int recompute;
1594
  unsigned int ii;
1595
1596
1597
  if (!( nblur && sbp && nin && kind )) {
1598
    biffAddf(GAGE, "%s: got NULL pointer", me);
1599
    return 1;
1600
  }
1601
  for (ii=0; ii<sbp->num; ii++) {
1602
    if (!nblur[ii]) {
1603
      biffAddf(GAGE, "%s: nblur[%u] NULL", me, ii);
1604
      return 1;
1605
    }
1606
  }
1607
  if (gageStackBlurParmCheck(sbp)) {
1608
    biffAddf(GAGE, "%s: trouble with blur parms", me);
1609
    return 1;
1610
  }
1611
  mop = airMopNew();
1612
1613
  /* set recompute flag */
1614
  if (!airStrlen(format)) {
1615
    /* no info about files to load, obviously have to recompute */
1616
    if (sbp->verbose) {
1617
      fprintf(stderr, "%s: no file info, must recompute blurrings\n", me);
1618
    }
1619
    recompute = AIR_TRUE;
1620
  } else {
1621
    char *fname, *suberr;
1622
    int firstExists;
1623
    FILE *file;
1624
    /* do have info about files to load, but may fail in many ways */
1625
    fname = AIR_CALLOC(strlen(format) + AIR_STRLEN_SMALL, char);
1626
    if (!fname) {
1627
      biffAddf(GAGE, "%s: couldn't allocate fname", me);
1628
      airMopError(mop); return 1;
1629
    }
1630
    airMopAdd(mop, fname, airFree, airMopAlways);
1631
    /* see if we can get the first file (number 0) */
1632
    sprintf(fname, format, 0);
1633
    firstExists = !!(file = fopen(fname, "r"));
1634
    airFclose(file);
1635
    if (!firstExists) {
1636
      if (sbp->verbose) {
1637
        fprintf(stderr, "%s: no file \"%s\"; will recompute blurrings\n",
1638
                me, fname);
1639
      }
1640
      recompute = AIR_TRUE;
1641
    } else if (nrrdLoadMulti(nblur, sbp->num, format, 0, NULL)) {
1642
      airMopAdd(mop, suberr = biffGetDone(NRRD), airFree, airMopAlways);
1643
      if (sbp->verbose) {
1644
        fprintf(stderr, "%s: will recompute blurrings that couldn't be "
1645
                "read:\n%s\n", me, suberr);
1646
      }
1647
      recompute = AIR_TRUE;
1648
    } else if (gageStackBlurCheck(AIR_CAST(const Nrrd*const*, nblur),
1649
                                  sbp, nin, kind)) {
1650
      airMopAdd(mop, suberr = biffGetDone(GAGE), airFree, airMopAlways);
1651
      if (sbp->verbose) {
1652
        fprintf(stderr, "%s: will recompute blurrings (from \"%s\") "
1653
                "that don't match:\n%s\n", me, format, suberr);
1654
      }
1655
      recompute = AIR_TRUE;
1656
    } else {
1657
      /* else precomputed blurrings could all be read, and did match */
1658
      if (sbp->verbose) {
1659
        fprintf(stderr, "%s: will reuse %u %s pre-blurrings.\n", me,
1660
                sbp->num, format);
1661
      }
1662
      recompute = AIR_FALSE;
1663
    }
1664
  }
1665
  if (recompute) {
1666
    if (gageStackBlur(nblur, sbp, nin, kind)) {
1667
      biffAddf(GAGE, "%s: trouble computing blurrings", me);
1668
      airMopError(mop); return 1;
1669
    }
1670
  }
1671
  if (recomputedP) {
1672
    *recomputedP = recompute;
1673
  }
1674
1675
  airMopOkay(mop);
1676
  return 0;
1677
}
1678
1679
/*
1680
******** gageStackBlurManage
1681
**
1682
** does the work of gageStackBlurGet and then some:
1683
** allocates the array of Nrrds, allocates an array of doubles for scale,
1684
** and saves output if recomputed
1685
*/
1686
int
1687
gageStackBlurManage(Nrrd ***nblurP, int *recomputedP,
1688
                    gageStackBlurParm *sbp,
1689
                    const char *format,
1690
                    int saveIfComputed, NrrdEncoding *enc,
1691
                    const Nrrd *nin, const gageKind *kind) {
1692
  static const char me[]="gageStackBlurManage";
1693
  Nrrd **nblur;
1694
  unsigned int ii;
1695
  airArray *mop;
1696
  int recomputed;
1697
1698
  if (!( nblurP && sbp && nin && kind )) {
1699
    biffAddf(GAGE, "%s: got NULL pointer", me);
1700
    return 1;
1701
  }
1702
  nblur = *nblurP = AIR_CALLOC(sbp->num, Nrrd *);
1703
  if (!nblur) {
1704
    biffAddf(GAGE, "%s: couldn't alloc %u Nrrd*s", me, sbp->num);
1705
    return 1;
1706
  }
1707
1708
  mop = airMopNew();
1709
  airMopAdd(mop, nblurP, (airMopper)airSetNull, airMopOnError);
1710
  airMopAdd(mop, *nblurP, airFree, airMopOnError);
1711
  for (ii=0; ii<sbp->num; ii++) {
1712
    nblur[ii] = nrrdNew();
1713
    airMopAdd(mop, nblur[ii], (airMopper)nrrdNuke, airMopOnError);
1714
  }
1715
  if (gageStackBlurGet(nblur, &recomputed, sbp, format, nin, kind)) {
1716
    biffAddf(GAGE, "%s: trouble getting nblur", me);
1717
    airMopError(mop); return 1;
1718
  }
1719
  if (recomputedP) {
1720
    *recomputedP = recomputed;
1721
  }
1722
  if (recomputed && format && saveIfComputed) {
1723
    NrrdIoState *nio;
1724
    int E;
1725
    E = 0;
1726
    if (enc) {
1727
      if (!enc->available()) {
1728
        biffAddf(GAGE, "%s: requested %s encoding which is not "
1729
                 "available in this build", me, enc->name);
1730
        airMopError(mop); return 1;
1731
      }
1732
      nio = nrrdIoStateNew();
1733
      airMopAdd(mop, nio, (airMopper)nrrdIoStateNix, airMopAlways);
1734
      if (!E) E |= nrrdIoStateEncodingSet(nio, nrrdEncodingGzip);
1735
    } else {
1736
      nio = NULL;
1737
    }
1738
    if (!E) E |= nrrdSaveMulti(format, AIR_CAST(const Nrrd *const *,
1739
                                                nblur),
1740
                               sbp->num, 0, nio);
1741
    if (E) {
1742
      biffMovef(GAGE, NRRD, "%s: trouble saving blurrings", me);
1743
      airMopError(mop); return 1;
1744
    }
1745
  }
1746
1747
  airMopOkay(mop);
1748
  return 0;
1749
}