コード例 #1
0
ファイル: stack.c プロジェクト: ryanfb/teem-parallel
/*
** little helper function to do pre-blurring of a given nrrd
** of the sort that might be useful for scale-space gage use
**
** nblur has to already be allocated for "blnum" Nrrd*s, AND, they all
** have to point to valid (possibly empty) Nrrds, so they can hold the
** results of blurring. "scale" is filled with the result of
** scaleCB(d_i), for "dom" evenly-spaced samples d_i between
** scldomMin and scldomMax
*/
int
gageStackBlur(Nrrd *const nblur[], const double *scale,
              unsigned int blnum,
              const Nrrd *nin, unsigned int baseDim,
              const NrrdKernelSpec *_kspec,
              int boundary, int renormalize, int verbose) {
    char me[]="gageStackBlur", err[BIFF_STRLEN], val[AIR_STRLEN_LARGE],
              keyscl[]="scale", keykern[]="kernel";
    unsigned int blidx, axi;
    NrrdResampleContext *rsmc;
    NrrdKernelSpec *kspec;
    airArray *mop;
    int E;

    if (!(nblur && scale && nin && _kspec)) {
        sprintf(err, "%s: got NULL pointer", me);
        biffAdd(GAGE, err);
        return 1;
    }
    if (!( blnum >= 2)) {
        sprintf(err, "%s: need blnum > 2, not %u", me, blnum);
        biffAdd(GAGE, err);
        return 1;
    }
    for (blidx=0; blidx<blnum; blidx++) {
        if (!AIR_EXISTS(scale[blidx])) {
            fprintf(stderr, "%s: scale[%u] = %g doesn't exist", me, blidx,
                    scale[blidx]);
            biffAdd(GAGE, err);
            return 1;
        }
        if (blidx) {
            if (!( scale[blidx-1] < scale[blidx] )) {
                fprintf(stderr, "%s: scale[%u] = %g not < scale[%u] = %g", me,
                        blidx, scale[blidx-1], blidx+1, scale[blidx]);
                biffAdd(GAGE, err);
                return 1;
            }
        }
    }
    if (3 + baseDim != nin->dim) {
        sprintf(err, "%s: need nin->dim %u (not %u) with baseDim %u", me,
                3 + baseDim, nin->dim, baseDim);
        biffAdd(GAGE, err);
        return 1;
    }
    if (airEnumValCheck(nrrdBoundary, boundary)) {
        sprintf(err, "%s: %d not a valid %s value", me,
                boundary, nrrdBoundary->name);
        biffAdd(GAGE, err);
        return 1;
    }

    mop = airMopNew();
    kspec = nrrdKernelSpecCopy(_kspec);
    if (!kspec) {
        sprintf(err, "%s: problem copying kernel spec", me);
        biffAdd(GAGE, err);
        airMopError(mop);
        return 1;
    }
    airMopAdd(mop, kspec, (airMopper)nrrdKernelSpecNix, airMopAlways);
    /* pre-allocate output Nrrds in case not already there */
    for (blidx=0; blidx<blnum; blidx++) {
        if (!nblur[blidx]) {
            sprintf(err, "%s: got NULL nblur[%u]", me, blidx);
            biffAdd(GAGE, err);
            airMopError(mop);
            return 1;
        }
    }
    rsmc = nrrdResampleContextNew();
    airMopAdd(mop, rsmc, (airMopper)nrrdResampleContextNix, airMopAlways);

    E = 0;
    if (!E) E |= nrrdResampleDefaultCenterSet(rsmc, nrrdDefaultCenter);
    if (!E) E |= nrrdResampleNrrdSet(rsmc, nin);
    if (baseDim) {
        unsigned int bai;
        for (bai=0; bai<baseDim; bai++) {
            if (!E) E |= nrrdResampleKernelSet(rsmc, bai, NULL, NULL);
        }
    }
    for (axi=0; axi<3; axi++) {
        if (!E) E |= nrrdResampleSamplesSet(rsmc, baseDim + axi,
                                                nin->axis[baseDim + axi].size);
        if (!E) E |= nrrdResampleRangeFullSet(rsmc, baseDim + axi);
    }
    if (!E) E |= nrrdResampleBoundarySet(rsmc, boundary);
    if (!E) E |= nrrdResampleTypeOutSet(rsmc, nrrdTypeDefault);
    if (!E) E |= nrrdResampleRenormalizeSet(rsmc, renormalize);
    if (E) {
        fprintf(stderr, "%s: trouble setting up resampling\n", me);
        biffAdd(GAGE, err);
        airMopError(mop);
        return 1;
    }
    for (blidx=0; blidx<blnum; blidx++) {
        kspec->parm[0] = scale[blidx];
        for (axi=0; axi<3; axi++) {
            if (!E) E |= nrrdResampleKernelSet(rsmc, baseDim + axi,
                                                   kspec->kernel, kspec->parm);
        }
        if (verbose) {
            fprintf(stderr, "%s: resampling %u of %u (scale %g) ... ", me, blidx,
                    blnum, scale[blidx]);
            fflush(stderr);
        }
        if (!E) E |= nrrdResampleExecute(rsmc, nblur[blidx]);
        if (!E) nrrdKeyValueAdd(nblur[blidx], me, "true");
        sprintf(val, "%g", scale[blidx]);
        if (!E) nrrdKeyValueAdd(nblur[blidx], keyscl, val);
        nrrdKernelSpecSprint(val, kspec);
        if (!E) nrrdKeyValueAdd(nblur[blidx], keykern, val);
        if (E) {
            if (verbose) {
                fprintf(stderr, "problem!\n");
            }
            sprintf(err, "%s: trouble resampling %u of %u (scale %g)",
                    me, blidx, blnum, scale[blidx]);
            biffAdd(GAGE, err);
            airMopError(mop);
            return 1;
        }
        if (verbose) {
            fprintf(stderr, "done.\n");
        }
    }

    airMopOkay(mop);
    return 0;
}
コード例 #2
0
ファイル: resample.c プロジェクト: ryanfb/teem-parallel
int
unrrdu_resampleMain(int argc, char **argv, char *me, hestParm *hparm) {
  hestOpt *opt = NULL;
  char *out, *err;
  Nrrd *nin, *nout;
  int type, bb, pret, norenorm, older, E, defaultCenter, verbose;
  unsigned int scaleLen, ai, samplesOut;
  airArray *mop;
  float *scale;
  double padVal;
  NrrdResampleInfo *info;
  NrrdResampleContext *rsmc;
  NrrdKernelSpec *unuk;

  mop = airMopNew();
  info = nrrdResampleInfoNew();
  airMopAdd(mop, info, (airMopper)nrrdResampleInfoNix, airMopAlways);
  hparm->elideSingleOtherDefault = AIR_FALSE;
  hestOptAdd(&opt, "old", NULL, airTypeInt, 0, 0, &older, NULL,
             "instead of using the new nrrdResampleContext implementation, "
             "use the old nrrdSpatialResample implementation");
  hestOptAdd(&opt, "s,size", "sz0", airTypeOther, 1, -1, &scale, NULL,
             "For each axis, information about how many samples in output:\n "
             "\b\bo \"=\": leave this axis completely untouched: no "
             "resampling whatsoever\n "
             "\b\bo \"x<float>\": multiply the number of input samples by "
             "<float>, and round to the nearest integer, to get the number "
             "of output samples.  Use \"x1\" to resample the axis but leave "
             "the number of samples unchanged\n "
             "\b\bo \"<int>\": exact number of output samples",
             &scaleLen, NULL, &unrrduHestScaleCB);
  hestOptAdd(&opt, "k,kernel", "kern", airTypeOther, 1, 1, &unuk,
             "cubic:0,0.5",
             "The kernel to use for resampling.  "
             "Kernels logically live in the input index space for upsampling, "
             "and in the output index space for downsampling.  "
             "Possibilities include:\n "
             "\b\bo \"box\": nearest neighbor interpolation\n "
             "\b\bo \"cheap\": nearest neighbor interpolation for upsampling, "
             "and non-blurring sub-sampling (pick subset of input samples) "
             "on downsampling\n "
             "\b\bo \"tent\": linear interpolation\n "
             "\b\bo \"cubic:B,C\": Mitchell/Netravali BC-family of "
             "cubics:\n "
             "\t\t\"cubic:1,0\": B-spline; maximal blurring\n "
             "\t\t\"cubic:0,0.5\": Catmull-Rom; good interpolating kernel\n "
             "\b\bo \"quartic:A\": 1-parameter family of "
             "interpolating quartics (\"quartic:0.0834\" is most accurate)\n "
             "\b\bo \"hann:R\": Hann (cosine bell) windowed sinc, radius R\n "
             "\b\bo \"black:R\": Blackman windowed sinc, radius R\n "
             "\b\bo \"gauss:S,C\": Gaussian blurring, with standard deviation "
             "S and cut-off at C standard deviations",
             NULL, NULL, nrrdHestKernelSpec);
  hestOptAdd(&opt, "nrn", NULL, airTypeInt, 0, 0, &norenorm, NULL,
             "don't do per-pass kernel weight renormalization. "
             "Doing the renormalization is not a big performance hit, and "
             "is sometimes needed to avoid \"grating\" on non-integral "
             "down-sampling.  Disabling the renormalization is needed for "
             "correct results with artificially narrow kernels. ");
  hestOptAdd(&opt, "b,boundary", "behavior", airTypeEnum, 1, 1, &bb, "bleed",
             "How to handle samples beyond the input bounds:\n "
             "\b\bo \"pad\": use some specified value\n "
             "\b\bo \"bleed\": extend border values outward\n "
             "\b\bo \"wrap\": wrap-around to other side", 
             NULL, nrrdBoundary);
  hestOptAdd(&opt, "v,value", "value", airTypeDouble, 1, 1, &padVal, "0.0",
             "for \"pad\" boundary behavior, pad with this value");
  hestOptAdd(&opt, "t,type", "type", airTypeOther, 1, 1, &type, "default",
             "type to save OUTPUT as. By default (not using this option), "
             "the output type is the same as the input type",
             NULL, NULL, &unrrduHestMaybeTypeCB);
  hestOptAdd(&opt, "cheap", NULL, airTypeInt, 0, 0, &(info->cheap), NULL,
             "DEPRECATED: the \"-k cheap\" option is the new (and more "
             "reliable) way to access this functionality. \"-cheap\" is "
             "only here for legacy use in combination with \"-old\".\n "
             "When downsampling (reducing number of samples), don't "
             "try to do correct filtering by scaling kernel to match "
             "new (stretched) index space; keep it in old index space. "
             "When used in conjunction with \"-k box\", this can implement "
             "subsampling which chooses every Nth value. ");
  hestOptAdd(&opt, "c,center", "center", airTypeEnum, 1, 1, &defaultCenter,
             (nrrdCenterCell == nrrdDefaultCenter
              ? "cell"
              : "node"),
             "(not available with \"-old\") "
             "default centering of axes when input nrrd "
             "axes don't have a known centering: \"cell\" or \"node\" ",
             NULL, nrrdCenter);
  hestOptAdd(&opt, "verbose", NULL, airTypeInt, 0, 0, &verbose, NULL,
             "(not available with \"-old\") "
             "turn on verbosity ");
  OPT_ADD_NIN(nin, "input nrrd");
  OPT_ADD_NOUT(out, "output nrrd");

  airMopAdd(mop, opt, (airMopper)hestOptFree, airMopAlways);
  
  USAGE(_unrrdu_resampleInfoL);
  PARSE();
  airMopAdd(mop, opt, (airMopper)hestParseFree, airMopAlways);

  nout = nrrdNew();
  airMopAdd(mop, nout, (airMopper)nrrdNuke, airMopAlways);

  if (scaleLen != nin->dim) {
    fprintf(stderr, "%s: # sampling sizes (%d) != input nrrd dimension (%d)\n",
            me, scaleLen, nin->dim);
    airMopError(mop);
    return 1;
  }
  if (!older) {
    rsmc = nrrdResampleContextNew();
    rsmc->verbose = verbose;
    airMopAdd(mop, rsmc, (airMopper)nrrdResampleContextNix, airMopAlways);
    E = AIR_FALSE;
    if (!E) E |= nrrdResampleDefaultCenterSet(rsmc, defaultCenter);
    if (!E) E |= nrrdResampleNrrdSet(rsmc, nin);
    for (ai=0; ai<nin->dim; ai++) {
      switch((int)scale[0 + 2*ai]) {
      case 0:
        /* no resampling */
        if (!E) E |= nrrdResampleKernelSet(rsmc, ai, NULL, NULL);
        break;
      case 1:
        /* scaling of input # samples */
        if (!E) E |= nrrdResampleKernelSet(rsmc, ai, unuk->kernel, unuk->parm);
        samplesOut = AIR_ROUNDUP(scale[1 + 2*ai]*nin->axis[ai].size);
        if (!E) E |= nrrdResampleSamplesSet(rsmc, ai, samplesOut);
        break;
      case 2:
        /* explicit # of samples */
        if (!E) E |= nrrdResampleKernelSet(rsmc, ai, unuk->kernel, unuk->parm);
        samplesOut = (size_t)scale[1 + 2*ai];
        if (!E) E |= nrrdResampleSamplesSet(rsmc, ai, samplesOut);
        break;
      }
      if (!E) E |= nrrdResampleRangeFullSet(rsmc, ai);
    }
    if (!E) E |= nrrdResampleBoundarySet(rsmc, bb);
    if (!E) E |= nrrdResampleTypeOutSet(rsmc, type);
    if (!E) E |= nrrdResamplePadValueSet(rsmc, padVal);
    if (!E) E |= nrrdResampleRenormalizeSet(rsmc, !norenorm);
    if (!E) E |= nrrdResampleExecute(rsmc, nout);
    if (E) {
      airMopAdd(mop, err = biffGetDone(NRRD), airFree, airMopAlways);
      fprintf(stderr, "%s: error resampling nrrd:\n%s", me, err);
      airMopError(mop);
      return 1;
    }
  } else {
    for (ai=0; ai<nin->dim; ai++) {
      /* this may be over-written below */
      info->kernel[ai] = unuk->kernel;
      switch((int)scale[0 + 2*ai]) {
      case 0:
        /* no resampling */
        info->kernel[ai] = NULL;
        break;
      case 1:
        /* scaling of input # samples */
        info->samples[ai] = AIR_ROUNDUP(scale[1 + 2*ai]*nin->axis[ai].size);
        break;
      case 2:
        /* explicit # of samples */
        info->samples[ai] = (size_t)scale[1 + 2*ai];
        break;
      }
      memcpy(info->parm[ai], unuk->parm, NRRD_KERNEL_PARMS_NUM*sizeof(double));
      if (info->kernel[ai] &&
          (!( AIR_EXISTS(nin->axis[ai].min) 
              && AIR_EXISTS(nin->axis[ai].max))) ) {
        nrrdAxisInfoMinMaxSet(nin, ai, 
                              (nin->axis[ai].center
                               ? nin->axis[ai].center 
                               : nrrdDefaultCenter));
      }
      info->min[ai] = nin->axis[ai].min;
      info->max[ai] = nin->axis[ai].max;
    }
    info->boundary = bb;
    info->type = type;
    info->padValue = padVal;
    info->renormalize = !norenorm;
    if (nrrdSpatialResample(nout, nin, info)) {
      airMopAdd(mop, err = biffGetDone(NRRD), airFree, airMopAlways);
      fprintf(stderr, "%s: error resampling nrrd:\n%s", me, err);
      airMopError(mop);
      return 1;
    }
  }
  

  SAVE(out, nout, NULL);

  airMopOkay(mop);
  return 0;
}
コード例 #3
0
ファイル: vh.c プロジェクト: SCIInstitute/Cleaver
int
main(int argc, char *argv[]) {
  char *me, *outS;
  hestOpt *hopt;
  hestParm *hparm;
  airArray *mop;

  char *err, done[13];
  Nrrd *nin, *nblur, *nout;
  NrrdKernelSpec *kb0, *kb1, *k00, *k11, *k22;
  NrrdResampleContext *rsmc;
  int E;
  unsigned int sx, sy, sz, xi, yi, zi, ai;
  gageContext *ctx;
  gagePerVolume *pvl;
  const double *gvec, *gmag, *evec0, *eval;
  double (*ins)(void *v, size_t I, double d);
  double (*lup)(const void *v, size_t I);
  double dotmax, dotpow, gmmax, evalshift, gmpow, _dotmax, _gmmax, scl, clamp;

  me = argv[0];
  mop = airMopNew();
  hparm = hestParmNew();
  hopt = NULL;
  airMopAdd(mop, hparm, (airMopper)hestParmFree, airMopAlways);
  hestOptAdd(&hopt, "i", "nin", airTypeOther, 1, 1, &nin, NULL,
             "input volume", NULL, NULL, nrrdHestNrrd);
  hestOptAdd(&hopt, "kb0", "kernel", airTypeOther, 1, 1, &kb0,
             "guass:3,5", "kernel to use for pre-process blurring",
             NULL, NULL, nrrdHestKernelSpec);
  hestOptAdd(&hopt, "kb1", "kernel", airTypeOther, 1, 1, &kb1,
             "cubic:1.5,1,0", "kernel to use for pos-process blurring",
             NULL, NULL, nrrdHestKernelSpec);
  hestOptAdd(&hopt, "k00", "kernel", airTypeOther, 1, 1, &k00,
             "cubic:1,0", "k00", NULL, NULL, nrrdHestKernelSpec);
  hestOptAdd(&hopt, "k11", "kernel", airTypeOther, 1, 1, &k11,
             "cubicd:1,0", "k00", NULL, NULL, nrrdHestKernelSpec);
  hestOptAdd(&hopt, "k22", "kernel", airTypeOther, 1, 1, &k22,
             "cubicdd:1,0", "k00", NULL, NULL, nrrdHestKernelSpec);
  hestOptAdd(&hopt, "dotmax", "dot", airTypeDouble, 1, 1, &dotmax, "5",
             "max effective value of dot(gvec, evec0)");
  hestOptAdd(&hopt, "evs", "shift", airTypeDouble, 1, 1, &evalshift, "0",
             "negative shift to avoid changing mostly flat regions");
  hestOptAdd(&hopt, "clamp", "clamp", airTypeDouble, 1, 1, &clamp, "nan",
             "if it exists, data value can't be forced below this");
  hestOptAdd(&hopt, "dotpow", "pow", airTypeDouble, 1, 1, &dotpow, "1",
             "exponent for dot");
  hestOptAdd(&hopt, "gmmax", "dot", airTypeDouble, 1, 1, &gmmax, "2",
             "max effective value of gmag");
  hestOptAdd(&hopt, "gmpow", "pow", airTypeDouble, 1, 1, &gmpow, "1",
             "exponent for gmag");
  hestOptAdd(&hopt, "scl", "scale", airTypeDouble, 1, 1, &scl, "0.1",
             "how much to scale hack to decrease input value");
  hestOptAdd(&hopt, "o", "filename", airTypeString, 1, 1, &outS, "-",
             "fixed volume output");
  hestParseOrDie(hopt, argc-1, argv+1, hparm,
                 me, vhInfo, AIR_TRUE, AIR_TRUE, AIR_TRUE);
  airMopAdd(mop, hopt, (airMopper)hestOptFree, airMopAlways);
  airMopAdd(mop, hopt, (airMopper)hestParseFree, airMopAlways);

  if (!( 3 == nin->dim
         && nrrdTypeBlock != nin->type )) {
    fprintf(stderr, "%s: need a 3-D scalar nrrd (not %u-D %s)", me,
            nin->dim, airEnumStr(nrrdType, nin->type));
    airMopError(mop); return 1;
  }

  nblur = nrrdNew();
  airMopAdd(mop, nblur, (airMopper)nrrdNuke, airMopAlways);
  if (nrrdCopy(nblur, nin)) {
    airMopAdd(mop, err = biffGetDone(NRRD), airFree, airMopAlways);
    fprintf(stderr, "%s: couldn't allocate output:\n%s", me, err);
    airMopError(mop); return 1;
  }

  fprintf(stderr, "%s: pre-blurring ... ", me);
  fflush(stderr);
  rsmc = nrrdResampleContextNew();
  airMopAdd(mop, rsmc, (airMopper)nrrdResampleContextNix, airMopAlways);
  E = AIR_FALSE;
  if (!E) E |= nrrdResampleDefaultCenterSet(rsmc, nrrdCenterCell);
  if (!E) E |= nrrdResampleNrrdSet(rsmc, nin);
  for (ai=0; ai<3; ai++) {
    if (!E) E |= nrrdResampleKernelSet(rsmc, ai, kb0->kernel, kb0->parm);
    if (!E) E |= nrrdResampleSamplesSet(rsmc, ai, nin->axis[ai].size);
    if (!E) E |= nrrdResampleRangeFullSet(rsmc, ai);
  }
  if (!E) E |= nrrdResampleBoundarySet(rsmc, nrrdBoundaryBleed);
  if (!E) E |= nrrdResampleTypeOutSet(rsmc, nrrdTypeDefault);
  if (!E) E |= nrrdResampleRenormalizeSet(rsmc, AIR_TRUE);
  if (!E) E |= nrrdResampleExecute(rsmc, nblur);
  if (E) {
    airMopAdd(mop, err = biffGetDone(NRRD), airFree, airMopAlways);
    fprintf(stderr, "%s: error resampling nrrd:\n%s", me, err);
    airMopError(mop);
    return 1;
  }
  fprintf(stderr, "done.\n");

  ctx = gageContextNew();
  airMopAdd(mop, ctx, (airMopper)gageContextNix, airMopAlways);
  gageParmSet(ctx, gageParmRenormalize, AIR_TRUE);
  gageParmSet(ctx, gageParmCheckIntegrals, AIR_TRUE);
  E = 0;
  if (!E) E |= !(pvl = gagePerVolumeNew(ctx, nblur, gageKindScl));
  if (!E) E |= gagePerVolumeAttach(ctx, pvl);
  if (!E) E |= gageKernelSet(ctx, gageKernel00, k00->kernel, k00->parm);
  if (!E) E |= gageKernelSet(ctx, gageKernel11, k11->kernel, k11->parm);
  if (!E) E |= gageKernelSet(ctx, gageKernel22, k22->kernel, k22->parm);
  if (!E) E |= gageQueryItemOn(ctx, pvl, gageSclGradVec);
  if (!E) E |= gageQueryItemOn(ctx, pvl, gageSclGradMag);
  if (!E) E |= gageQueryItemOn(ctx, pvl, gageSclHessEvec0);
  if (!E) E |= gageQueryItemOn(ctx, pvl, gageSclHessEval);
  if (!E) E |= gageUpdate(ctx);
  if (E) {
    airMopAdd(mop, err = biffGetDone(GAGE), airFree, airMopAlways);
    fprintf(stderr, "%s: trouble:\n%s\n", me, err);
    airMopError(mop); return 1;
  }
  gvec = gageAnswerPointer(ctx, pvl, gageSclGradVec);
  gmag = gageAnswerPointer(ctx, pvl, gageSclGradMag);
  evec0 = gageAnswerPointer(ctx, pvl, gageSclHessEvec0);
  eval = gageAnswerPointer(ctx, pvl, gageSclHessEval);

  nout = nrrdNew();
  airMopAdd(mop, nout, (airMopper)nrrdNuke, airMopAlways);
  if (nrrdCopy(nout, nin)) {
    airMopAdd(mop, err = biffGetDone(NRRD), airFree, airMopAlways);
    fprintf(stderr, "%s: couldn't allocate output:\n%s", me, err);
    airMopError(mop); return 1;
  }

  if (!(nout->type == nin->type && nblur->type == nin->type)) {
    fprintf(stderr, "%s: whoa, types (%s %s %s) not all equal\n", me,
            airEnumStr(nrrdType, nin->type),
            airEnumStr(nrrdType, nblur->type),
            airEnumStr(nrrdType, nout->type));
  }
  ins = nrrdDInsert[nout->type];
  lup = nrrdDLookup[nout->type];
  sx = nin->axis[0].size;
  sy = nin->axis[1].size;
  sz = nin->axis[2].size;

  gageProbe(ctx, 0, 0, 0);
  _dotmax = ELL_3V_DOT(gvec, evec0);
  _gmmax = *gmag;

  fprintf(stderr, "%s: hacking       ", me);
  fflush(stderr);
  for (zi=0; zi<sz; zi++) {
    fprintf(stderr, "%s", airDoneStr(0, zi, sz-1, done));
    fflush(stderr);
    for (yi=0; yi<sy; yi++) {
      for (xi=0; xi<sx; xi++) {
        size_t si;
        double dot, evl, gm, shift, in, out, mode;

        gageProbe(ctx, xi, yi, zi);
        si = xi + sx*(yi + sy*zi);

        dot = ELL_3V_DOT(gvec, evec0);
        _dotmax = AIR_MAX(_dotmax, dot);
        dot = AIR_ABS(dot);
        dot = 1 - AIR_MIN(dot, dotmax)/dotmax;
        dot = pow(dot, dotpow);

        evl = AIR_MAX(0, eval[0] - evalshift);
        mode = airMode3_d(eval);
        evl *= AIR_AFFINE(-1, mode, 1, 0, 1);

        _gmmax = AIR_MAX(_gmmax, *gmag);
        gm = 1 - AIR_MIN(*gmag, gmmax)/gmmax;
        gm = pow(gm, gmpow);

        shift = scl*gm*evl*dot;
        if (AIR_EXISTS(clamp)) {
          in = lup(nin->data, si);
          out = in - shift;
          out = AIR_MAX(out, clamp);
          shift = AIR_MAX(0, in - out);
        }

        ins(nout->data, si, shift);
      }
    }
  }
  fprintf(stderr, "\n");
  fprintf(stderr, "%s: max dot seen: %g\n", me, _dotmax);
  fprintf(stderr, "%s: max gm seen: %g\n", me, _gmmax);

  fprintf(stderr, "%s: post-blurring ... ", me);
  fflush(stderr);
  E = AIR_FALSE;
  if (!E) E |= nrrdResampleNrrdSet(rsmc, nout);
  for (ai=0; ai<3; ai++) {
    if (!E) E |= nrrdResampleKernelSet(rsmc, ai, kb1->kernel, kb1->parm);
    if (!E) E |= nrrdResampleSamplesSet(rsmc, ai, nout->axis[ai].size);
    if (!E) E |= nrrdResampleRangeFullSet(rsmc, ai);
  }
  if (!E) E |= nrrdResampleExecute(rsmc, nblur);
  if (E) {
    airMopAdd(mop, err = biffGetDone(NRRD), airFree, airMopAlways);
    fprintf(stderr, "%s: error resampling nrrd:\n%s", me, err);
    airMopError(mop);
    return 1;
  }
  fprintf(stderr, "done.\n");

  for (zi=0; zi<sz; zi++) {
    for (yi=0; yi<sy; yi++) {
      for (xi=0; xi<sx; xi++) {
        size_t si;
        double in, shift;

        si = xi + sx*(yi + sy*zi);
        in = lup(nin->data, si);
        shift = lup(nblur->data, si);
        ins(nout->data, si, in - shift);
      }
    }
  }

  if (nrrdSave(outS, nout, NULL)) {
    airMopAdd(mop, err = biffGetDone(NRRD), airFree, airMopAlways);
    fprintf(stderr, "%s: couldn't save output:\n%s", me, err);
    airMopError(mop); return 1;
  }

  airMopOkay(mop);
  exit(0);
}