Пример #1
0
/*
******** nrrdCrop()
**
** select some sub-volume inside a given nrrd, producing an output
** nrrd with the same dimensions, but with equal or smaller sizes
** along each axis.
*/
int
nrrdCrop(Nrrd *nout, const Nrrd *nin, size_t *min, size_t *max) {
  char me[]="nrrdCrop", func[] = "crop", err[BIFF_STRLEN],
    buff1[NRRD_DIM_MAX*30], buff2[AIR_STRLEN_SMALL];
  unsigned int ai;
  size_t I,
    lineSize,                /* #bytes in one scanline to be copied */
    typeSize,                /* size of data type */
    cIn[NRRD_DIM_MAX],       /* coords for line start, in input */
    cOut[NRRD_DIM_MAX],      /* coords for line start, in output */
    szIn[NRRD_DIM_MAX],
    szOut[NRRD_DIM_MAX],
    idxIn, idxOut,           /* linear indices for input and output */
    numLines;                /* number of scanlines in output nrrd */
  char *dataIn, *dataOut;

  /* errors */
  if (!(nout && nin && min && max)) {
    sprintf(err, "%s: got NULL pointer", me);
    biffAdd(NRRD, err); return 1;
  }
  if (nout == nin) {
    sprintf(err, "%s: nout==nin disallowed", me);
    biffAdd(NRRD, err); return 1;
  }
  for (ai=0; ai<nin->dim; ai++) {
    if (!(min[ai] <= max[ai])) {
      sprintf(err, "%s: axis %d min (" _AIR_SIZE_T_CNV 
              ") not <= max (" _AIR_SIZE_T_CNV ")", 
              me, ai, min[ai], max[ai]);
      biffAdd(NRRD, err); return 1;
    }
    if (!( min[ai] < nin->axis[ai].size && max[ai] < nin->axis[ai].size )) {
      sprintf(err, "%s: axis %d min (" _AIR_SIZE_T_CNV  
              ") or max (" _AIR_SIZE_T_CNV  ") out of bounds [0," 
              _AIR_SIZE_T_CNV  "]",
              me, ai, min[ai], max[ai], nin->axis[ai].size-1);
      biffAdd(NRRD, err); return 1;
    }
  }
  /* this shouldn't actually be necessary .. */
  if (!nrrdElementSize(nin)) {
    sprintf(err, "%s: nrrd reports zero element size!", me);
    biffAdd(NRRD, err); return 1;
  }

  /* allocate */
  nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, szIn);
  numLines = 1;
  for (ai=0; ai<nin->dim; ai++) {
    szOut[ai] = max[ai] - min[ai] + 1;
    if (ai) {
      numLines *= szOut[ai];
    }
  }
  nout->blockSize = nin->blockSize;
  if (nrrdMaybeAlloc_nva(nout, nin->type, nin->dim, szOut)) {
    sprintf(err, "%s:", me);
    biffAdd(NRRD, err); return 1;
  }
  lineSize = szOut[0]*nrrdElementSize(nin);
  
  /* the skinny */
  typeSize = nrrdElementSize(nin);
  dataIn = (char *)nin->data;
  dataOut = (char *)nout->data;
  memset(cOut, 0, NRRD_DIM_MAX*sizeof(unsigned int));
  /*
  printf("!%s: nin->dim = %d\n", me, nin->dim);
  printf("!%s: min  = %d %d %d\n", me, min[0], min[1], min[2]);
  printf("!%s: szIn = %d %d %d\n", me, szIn[0], szIn[1], szIn[2]);
  printf("!%s: szOut = %d %d %d\n", me, szOut[0], szOut[1], szOut[2]);
  printf("!%s: lineSize = %d\n", me, lineSize);
  printf("!%s: typeSize = %d\n", me, typeSize);
  printf("!%s: numLines = %d\n", me, (int)numLines);
  */
  for (I=0; I<numLines; I++) {
    for (ai=0; ai<nin->dim; ai++) {
      cIn[ai] = cOut[ai] + min[ai];
    }
    NRRD_INDEX_GEN(idxOut, cOut, szOut, nin->dim);
    NRRD_INDEX_GEN(idxIn, cIn, szIn, nin->dim);
    /*
    printf("!%s: %5d: cOut=(%3d,%3d,%3d) --> idxOut = %5d\n",
           me, (int)I, cOut[0], cOut[1], cOut[2], (int)idxOut);
    printf("!%s: %5d:  cIn=(%3d,%3d,%3d) -->  idxIn = %5d\n",
           me, (int)I, cIn[0], cIn[1], cIn[2], (int)idxIn);
    */
    memcpy(dataOut + idxOut*typeSize, dataIn + idxIn*typeSize, lineSize);
    /* the lowest coordinate in cOut[] will stay zero, since we are 
       copying one (1-D) scanline at a time */
    NRRD_COORD_INCR(cOut, szOut, nin->dim, 1);
  }
  if (nrrdAxisInfoCopy(nout, nin, NULL, (NRRD_AXIS_INFO_SIZE_BIT |
                                         NRRD_AXIS_INFO_MIN_BIT |
                                         NRRD_AXIS_INFO_MAX_BIT ))) {
    sprintf(err, "%s:", me);
    biffAdd(NRRD, err); return 1;
  }
  for (ai=0; ai<nin->dim; ai++) {
    nrrdAxisInfoPosRange(&(nout->axis[ai].min), &(nout->axis[ai].max),
                         nin, ai, min[ai], max[ai]);
    /* do the safe thing first */
    nout->axis[ai].kind = _nrrdKindAltered(nin->axis[ai].kind, AIR_FALSE);
    /* try cleverness */
    if (!nrrdStateKindNoop) {
      if (nout->axis[ai].size == nin->axis[ai].size) {
        /* we can safely copy kind; the samples didn't change */
        nout->axis[ai].kind = nin->axis[ai].kind;
      } else if (nrrdKind4Color == nin->axis[ai].kind
                 && 3 == szOut[ai]) {
        nout->axis[ai].kind = nrrdKind3Color;
      } else if (nrrdKind4Vector == nin->axis[ai].kind
                 && 3 == szOut[ai]) {
        nout->axis[ai].kind = nrrdKind3Vector;
      } else if ((nrrdKind4Vector == nin->axis[ai].kind
                  || nrrdKind3Vector == nin->axis[ai].kind)
                 && 2 == szOut[ai]) {
        nout->axis[ai].kind = nrrdKind2Vector;
      } else if (nrrdKindRGBAColor == nin->axis[ai].kind
                 && 0 == min[ai]
                 && 2 == max[ai]) {
        nout->axis[ai].kind = nrrdKindRGBColor;
      } else if (nrrdKind2DMaskedSymMatrix == nin->axis[ai].kind
                 && 1 == min[ai]
                 && max[ai] == szIn[ai]-1) {
        nout->axis[ai].kind = nrrdKind2DSymMatrix;
      } else if (nrrdKind2DMaskedMatrix == nin->axis[ai].kind
                 && 1 == min[ai]
                 && max[ai] == szIn[ai]-1) {
        nout->axis[ai].kind = nrrdKind2DMatrix;
      } else if (nrrdKind3DMaskedSymMatrix == nin->axis[ai].kind
                 && 1 == min[ai]
                 && max[ai] == szIn[ai]-1) {
        nout->axis[ai].kind = nrrdKind3DSymMatrix;
      } else if (nrrdKind3DMaskedMatrix == nin->axis[ai].kind
                 && 1 == min[ai]
                 && max[ai] == szIn[ai]-1) {
        nout->axis[ai].kind = nrrdKind3DMatrix;
      }
    }
  }
  strcpy(buff1, "");
  for (ai=0; ai<nin->dim; ai++) {
    sprintf(buff2, "%s[" _AIR_SIZE_T_CNV  "," _AIR_SIZE_T_CNV  "]",
            (ai ? "x" : ""), min[ai], max[ai]);
    strcat(buff1, buff2);
  }
  if (nrrdContentSet_va(nout, func, nin, "%s", buff1)) {
    sprintf(err, "%s:", me);
    biffAdd(NRRD, err); return 1;
  }
  if (nrrdBasicInfoCopy(nout, nin,
                        NRRD_BASIC_INFO_DATA_BIT
                        | NRRD_BASIC_INFO_TYPE_BIT
                        | NRRD_BASIC_INFO_BLOCKSIZE_BIT
                        | NRRD_BASIC_INFO_DIMENSION_BIT
                        | NRRD_BASIC_INFO_SPACEORIGIN_BIT
                        | NRRD_BASIC_INFO_CONTENT_BIT
                        | NRRD_BASIC_INFO_COMMENTS_BIT
                        | (nrrdStateKeyValuePairsPropagate
                           ? 0
                           : NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) {
    sprintf(err, "%s:", me);
    biffAdd(NRRD, err); return 1;
  }
  /* copy origin, then shift it along the spatial axes */
  nrrdSpaceVecCopy(nout->spaceOrigin, nin->spaceOrigin);
  for (ai=0; ai<nin->dim; ai++) {
    if (AIR_EXISTS(nin->axis[ai].spaceDirection[0])) {
      nrrdSpaceVecScaleAdd2(nout->spaceOrigin,
                            1.0, nout->spaceOrigin,
                            min[ai], nin->axis[ai].spaceDirection);
    }
  }
                         

  return 0;
}
Пример #2
0
/*
******** nrrdSlice()
**
** slices a nrrd along a given axis, at a given position.
**
** This is a newer version of the procedure, which is simpler, faster,
** and requires less memory overhead than the first one.  It is based
** on the observation that any slice is a periodic square-wave pattern
** in the original data (viewed as a one- dimensional array).  The
** characteristics of that periodic pattern are how far from the
** beginning it starts (offset), the length of the "on" part (length),
** the period (period), and the number of periods (numper). 
*/
int
nrrdSlice(Nrrd *nout, const Nrrd *nin, unsigned int saxi, size_t pos) {
  char me[]="nrrdSlice", func[]="slice", err[BIFF_STRLEN];
  size_t 
    I, 
    rowLen,                  /* length of segment */
    colStep,                 /* distance between start of each segment */
    colLen,                  /* number of periods */
    szOut[NRRD_DIM_MAX];
  unsigned int ai, outdim;
  int map[NRRD_DIM_MAX];
  char *src, *dest;

  if (!(nin && nout)) {
    sprintf(err, "%s: got NULL pointer", me);
    biffAdd(NRRD, err); return 1;
  }
  if (nout == nin) {
    sprintf(err, "%s: nout==nin disallowed", me);
    biffAdd(NRRD, err); return 1;
  }
  if (1 == nin->dim) {
    sprintf(err, "%s: can't slice a 1-D nrrd; use nrrd{I,F,D}Lookup[]", me);
    biffAdd(NRRD, err); return 1;
  }
  if (!( saxi < nin->dim )) {
    sprintf(err, "%s: slice axis %d out of bounds (0 to %d)", 
            me, saxi, nin->dim-1);
    biffAdd(NRRD, err); return 1;
  }
  if (!( pos < nin->axis[saxi].size )) {
    sprintf(err, "%s: position " _AIR_SIZE_T_CNV 
            " out of bounds (0 to " _AIR_SIZE_T_CNV  ")", 
            me, pos, nin->axis[saxi].size-1);
    biffAdd(NRRD, err); return 1;
  }
  /* this shouldn't actually be necessary .. */
  if (!nrrdElementSize(nin)) {
    sprintf(err, "%s: nrrd reports zero element size!", me);
    biffAdd(NRRD, err); return 1;
  }

  /* set up control variables */
  rowLen = colLen = 1;
  for (ai=0; ai<nin->dim; ai++) {
    if (ai < saxi) {
      rowLen *= nin->axis[ai].size;
    } else if (ai > saxi) {
      colLen *= nin->axis[ai].size;
    }
  }
  rowLen *= nrrdElementSize(nin);
  colStep = rowLen*nin->axis[saxi].size;

  outdim = nin->dim-1;
  for (ai=0; ai<outdim; ai++) {
    map[ai] = ai + (ai >= saxi);
    szOut[ai] = nin->axis[map[ai]].size;
  }
  nout->blockSize = nin->blockSize;
  if (nrrdMaybeAlloc_nva(nout, nin->type, outdim, szOut)) {
    sprintf(err, "%s: failed to create slice", me);
    biffAdd(NRRD, err); return 1;
  }

  /* the skinny */
  src = (char *)nin->data;
  dest = (char *)nout->data;
  src += rowLen*pos;
  for (I=0; I<colLen; I++) {
    /* HEY: replace with AIR_MEMCPY() or similar, when applicable */
    memcpy(dest, src, rowLen);
    src += colStep;
    dest += rowLen;
  }

  /* copy the peripheral information */
  if (nrrdAxisInfoCopy(nout, nin, map, NRRD_AXIS_INFO_NONE)) {
    sprintf(err, "%s:", me);
    biffAdd(NRRD, err); return 1;
  }
  if (nrrdContentSet_va(nout, func, nin, "%d,%d", saxi, pos)) {
    sprintf(err, "%s:", me);
    biffAdd(NRRD, err); return 1;
  }
  if (nrrdBasicInfoCopy(nout, nin,
                        NRRD_BASIC_INFO_DATA_BIT
                        | NRRD_BASIC_INFO_TYPE_BIT
                        | NRRD_BASIC_INFO_BLOCKSIZE_BIT
                        | NRRD_BASIC_INFO_DIMENSION_BIT
                        | NRRD_BASIC_INFO_SPACEORIGIN_BIT
                        | NRRD_BASIC_INFO_CONTENT_BIT
                        | NRRD_BASIC_INFO_COMMENTS_BIT
                        | (nrrdStateKeyValuePairsPropagate
                           ? 0
                           : NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) {
    sprintf(err, "%s:", me);
    biffAdd(NRRD, err); return 1;
  }
  /* translate origin if this was a spatial axis, otherwise copy */
  /* note that if there is no spatial info at all, this is all harmless */
  if (AIR_EXISTS(nin->axis[saxi].spaceDirection[0])) {
    nrrdSpaceVecScaleAdd2(nout->spaceOrigin,
                          1.0, nin->spaceOrigin,
                          pos, nin->axis[saxi].spaceDirection);
  } else {
    nrrdSpaceVecCopy(nout->spaceOrigin, nin->spaceOrigin);
  }
  return 0;
}
Пример #3
0
int
unrrdu_dnormMain(int argc, const char **argv, const char *me,
                 hestParm *hparm) {
    char *outS;
    int pret;

    Nrrd *nin, *nout;
    NrrdIoState *nio;
    int kindIn, kindOut, headerOnly, haveMM, trivialOrient, recenter, gotmf;
    unsigned int kindAxis, axi, si, sj;
    double sscl;

    hestOpt *opt = NULL;
    char *err;
    airArray *mop;

    hestOptAdd(&opt, "h,header", NULL, airTypeInt, 0, 0, &headerOnly, NULL,
               "output header of nrrd file only, not the data itself");
    hestOptAdd(&opt, "to", NULL, airTypeInt, 0, 0, &trivialOrient, NULL,
               "(*t*rivial *o*rientation) "
               "even if the input nrrd comes with full orientation or "
               "per-axis min-max info, ignore it and instead assert the "
               "identity mapping between index and world space");
    hestOptAdd(&opt, "c,center", NULL, airTypeInt, 0, 0, &recenter, NULL,
               "re-locate output spaceOrigin so that field is centered "
               "around origin of space coordinates");
    hestOptAdd(&opt, "s,scaling", "scl", airTypeDouble, 1, 1, &sscl, "1.0",
               "when having to contrive orientation information and there's "
               "no per-axis min/max to inform what the sample spacing is, "
               "this is the sample spacing to assert");
    OPT_ADD_NIN(nin, "input image");
    OPT_ADD_NOUT(outS, "output filename");

    mop = airMopNew();
    airMopAdd(mop, opt, (airMopper)hestOptFree, airMopAlways);
    USAGE(_unrrdu_dnormInfoL);
    PARSE();
    airMopAdd(mop, opt, (airMopper)hestParseFree, airMopAlways);

    /* can't deal with block type */
    if (nrrdTypeBlock == nin->type) {
        fprintf(stderr, "%s: can only have scalar kinds (not %s)\n", me,
                airEnumStr(nrrdType, nrrdTypeBlock));
        airMopError(mop);
        exit(1);
    }

    /* make sure all kinds are set to something */
    /* see if there's a range kind, verify that there's only one */
    /* set haveMM */
    haveMM = AIR_TRUE;
    kindIn = nrrdKindUnknown;
    kindAxis = 0;
    for (axi=0; axi<nin->dim; axi++) {
        if (nrrdKindUnknown == nin->axis[axi].kind
                || nrrdKindIsDomain(nin->axis[axi].kind)) {
            haveMM &= AIR_EXISTS(nin->axis[axi].min);
            haveMM &= AIR_EXISTS(nin->axis[axi].max);
        } else {
            if (nrrdKindUnknown != kindIn) {
                fprintf(stderr, "%s: got non-domain kind %s on axis %u, but already "
                        "have %s from axis %u\n", me,
                        airEnumStr(nrrdKind, nin->axis[axi].kind), axi,
                        airEnumStr(nrrdKind, kindIn), kindAxis);
                airMopError(mop);
                exit(1);
            }
            kindIn = nin->axis[axi].kind;
            kindAxis = axi;
        }
    }
    /* see if the non-domain kind is something we can interpret as a tensor */
    if (nrrdKindUnknown != kindIn) {
        switch (kindIn) {
        /* ======= THESE are the kinds that we can possibly output ======= */
        case nrrdKind2Vector:
        case nrrdKind3Vector:
        case nrrdKind4Vector:
        case nrrdKind2DSymMatrix:
        case nrrdKind2DMatrix:
        case nrrdKind3DSymMatrix:
        case nrrdKind3DMatrix:
            /* =============================================================== */
            kindOut = kindIn;
            break;
        /* Some other kinds are mapped to those above */
        case nrrdKind3Color:
        case nrrdKindRGBColor:
            kindOut = nrrdKind3Vector;
            break;
        case nrrdKind4Color:
        case nrrdKindRGBAColor:
            kindOut = nrrdKind4Vector;
            break;
        default:
            fprintf(stderr, "%s: got non-conforming kind %s on axis %u\n", me,
                    airEnumStr(nrrdKind, kindIn), kindAxis);
            airMopError(mop);
            exit(1);
            break;
        }
    } else {
        /* kindIn is nrrdKindUnknown, so its a simple scalar image,
           and that's what the output will be too; kindOut == nrrdKindUnknown
           is used in the code below to say "its a scalar image" */
        kindOut = nrrdKindUnknown;
    }

    /* initialize output by copying */
    nout = nrrdNew();
    airMopAdd(mop, nout, (airMopper)nrrdNuke, airMopAlways);
    if (nrrdCopy(nout, nin)) {
        airMopAdd(mop, err = biffGet(NRRD), airFree, airMopAlways);
        fprintf(stderr, "%s: trouble copying:\n%s", me, err);
        airMopError(mop);
        exit(1);
    }

    /* no comments, either advertising the format URL or anything else */
    nio = nrrdIoStateNew();
    airMopAdd(mop, nio, (airMopper)nrrdIoStateNix, airMopAlways);
    nio->skipFormatURL = AIR_TRUE;
    if (headerOnly) {
        nio->skipData = AIR_TRUE;
    }
    nrrdCommentClear(nout);

    /* no measurement frame */
    gotmf = AIR_FALSE;
    for (si=0; si<NRRD_SPACE_DIM_MAX; si++) {
        for (sj=0; sj<NRRD_SPACE_DIM_MAX; sj++) {
            gotmf |= AIR_EXISTS(nout->measurementFrame[si][sj]);
        }
    }
    if (gotmf) {
        fprintf(stderr, "%s: WARNING: incoming array measurement frame; "
                "it will be erased on output.\n", me);
        /* airMopError(mop); exit(1); */
    }
    for (si=0; si<NRRD_SPACE_DIM_MAX; si++) {
        for (sj=0; sj<NRRD_SPACE_DIM_MAX; sj++) {
            nout->measurementFrame[si][sj] = AIR_NAN;
        }
    }

    /* no key/value pairs */
    nrrdKeyValueClear(nout);

    /* no content field */
    nout->content = airFree(nout->content);

    /* normalize domain kinds to "space" */
    /* turn off centers (Diderot assumes cell-centered, but that could
       probably stand to be tested and enforced more) */
    /* turn off thickness */
    /* turn off labels and units */
    for (axi=0; axi<nout->dim; axi++) {
        if (nrrdKindUnknown == kindOut) {
            nout->axis[axi].kind = nrrdKindSpace;
        } else {
            nout->axis[axi].kind = (kindAxis == axi
                                    ? kindOut
                                    : nrrdKindSpace);
        }
        nout->axis[axi].center = nrrdCenterUnknown;
        nout->axis[axi].thickness = AIR_NAN;
        nout->axis[axi].label = airFree(nout->axis[axi].label);
        nout->axis[axi].units = airFree(nout->axis[axi].units);
        nout->axis[axi].min = AIR_NAN;
        nout->axis[axi].max = AIR_NAN;
        nout->axis[axi].spacing = AIR_NAN;
    }

    /* logic of orientation definition:
       If space dimension is known:
          set origin to zero if not already set
          set space direction to unit vector if not already set
       Else if have per-axis min and max:
          set spae origin and directions to communicate same intent
          as original per-axis min and max and original centering
       Else
          set origin to zero and all space directions to units.
       It might be nice to use gage's logic for mapping from world to index,
       but we have to accept a greater variety of kinds and dimensions
       than gage ever has to process.
    */
    if (nout->spaceDim && !trivialOrient) {
        int saxi = 0;
        /* we use only the space dimension, not any named space */
        nout->space = nrrdSpaceUnknown;
        if (!nrrdSpaceVecExists(nout->spaceDim, nout->spaceOrigin)) {
            nrrdSpaceVecSetZero(nout->spaceOrigin);
        }
        for (axi=0; axi<nout->dim; axi++) {
            if (nrrdKindUnknown == kindOut || kindAxis != axi) {
                /* its a domain axis of output */
                if (!nrrdSpaceVecExists(nout->spaceDim,
                                        nout->axis[axi].spaceDirection)) {
                    nrrdSpaceVecSetZero(nout->axis[axi].spaceDirection);
                    nout->axis[axi].spaceDirection[saxi] = sscl;
                }
                /* else we leave existing space vector as is */
                saxi++;
            } else {
                /* else its a range axis */
                nrrdSpaceVecSetNaN(nout->axis[axi].spaceDirection);
            }
        }
    } else if (haveMM && !trivialOrient) {
        int saxi = 0;
        size_t N;
        double rng;
        for (axi=0; axi<nout->dim; axi++) {
            if (nrrdKindUnknown == kindOut || kindAxis != axi) {
                /* its a domain axis of output */
                nrrdSpaceVecSetZero(nout->axis[axi].spaceDirection);
                rng = nin->axis[axi].max - nin->axis[axi].min;
                if (nrrdCenterNode == nin->axis[axi].center) {
                    nout->spaceOrigin[saxi] = nin->axis[axi].min;
                    N = nin->axis[axi].size;
                    nout->axis[axi].spaceDirection[saxi] = rng/(N-1);
                } else {
                    /* unknown centering treated as cell */
                    N = nin->axis[axi].size;
                    nout->spaceOrigin[saxi] = nin->axis[axi].min + (rng/N)/2;
                    nout->axis[axi].spaceDirection[saxi] = rng/N;
                }
                saxi++;
            } else {
                /* else its a range axis */
                nrrdSpaceVecSetNaN(nout->axis[axi].spaceDirection);
            }
        }
        nout->spaceDim = saxi;
    } else {
        /* either trivialOrient, or, not spaceDim and not haveMM */
        int saxi = 0;
        nout->space = nrrdSpaceUnknown;
        nrrdSpaceVecSetZero(nout->spaceOrigin);
        for (axi=0; axi<nout->dim; axi++) {
            if (nrrdKindUnknown == kindOut || kindAxis != axi) {
                /* its a domain axis of output */
                nrrdSpaceVecSetZero(nout->axis[axi].spaceDirection);
                nout->axis[axi].spaceDirection[saxi]
                    = (AIR_EXISTS(nin->axis[axi].spacing)
                       ? nin->axis[axi].spacing
                       : sscl);
                saxi++;
            } else {
                /* else its a range axis */
                nrrdSpaceVecSetNaN(nout->axis[axi].spaceDirection);
            }
        }
        nout->spaceDim = saxi;
    }

    /* space dimension has to match the number of domain axes */
    if (nout->dim != nout->spaceDim + !!kindOut) {
        fprintf(stderr, "%s: output dim %d != spaceDim %d + %d %s%s%s\n",
                me, nout->dim, nout->spaceDim, !!kindOut,
                kindOut ? "for non-scalar (" : "(scalar data)",
                kindOut ? airEnumStr(nrrdKind, kindOut) : "",
                kindOut ? ") data" : "");
        airMopError(mop);
        exit(1);
    }

    if (recenter) {
        /* sets field's origin so field is centered on the origin. capiche? */
        /* this code was tacked on later than the stuff above, so its
           logic could probably be moved up there, but it seems cleaner to
           have it as a separate post-process */
        double mean[NRRD_SPACE_DIM_MAX];
        nrrdSpaceVecSetZero(mean);
        for (axi=0; axi<nout->dim; axi++) {
            if (nrrdKindUnknown == kindOut || kindAxis != axi) {
                nrrdSpaceVecScaleAdd2(mean, 1.0, mean,
                                      0.5*(nout->axis[axi].size - 1),
                                      nout->axis[axi].spaceDirection);
            }
        }
        nrrdSpaceVecScaleAdd2(mean, 1.0, mean,
                              1.0, nout->spaceOrigin);
        /* now mean is the center of the field */
        nrrdSpaceVecScaleAdd2(nout->spaceOrigin,
                              1.0, nout->spaceOrigin,
                              -1.0, mean);
    }

    if (nrrdSave(outS, nout, nio)) {
        airMopAdd(mop, err = biffGet(NRRD), airFree, airMopAlways);
        fprintf(stderr, "%s: trouble saving \"%s\":\n%s",
                me, outS, err);
        airMopError(mop);
        exit(1);
    }

    airMopOkay(mop);
    return 0;
}