int nrrdApplyMulti1DRegMap(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, const Nrrd *nmmap, int typeOut, int rescale) { char me[]="nrrdApplyMulti1DRegMap", err[BIFF_STRLEN]; NrrdRange *range; airArray *mop; if (!(nout && nmmap && nin)) { sprintf(err, "%s: got NULL pointer", me); biffAdd(NRRD, err); return 1; } mop = airMopNew(); if (_range) { range = nrrdRangeCopy(_range); nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); } else { range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); } airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); if (_nrrdApply1DSetUp(nout, nin, range, nmmap, kindRmap, typeOut, rescale, AIR_TRUE) || _nrrdApply1DLutOrRegMap(nout, nin, range, nmmap, AIR_TRUE, rescale, AIR_TRUE)) { sprintf(err, "%s:", me); biffAdd(NRRD, err); airMopError(mop); return 1; } airMopOkay(mop); return 0; }
/* ******** nrrdApply1DIrregMap() ** ** Linear interpolation between irregularly spaced control points. ** Obviously, the location of the control point has to be given ** explicitly. The map nrrd must have dimension 2, and each ** control point is represented by a scanline along axis 0. The ** first value is the position of the control point, and the remaining ** value(s) are linearly weighted according to the position of the ** input value among the control point locations. ** ** To allow "coloring" of non-existant values -inf, NaN, and +inf, if ** the very first value of the map (the location of the first control ** point) is non-existant, then the first three control point locations ** must be -inf, NaN, and +inf, in that order, and the information ** about these points will be used for corresponding input values. ** Doing this makes everything slower, however, because airFPClass_f() ** is called on every single value. ** ** This assumes that nrrd1DIrregMapCheck has been called on "nmap", ** and that nrrd1DIrregAclCheck has been called on "nacl" (if it is ** non-NULL). */ int nrrdApply1DIrregMap(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, const Nrrd *nmap, const Nrrd *nacl, int typeOut, int rescale) { char me[]="nrrdApply1DIrregMap", err[BIFF_STRLEN]; size_t N, I; int i, *acl, entLen, posLen, aclLen, mapIdx, aclIdx, entSize, colSize, inSize, lo, hi, baseI; double val, *pos, domMin, domMax, mapIdxFrac, (*mapLup)(const void *v, size_t I), (*inLoad)(const void *v), (*outInsert)(void *v, size_t I, double d); char *inData, *outData, *entData0, *entData1; NrrdRange *range; airArray *mop; if (!(nout && nmap && nin)) { sprintf(err, "%s: got NULL pointer", me); biffAdd(NRRD, err); return 1; } mop = airMopNew(); if (_range) { range = nrrdRangeCopy(_range); nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); } else { range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); } airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); if (_nrrdApply1DSetUp(nout, nin, range, nmap, kindImap, typeOut, rescale, AIR_FALSE)) { sprintf(err, "%s:", me); biffAdd(NRRD, err); airMopError(mop); return 1; } if (nacl && nrrd1DIrregAclCheck(nacl)) { sprintf(err, "%s: given acl isn't valid", me); biffAdd(NRRD, err); airMopError(mop); return 1; } if (nacl) { acl = (int *)nacl->data; aclLen = nacl->axis[1].size; } else { acl = NULL; aclLen = 0; } pos = _nrrd1DIrregMapDomain(&posLen, &baseI, nmap); if (!pos) { sprintf(err, "%s: couldn't determine domain", me); biffAdd(NRRD, err); airMopError(mop); return 1; } airMopAdd(mop, pos, airFree, airMopAlways); mapLup = nrrdDLookup[nmap->type]; inData = (char *)nin->data; inLoad = nrrdDLoad[nin->type]; inSize = nrrdElementSize(nin); mapLup = nrrdDLookup[nmap->type]; entLen = nmap->axis[0].size; /* entLen is really 1 + entry length */ entSize = entLen*nrrdElementSize(nmap); colSize = (entLen-1)*nrrdTypeSize[typeOut]; outData = (char *)nout->data; outInsert = nrrdDInsert[nout->type]; domMin = pos[0]; domMax = pos[posLen-1]; N = nrrdElementNumber(nin); for (I=0; I<N; I++, inData += inSize, outData += colSize) { val = inLoad(inData); /* _VV = ( (AIR_EXISTS(val) && (21 == (int)(-val))) || 22400 < I ); */ /* if (_VV) fprintf(stderr, "##%s: (%d) val = % 31.15f\n", me, (int)I, val); */ if (!AIR_EXISTS(val)) { /* got a non-existant value */ if (baseI) { /* and we know how to deal with them */ switch (airFPClass_d(val)) { case airFP_NEG_INF: mapIdx = 0; break; case airFP_SNAN: case airFP_QNAN: mapIdx = 1; break; case airFP_POS_INF: mapIdx = 2; break; default: mapIdx = 0; fprintf(stderr, "%s: PANIC: non-existant value/class %g/%d " "not handled\n", me, val, airFPClass_d(val)); exit(1); } entData0 = (char*)(nmap->data) + mapIdx*entSize; for (i=1; i<entLen; i++) { outInsert(outData, i-1, mapLup(entData0, i)); } continue; /* we're done! (with this value) */ } else { /* we don't know how to properly deal with this non-existant value: we use the first entry, and then fall through to code below */ mapIdx = 0; mapIdxFrac = 0.0; } } else { /* we have an existant value */ if (rescale) { val = AIR_AFFINE(range->min, val, range->max, domMin, domMax); /* if (_VV) fprintf(stderr, " rescaled --> % 31.15f\n", val); */ } val = AIR_CLAMP(domMin, val, domMax); if (acl) { aclIdx = airIndex(domMin, val, domMax, aclLen); lo = acl[0 + 2*aclIdx]; hi = acl[1 + 2*aclIdx]; } else { lo = 0; hi = posLen-2; } if (lo < hi) { mapIdx = _nrrd1DIrregFindInterval(pos, val, lo, hi); } else { /* acl did its job ==> lo == hi */ mapIdx = lo; } } mapIdxFrac = AIR_AFFINE(pos[mapIdx], val, pos[mapIdx+1], 0.0, 1.0); /* if (_VV) fprintf(stderr, "##%s: val=\n% 31.15f --> " "mapIdx,frac = %d,\n% 31.15f\n", me, val, mapIdx, mapIdxFrac); */ entData0 = (char*)(nmap->data) + (baseI+mapIdx)*entSize; entData1 = (char*)(nmap->data) + (baseI+mapIdx+1)*entSize; /* if (_VV) fprintf(stderr, "##%s: 2; %d/\n% 31.15f --> entLen=%d " "baseI=%d -->\n", me, mapIdx, mapIdxFrac, entLen, baseI); */ for (i=1; i<entLen; i++) { val = ((1-mapIdxFrac)*mapLup(entData0, i) + mapIdxFrac*mapLup(entData1, i)); /* if (_VV) fprintf(stderr, "% 31.15f\n", val); */ outInsert(outData, i-1, val); } /* if (_VV) fprintf(stderr, "##%s: 3\n", me); */ } airMopOkay(mop); return 0; }
/* ******** nrrdHistoAxis ** ** replace scanlines along one scanline with a histogram of the scanline ** ** this looks at nin->min and nin->max to see if they are both non-NaN. ** If so, it uses these as the range of the histogram, otherwise it ** finds the min and max present in the volume ** ** By its very nature, and by the simplicity of this implemention, ** this can be a slow process due to terrible memory locality. User ** may want to permute axes before and after this, but that can be ** slow too... */ int nrrdHistoAxis(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, unsigned int hax, size_t bins, int type) { static const char me[]="nrrdHistoAxis", func[]="histax"; int map[NRRD_DIM_MAX]; unsigned int ai, hidx; size_t szIn[NRRD_DIM_MAX], szOut[NRRD_DIM_MAX], size[NRRD_DIM_MAX], coordIn[NRRD_DIM_MAX], coordOut[NRRD_DIM_MAX]; size_t I, hI, num; double val, count; airArray *mop; NrrdRange *range; if (!(nin && nout)) { biffAddf(NRRD, "%s: got NULL pointer", me); return 1; } if (nout == nin) { biffAddf(NRRD, "%s: nout==nin disallowed", me); return 1; } if (!(bins > 0)) { biffAddf(NRRD, "%s: bins value (" _AIR_SIZE_T_CNV ") invalid", me, bins); return 1; } if (airEnumValCheck(nrrdType, type) || nrrdTypeBlock == type) { biffAddf(NRRD, "%s: invalid nrrd type %d", me, type); return 1; } if (!( hax <= nin->dim-1 )) { biffAddf(NRRD, "%s: axis %d is not in range [0,%d]", me, hax, nin->dim-1); return 1; } mop = airMopNew(); if (_range) { range = nrrdRangeCopy(_range); nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); } else { range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); } airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, size); size[hax] = bins; if (nrrdMaybeAlloc_nva(nout, type, nin->dim, size)) { biffAddf(NRRD, "%s: failed to alloc output nrrd", me); airMopError(mop); return 1; } /* copy axis information */ for (ai=0; ai<nin->dim; ai++) { map[ai] = ai != hax ? (int)ai : -1; } nrrdAxisInfoCopy(nout, nin, map, NRRD_AXIS_INFO_NONE); /* axis hax now has to be set manually */ nout->axis[hax].size = bins; nout->axis[hax].spacing = AIR_NAN; /* min and max convey the information */ nout->axis[hax].thickness = AIR_NAN; nout->axis[hax].min = range->min; nout->axis[hax].max = range->max; nout->axis[hax].center = nrrdCenterCell; if (nin->axis[hax].label) { nout->axis[hax].label = (char *)calloc(strlen("histax()") + strlen(nin->axis[hax].label) + 1, sizeof(char)); if (nout->axis[hax].label) { sprintf(nout->axis[hax].label, "histax(%s)", nin->axis[hax].label); } else { biffAddf(NRRD, "%s: couldn't allocate output label", me); airMopError(mop); return 1; } } else { nout->axis[hax].label = NULL; } if (!nrrdStateKindNoop) { nout->axis[hax].kind = nrrdKindDomain; } /* the skinny: we traverse the input samples in linear order, and increment the bin in the histogram for the scanline we're in. This is not terribly clever, and the memory locality is a disaster */ nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, szIn); nrrdAxisInfoGet_nva(nout, nrrdAxisInfoSize, szOut); memset(coordIn, 0, NRRD_DIM_MAX*sizeof(size_t)); num = nrrdElementNumber(nin); for (I=0; I<num; I++) { /* get input nrrd value and compute its histogram index */ val = nrrdDLookup[nin->type](nin->data, I); if (AIR_EXISTS(val) && AIR_IN_CL(range->min, val, range->max)) { hidx = airIndex(range->min, val, range->max, AIR_CAST(unsigned int, bins)); memcpy(coordOut, coordIn, nin->dim*sizeof(size_t)); coordOut[hax] = (unsigned int)hidx; NRRD_INDEX_GEN(hI, coordOut, szOut, nout->dim); count = nrrdDLookup[nout->type](nout->data, hI); count = nrrdDClamp[nout->type](count + 1); nrrdDInsert[nout->type](nout->data, hI, count); } NRRD_COORD_INCR(coordIn, szIn, nin->dim, 0); }
/* ******** nrrdHisto() ** ** makes a 1D histogram of a given size and type ** ** pre-NrrdRange policy: ** this looks at nin->min and nin->max to see if they are both non-NaN. ** If so, it uses these as the range of the histogram, otherwise it ** finds the min and max present in the volume. If nin->min and nin->max ** are being used as the histogram range, then values which fall outside ** this are ignored (they don't contribute to the histogram). ** ** post-NrrdRange policy: */ int nrrdHisto(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, const Nrrd *nwght, size_t bins, int type) { static const char me[]="nrrdHisto", func[]="histo"; size_t I, num, idx; airArray *mop; NrrdRange *range; double min, max, eps, val, count, incr, (*lup)(const void *v, size_t I); if (!(nin && nout)) { /* _range and nwght can be NULL */ biffAddf(NRRD, "%s: got NULL pointer", me); return 1; } if (nout == nin) { biffAddf(NRRD, "%s: nout==nin disallowed", me); return 1; } if (!(bins > 0)) { biffAddf(NRRD, "%s: bins value (" _AIR_SIZE_T_CNV ") invalid", me, bins); return 1; } if (airEnumValCheck(nrrdType, type) || nrrdTypeBlock == type) { biffAddf(NRRD, "%s: invalid nrrd type %d", me, type); return 1; } if (nwght) { if (nout==nwght) { biffAddf(NRRD, "%s: nout==nwght disallowed", me); return 1; } if (nrrdTypeBlock == nwght->type) { biffAddf(NRRD, "%s: nwght type %s invalid", me, airEnumStr(nrrdType, nrrdTypeBlock)); return 1; } if (!nrrdSameSize(nin, nwght, AIR_TRUE)) { biffAddf(NRRD, "%s: nwght size mismatch with nin", me); return 1; } lup = nrrdDLookup[nwght->type]; } else { lup = NULL; } if (nrrdMaybeAlloc_va(nout, type, 1, bins)) { biffAddf(NRRD, "%s: failed to alloc histo array (len " _AIR_SIZE_T_CNV ")", me, bins); return 1; } mop = airMopNew(); /* nout->axis[0].size set */ nout->axis[0].spacing = AIR_NAN; nout->axis[0].thickness = AIR_NAN; if (nout && AIR_EXISTS(nout->axis[0].min) && AIR_EXISTS(nout->axis[0].max)) { /* HEY: total hack to externally nail down min and max of histogram: use the min and max already set on axis[0] */ /* HEY: shouldn't this blatent hack be further restricted by also checking the existence of range->min and range->max ? */ min = nout->axis[0].min; max = nout->axis[0].max; } else { if (_range) { range = nrrdRangeCopy(_range); nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); } else { range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); } airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); min = range->min; max = range->max; nout->axis[0].min = min; nout->axis[0].max = max; } eps = (min == max ? 1.0 : 0.0); nout->axis[0].center = nrrdCenterCell; /* nout->axis[0].label set below */ /* make histogram */ num = nrrdElementNumber(nin); for (I=0; I<num; I++) { val = nrrdDLookup[nin->type](nin->data, I); if (AIR_EXISTS(val)) { if (val < min || val > max+eps) { /* value is outside range; ignore it */ continue; } if (AIR_IN_CL(min, val, max)) { idx = airIndex(min, val, max+eps, AIR_CAST(unsigned int, bins)); /* printf("!%s: %d: index(%g, %g, %g, %d) = %d\n", me, (int)I, min, val, max, bins, idx); */ /* count is a double in order to simplify clamping the hit values to the representable range for nout->type */ count = nrrdDLookup[nout->type](nout->data, idx); incr = nwght ? lup(nwght->data, I) : 1; count = nrrdDClamp[nout->type](count + incr); nrrdDInsert[nout->type](nout->data, idx, count); } } } if (nrrdContentSet_va(nout, func, nin, "%d", bins)) { biffAddf(NRRD, "%s:", me); airMopError(mop); return 1; } nout->axis[0].label = (char *)airFree(nout->axis[0].label); nout->axis[0].label = (char *)airStrdup(nout->content); if (!nrrdStateKindNoop) { nout->axis[0].kind = nrrdKindDomain; } airMopOkay(mop); return 0; }
/* ******** nrrdArithGamma() ** ** map the values in a nrrd through a power function; essentially: ** val = pow(val, 1/gamma), but this is after the val has been normalized ** to be in the range of 0.0 to 1.0 (assuming that the given min and ** max really are the full range of the values in the nrrd). Thus, ** the given min and max values are fixed points of this ** transformation. Using a negative gamma means that after the pow() ** function has been applied, the value is inverted with respect to ** min and max (like in xv). */ int nrrdArithGamma(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, double Gamma) { static const char me[]="nrrdArithGamma", func[]="gamma"; double val, min, max; size_t I, num; NrrdRange *range; airArray *mop; double (*lup)(const void *, size_t); double (*ins)(void *, size_t, double); if (!(nout && nin)) { /* _range can be NULL */ biffAddf(NRRD, "%s: got NULL pointer", me); return 1; } if (!( AIR_EXISTS(Gamma) )) { biffAddf(NRRD, "%s: gamma doesn't exist", me); return 1; } if (!( nrrdTypeBlock != nin->type && nrrdTypeBlock != nout->type )) { biffAddf(NRRD, "%s: can't deal with %s type", me, airEnumStr(nrrdType, nrrdTypeBlock)); return 1; } if (nout != nin) { if (nrrdCopy(nout, nin)) { biffAddf(NRRD, "%s: couldn't initialize by copy to output", me); return 1; } } mop = airMopNew(); if (_range) { range = nrrdRangeCopy(_range); nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); } else { range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeTrue); } airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); min = range->min; max = range->max; if (min == max) { /* this is stupid. We want min < max to avoid making NaNs */ max += 1; } lup = nrrdDLookup[nin->type]; ins = nrrdDInsert[nout->type]; Gamma = 1/Gamma; num = nrrdElementNumber(nin); if (Gamma < 0.0) { Gamma = -Gamma; for (I=0; I<num; I++) { val = lup(nin->data, I); val = AIR_AFFINE(min, val, max, 0.0, 1.0); val = pow(val, Gamma); val = AIR_AFFINE(1.0, val, 0.0, min, max); ins(nout->data, I, val); } } else { for (I=0; I<num; I++) { val = lup(nin->data, I); val = AIR_AFFINE(min, val, max, 0.0, 1.0); val = pow(val, Gamma); val = AIR_AFFINE(0.0, val, 1.0, min, max); ins(nout->data, I, val); } } if (nrrdContentSet_va(nout, func, nin, "%g,%g,%g", min, max, Gamma)) { biffAddf(NRRD, "%s:", me); airMopError(mop); return 1; } if (nout != nin) { nrrdAxisInfoCopy(nout, nin, NULL, NRRD_AXIS_INFO_NONE); } /* basic info handled by nrrdCopy above */ airMopOkay(mop); return 0; }