GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/nrrd/apply1D.c Lines: 0 471 0.0 %
Date: 2017-05-26 Branches: 0 267 0.0 %

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 "nrrd.h"
25
#include "privateNrrd.h"
26
27
/*
28
** learned: even when using doubles, because of limited floating point
29
** precision, you can get different results between quantizing
30
** unrescaled (value directly from nrrd, map domain set to nrrd range,
31
** as with early behavior of unu rmap) and rescaled (value from nrrd
32
** scaled to fit in existing map domain, as with unu imap -r) value,
33
** to the exact same index space.
34
*/
35
36
/*
37
** I won't try to support mapping individual values through a
38
** colormap, as with a function evaluation on a single passed value.
39
** That will be handled in an upcoming library...
40
*/
41
42
/*
43
** this identifies the different kinds of 1D maps, useful for the
44
** functions in this file only
45
*/
46
enum {
47
  kindLut=0,
48
  kindRmap=1,
49
  kindImap=2
50
};
51
52
double
53
_nrrdApplyDomainMin(const Nrrd *nmap, int ramps, int mapAxis) {
54
  double ret;
55
56
  AIR_UNUSED(ramps);
57
  ret = nmap->axis[mapAxis].min;
58
  ret = AIR_EXISTS(ret) ? ret : 0;
59
  return ret;
60
}
61
62
double
63
_nrrdApplyDomainMax(const Nrrd *nmap, int ramps, int mapAxis) {
64
  double ret;
65
66
  ret = nmap->axis[mapAxis].max;
67
  if (!AIR_EXISTS(ret)) {
68
    ret = AIR_CAST(double, nmap->axis[mapAxis].size);
69
    ret = ramps ? ret-1 : ret;
70
  }
71
  return ret;
72
}
73
74
/*
75
** _nrrdApply1DSetUp()
76
**
77
** some error checking and initializing needed for 1D LUTS, regular,
78
** and irregular maps.  The intent is that if this succeeds, then
79
** there is no need for any further error checking.
80
**
81
** The only thing this function DOES is allocate the output nrrd, and
82
** set meta information.  The rest is just error checking.
83
**
84
** The given NrrdRange has to be fleshed out by the caller: it can't
85
** be NULL, and both range->min and range->max must exist.
86
*/
87
int
88
_nrrdApply1DSetUp(Nrrd *nout, const Nrrd *nin, const NrrdRange *range,
89
                  const Nrrd *nmap, int kind, int typeOut,
90
                  int rescale, int multi) {
91
  static const char me[]="_nrrdApply1DSetUp";
92
  char *mapcnt;
93
  char nounStr[][AIR_STRLEN_SMALL]={"lut",
94
                                    "regular map",
95
                                    "irregular map"};
96
  char mnounStr[][AIR_STRLEN_SMALL]={"multi lut",
97
                                     "multi regular map",
98
                                     "multi irregular map"};
99
                                      /* wishful thinking */
100
  char verbStr[][AIR_STRLEN_SMALL]={"lut",
101
                                    "rmap",
102
                                    "imap"};
103
  char mverbStr[][AIR_STRLEN_SMALL]={"mlut",
104
                                     "mrmap",
105
                                     "mimap"}; /* wishful thinking */
106
  int mapAxis, copyMapAxis0=AIR_FALSE, axisMap[NRRD_DIM_MAX];
107
  unsigned int ax, dim, entLen;
108
  size_t size[NRRD_DIM_MAX];
109
  double domMin, domMax;
110
111
  if (nout == nin) {
112
    biffAddf(NRRD, "%s: due to laziness, nout==nin always disallowed", me);
113
    return 1;
114
  }
115
  if (airEnumValCheck(nrrdType, typeOut)) {
116
    biffAddf(NRRD, "%s: invalid requested output type %d", me, typeOut);
117
    return 1;
118
  }
119
  if (nrrdTypeBlock == nin->type || nrrdTypeBlock == typeOut) {
120
    biffAddf(NRRD, "%s: input or requested output type is %s, need scalar",
121
             me, airEnumStr(nrrdType, nrrdTypeBlock));
122
    return 1;
123
  }
124
  if (rescale) {
125
    if (!range) {
126
      biffAddf(NRRD, "%s: want rescaling but didn't get a range", me);
127
      return 1;
128
    }
129
    if (!( AIR_EXISTS(range->min) && AIR_EXISTS(range->max) )) {
130
      biffAddf(NRRD, "%s: want rescaling but not both "
131
               "range->{min,max} %g %g exist", me, range->min, range->max);
132
      return 1;
133
    }
134
    /* HEY: consider adding an error check for range->min == range->max
135
       here; the code below now guards
136
       AIR_AFFINE(range->min, val, range->max, domMin, domMax)
137
       against producing NaNs, but maybe that's too clever */
138
  }
139
  if (kindLut == kind || kindRmap == kind) {
140
    if (!multi) {
141
      mapAxis = nmap->dim - 1;
142
      if (!(0 == mapAxis || 1 == mapAxis)) {
143
        biffAddf(NRRD, "%s: dimension of %s should be 1 or 2, not %d",
144
                 me, nounStr[kind], nmap->dim);
145
        return 1;
146
      }
147
      copyMapAxis0 = (1 == mapAxis);
148
    } else {
149
      mapAxis = nmap->dim - nin->dim - 1;
150
      if (!(0 == mapAxis || 1 == mapAxis)) {
151
        biffAddf(NRRD, "%s: dimension of %s should be %d or %d, not %d",
152
                 me, mnounStr[kind],
153
                 nin->dim + 1, nin->dim + 2, nmap->dim);
154
        return 1;
155
      }
156
      copyMapAxis0 = (1 == mapAxis);
157
      /* need to make sure the relevant sizes match */
158
      for (ax=0; ax<nin->dim; ax++) {
159
        unsigned int taxi = mapAxis + 1 + ax;
160
        if (taxi > NRRD_DIM_MAX-1) {
161
          biffAddf(NRRD, "%s: test axis index %u exceeds NRRD_DIM_MAX-1 %u",
162
                   me, taxi, NRRD_DIM_MAX-1);
163
          return 1;
164
        }
165
        if (nin->axis[ax].size != nmap->axis[taxi].size) {
166
          char stmp1[AIR_STRLEN_SMALL], stmp2[AIR_STRLEN_SMALL];
167
          biffAddf(NRRD, "%s: input and mmap don't have compatible sizes: "
168
                   "nin->axis[%d].size (%s) "
169
                   "!= nmap->axis[%d].size (%s): ", me,
170
                   ax, airSprintSize_t(stmp1, nin->axis[ax].size),
171
                   mapAxis + 1 + ax,
172
                   airSprintSize_t(stmp2, nmap->axis[taxi].size));
173
          return 1;
174
        }
175
      }
176
    }
177
    domMin = _nrrdApplyDomainMin(nmap, AIR_FALSE, mapAxis);
178
    domMax = _nrrdApplyDomainMax(nmap, AIR_FALSE, mapAxis);
179
    if (!( domMin < domMax )) {
180
      biffAddf(NRRD, "%s: (axis %d) domain min (%g) not less than max (%g)",
181
               me, mapAxis, domMin, domMax);
182
      return 1;
183
    }
184
    if (nrrdHasNonExist(nmap)) {
185
      biffAddf(NRRD, "%s: %s nrrd has non-existent values",
186
               me, multi ? mnounStr[kind] : nounStr[kind]);
187
      return 1;
188
    }
189
    entLen = mapAxis ? AIR_CAST(unsigned int, nmap->axis[0].size) : 1;
190
  } else {
191
    if (multi) {
192
      biffAddf(NRRD, "%s: sorry, multi irregular maps not implemented", me);
193
      return 1;
194
    }
195
    /* its an irregular map */
196
    if (nrrd1DIrregMapCheck(nmap)) {
197
      biffAddf(NRRD, "%s: problem with irregular map", me);
198
      return 1;
199
    }
200
    /* mapAxis has no meaning for irregular maps, but we'll pretend ... */
201
    mapAxis = nmap->axis[0].size == 2 ? 0 : 1;
202
    copyMapAxis0 = AIR_TRUE;
203
    entLen = AIR_CAST(unsigned int, nmap->axis[0].size-1);
204
  }
205
  if (mapAxis + nin->dim > NRRD_DIM_MAX) {
206
    biffAddf(NRRD, "%s: input nrrd dim %d through non-scalar %s exceeds "
207
             "NRRD_DIM_MAX %d",
208
             me, nin->dim,
209
             multi ? mnounStr[kind] : nounStr[kind], NRRD_DIM_MAX);
210
    return 1;
211
  }
212
  nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, size+mapAxis);
213
  if (mapAxis) {
214
    size[0] = entLen;
215
    axisMap[0] = -1;
216
  }
217
  for (dim=0; dim<nin->dim; dim++) {
218
    axisMap[dim+mapAxis] = dim;
219
  }
220
  /*
221
  fprintf(stderr, "##%s: pre maybe alloc: nout->data = %p\n", me, nout->data);
222
  for (dim=0; dim<mapAxis + nin->dim; dim++) {
223
    fprintf(stderr, "    size[%d] = %d\n", d, (int)size[d]);
224
  }
225
  fprintf(stderr, "   nout->dim = %d; nout->type = %d = %s; sizes = %d,%d\n",
226
          nout->dim, nout->type,
227
          airEnumStr(nrrdType, nout->type));
228
  fprintf(stderr, "   typeOut = %d = %s\n", typeOut,
229
          airEnumStr(nrrdType, typeOut));
230
  */
231
  if (nrrdMaybeAlloc_nva(nout, typeOut, mapAxis + nin->dim, size)) {
232
    biffAddf(NRRD, "%s: couldn't allocate output nrrd", me);
233
    return 1;
234
  }
235
  /*
236
  fprintf(stderr, "   nout->dim = %d; nout->type = %d = %s\n",
237
          nout->dim, nout->type,
238
          airEnumStr(nrrdType, nout->type),
239
          nout->axis[0].size, nout->axis[1].size);
240
  for (d=0; d<nout->dim; d++) {
241
    fprintf(stderr, "    size[%d] = %d\n", d, (int)nout->axis[d].size);
242
  }
243
  fprintf(stderr, "##%s: post maybe alloc: nout->data = %p\n", me, nout->data);
244
  */
245
  if (nrrdAxisInfoCopy(nout, nin, axisMap, NRRD_AXIS_INFO_NONE)) {
246
    biffAddf(NRRD, "%s: trouble copying axis info", me);
247
    return 1;
248
  }
249
  if (copyMapAxis0) {
250
    _nrrdAxisInfoCopy(nout->axis + 0, nmap->axis + 0,
251
                      NRRD_AXIS_INFO_SIZE_BIT);
252
  }
253
254
  mapcnt = _nrrdContentGet(nmap);
255
  if (nrrdContentSet_va(nout, multi ? mverbStr[kind] : verbStr[kind],
256
                        nin, "%s", mapcnt)) {
257
    biffAddf(NRRD, "%s:", me);
258
    free(mapcnt); return 1;
259
  }
260
  free(mapcnt);
261
  if (nrrdBasicInfoCopy(nout, nin,
262
                        NRRD_BASIC_INFO_DATA_BIT
263
                        | NRRD_BASIC_INFO_TYPE_BIT
264
                        | NRRD_BASIC_INFO_BLOCKSIZE_BIT
265
                        | NRRD_BASIC_INFO_DIMENSION_BIT
266
                        | NRRD_BASIC_INFO_CONTENT_BIT
267
                        | (nrrdStateKeyValuePairsPropagate
268
                           ? 0
269
                           : NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) {
270
    biffAddf(NRRD, "%s:", me);
271
    return 1;
272
  }
273
  return 0;
274
}
275
276
/*
277
** _nrrdApply1DLutOrRegMap()
278
**
279
** the guts of nrrdApply1DLut and nrrdApply1DRegMap
280
**
281
** yikes, does NOT use biff, since we're only supposed to be called
282
** after copious error checking.
283
**
284
** FOR INSTANCE, this allows nout == nin, which could be a big
285
** problem if mapAxis == 1.
286
**
287
** we don't need a typeOut arg because nout has already been allocated
288
** as some specific type; we'll look at that.
289
**
290
** NOTE: non-existent values get passed through regular maps and luts
291
** "unchanged".  However, if the output type is integral, the results
292
** are probaby undefined.  HEY: there is currently no warning message
293
** or error handling based on nrrdStateDisallowIntegerNonExist, but
294
** there really should be.
295
*/
296
int
297
_nrrdApply1DLutOrRegMap(Nrrd *nout, const Nrrd *nin, const NrrdRange *range,
298
                        const Nrrd *nmap, int ramps, int rescale, int multi) {
299
  /* static const char me[]="_nrrdApply1DLutOrRegMap"; */
300
  char *inData, *outData, *mapData, *entData0, *entData1;
301
  size_t N, I;
302
  double (*inLoad)(const void *v), (*mapLup)(const void *v, size_t I),
303
    (*outInsert)(void *v, size_t I, double d),
304
    val, mapIdxFrac, domMin, domMax;
305
  unsigned int i, mapAxis, mapLen, mapIdx, entSize, entLen, inSize, outSize;
306
307
  if (!multi) {
308
    mapAxis = nmap->dim - 1;           /* axis of nmap containing entries */
309
  } else {
310
    mapAxis = nmap->dim - nin->dim - 1;
311
  }
312
  mapData = (char *)nmap->data;        /* map data, as char* */
313
                                       /* low end of map domain */
314
  domMin = _nrrdApplyDomainMin(nmap, ramps, mapAxis);
315
                                       /* high end of map domain */
316
  domMax = _nrrdApplyDomainMax(nmap, ramps, mapAxis);
317
  mapLen = AIR_CAST(unsigned int, nmap->axis[mapAxis].size);   /* number of entries in map */
318
  mapLup = nrrdDLookup[nmap->type];    /* how to get doubles out of map */
319
  inData = (char *)nin->data;          /* input data, as char* */
320
  inLoad = nrrdDLoad[nin->type];       /* how to get doubles out of nin */
321
  inSize = AIR_CAST(unsigned int, nrrdElementSize(nin));       /* size of one input value */
322
  outData = (char *)nout->data;        /* output data, as char* */
323
  outInsert = nrrdDInsert[nout->type]; /* putting doubles into output */
324
  entLen = (mapAxis                    /* number of elements in one entry */
325
            ? AIR_CAST(unsigned int, nmap->axis[0].size)
326
            : 1);
327
  outSize = entLen*AIR_CAST(unsigned int, nrrdElementSize(nout)); /* size of entry in output */
328
  entSize = entLen*AIR_CAST(unsigned int, nrrdElementSize(nmap)); /* size of entry in map */
329
330
  N = nrrdElementNumber(nin);       /* the number of values to be mapped */
331
  if (ramps) {
332
    /* regular map */
333
    for (I=0; I<N; I++) {
334
      /* ...
335
      if (!(I % 100)) fprintf(stderr, "I = %d\n", (int)I);
336
      ... */
337
      val = inLoad(inData);
338
      /* ...
339
      fprintf(stderr, "##%s: val = \na% 31.15f --> ", me, val);
340
      ... */
341
      if (rescale) {
342
        val = (range->min != range->max
343
               ? AIR_AFFINE(range->min, val, range->max, domMin, domMax)
344
               : domMin);
345
        /* ...
346
        fprintf(stderr, "\nb% 31.15f (min,max = %g,%g)--> ", val,
347
                range->min, range->max);
348
        ... */
349
      }
350
      /* ...
351
      fprintf(stderr, "\nc% 31.15f --> clamp(%g,%g), %d --> ",
352
              val, domMin, domMax, mapLen);
353
      ... */
354
      if (AIR_EXISTS(val)) {
355
        val = AIR_CLAMP(domMin, val, domMax);
356
        mapIdxFrac = AIR_AFFINE(domMin, val, domMax, 0, mapLen-1);
357
        /* ...
358
        fprintf(stderr, "mapIdxFrac = \nd% 31.15f --> ",
359
                mapIdxFrac);
360
        ... */
361
        mapIdx = (unsigned int)mapIdxFrac;
362
        mapIdx -= mapIdx == mapLen-1;
363
        mapIdxFrac -= mapIdx;
364
        /* ...
365
        fprintf(stderr, "%s: (%d,\ne% 31.15f) --> \n",
366
                me, mapIdx, mapIdxFrac);
367
        ... */
368
        entData0 = mapData + mapIdx*entSize;
369
        entData1 = mapData + (mapIdx+1)*entSize;
370
        for (i=0; i<entLen; i++) {
371
          val = ((1-mapIdxFrac)*mapLup(entData0, i) +
372
                 mapIdxFrac*mapLup(entData1, i));
373
          outInsert(outData, i, val);
374
          /* ...
375
          fprintf(stderr, "f% 31.15f\n", val);
376
          ... */
377
        }
378
      } else {
379
        /* copy non-existent values from input to output */
380
        for (i=0; i<entLen; i++) {
381
          outInsert(outData, i, val);
382
        }
383
      }
384
      inData += inSize;
385
      outData += outSize;
386
      if (multi) {
387
        mapData += mapLen*entSize;
388
      }
389
    }
390
  } else {
391
    /* lookup table */
392
    for (I=0; I<N; I++) {
393
      val = inLoad(inData);
394
      if (rescale) {
395
        val = (range->min != range->max
396
               ? AIR_AFFINE(range->min, val, range->max, domMin, domMax)
397
               : domMin);
398
      }
399
      if (AIR_EXISTS(val)) {
400
        mapIdx = airIndexClamp(domMin, val, domMax, mapLen);
401
        entData0 = mapData + mapIdx*entSize;
402
        for (i=0; i<entLen; i++) {
403
          outInsert(outData, i, mapLup(entData0, i));
404
        }
405
      } else {
406
        /* copy non-existent values from input to output */
407
        for (i=0; i<entLen; i++) {
408
          outInsert(outData, i, val);
409
        }
410
      }
411
      inData += inSize;
412
      outData += outSize;
413
      if (multi) {
414
        mapData += mapLen*entSize;
415
      }
416
    }
417
  }
418
419
  return 0;
420
}
421
422
/*
423
******** nrrdApply1DLut
424
**
425
** A "lut" is a simple lookup table: the data points are evenly spaced,
426
** with cell-centering assumed, and there is no interpolation except
427
** nearest neighbor.  The axis min and max are used to determine the
428
** range of values that can be mapped with the lut.
429
**
430
** Of the three kinds of 1-D maps, only luts can have output type block.
431
**
432
** If the lut nrrd is 1-D, then the output is a scalar nrrd with the
433
** same dimension as the input.  If the lut nrrd is 2-D, then each
434
** value in the input is mapped to a vector of values from the lut,
435
** which is always a scanline along axis 0.
436
**
437
** This allows lut length to be simply 1.
438
*/
439
int
440
nrrdApply1DLut(Nrrd *nout, const Nrrd *nin,
441
               const NrrdRange *_range, const Nrrd *nlut,
442
               int typeOut, int rescale) {
443
  static const char me[]="nrrdApply1DLut";
444
  NrrdRange *range;
445
  airArray *mop;
446
447
  if (!(nout && nlut && nin)) {
448
    biffAddf(NRRD, "%s: got NULL pointer", me);
449
    return 1;
450
  }
451
  mop = airMopNew();
452
  if (_range) {
453
    range = nrrdRangeCopy(_range);
454
    nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState);
455
  } else {
456
    range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState);
457
  }
458
  airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways);
459
  if (_nrrdApply1DSetUp(nout, nin, range, nlut, kindLut, typeOut,
460
                        rescale, AIR_FALSE /* multi */)
461
      || _nrrdApply1DLutOrRegMap(nout, nin, range, nlut, AIR_FALSE /* ramps */,
462
                                 rescale, AIR_FALSE /* multi */)) {
463
    biffAddf(NRRD, "%s:", me);
464
    airMopError(mop); return 1;
465
  }
466
  airMopOkay(mop);
467
  return 0;
468
}
469
470
int
471
nrrdApplyMulti1DLut(Nrrd *nout, const Nrrd *nin,
472
                    const NrrdRange *_range, const Nrrd *nmlut,
473
                    int typeOut, int rescale) {
474
  static const char me[]="nrrdApplyMulti1DLut";
475
  NrrdRange *range;
476
  airArray *mop;
477
478
  if (!(nout && nmlut && nin)) {
479
    biffAddf(NRRD, "%s: got NULL pointer", me);
480
    return 1;
481
  }
482
  mop = airMopNew();
483
  if (_range) {
484
    range = nrrdRangeCopy(_range);
485
    nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState);
486
  } else {
487
    range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState);
488
  }
489
  airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways);
490
  if (_nrrdApply1DSetUp(nout, nin, range, nmlut, kindLut, typeOut,
491
                        rescale, AIR_TRUE /* multi */)
492
      || _nrrdApply1DLutOrRegMap(nout, nin, range, nmlut,
493
                                 AIR_FALSE /* ramps */,
494
                                 rescale, AIR_TRUE /* multi */)) {
495
    biffAddf(NRRD, "%s:", me);
496
    airMopError(mop); return 1;
497
  }
498
  airMopOkay(mop);
499
  return 0;
500
}
501
502
/*
503
******** nrrdApply1DRegMap
504
**
505
** A regular map has data points evenly spaced, with node-centering and
506
** and linear interpolation assumed.  As with luts, the axis min and
507
** max determine the range of values that are mapped.  This function
508
** is used in nrrdHistoEq() and is the basis of the very popular "unu rmap".
509
**
510
** If the lut nrrd is 1-D, then the output is a scalar nrrd with the
511
** same dimension as the input.  If the lut nrrd is 2-D, then each
512
** value in the input is mapped to a linear weighting of vectors
513
** from the map; the vectors are the scanlines along axis 0.
514
**
515
** NB: this function makes NO provisions for non-existent input values.
516
** There won't be any memory errors, but the results are undefined.
517
**
518
** This allows map length to be simply 1.
519
*/
520
int
521
nrrdApply1DRegMap(Nrrd *nout, const Nrrd *nin,
522
                  const NrrdRange *_range, const Nrrd *nmap,
523
                  int typeOut, int rescale) {
524
  static const char me[]="nrrdApply1DRegMap";
525
  NrrdRange *range;
526
  airArray *mop;
527
528
  if (!(nout && nmap && nin)) {
529
    biffAddf(NRRD, "%s: got NULL pointer", me);
530
    return 1;
531
  }
532
  mop = airMopNew();
533
  if (_range) {
534
    range = nrrdRangeCopy(_range);
535
    nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState);
536
  } else {
537
    range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState);
538
  }
539
  airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways);
540
  if (_nrrdApply1DSetUp(nout, nin, range, nmap, kindRmap, typeOut,
541
                        rescale, AIR_FALSE /* multi */)
542
      || _nrrdApply1DLutOrRegMap(nout, nin, range, nmap, AIR_TRUE /* ramps */,
543
                                 rescale, AIR_FALSE /* multi */)) {
544
    biffAddf(NRRD, "%s:", me);
545
    airMopError(mop); return 1;
546
  }
547
  airMopOkay(mop);
548
  return 0;
549
}
550
551
int
552
nrrdApplyMulti1DRegMap(Nrrd *nout, const Nrrd *nin,
553
                       const NrrdRange *_range, const Nrrd *nmmap,
554
                       int typeOut, int rescale) {
555
  static const char me[]="nrrdApplyMulti1DRegMap";
556
  NrrdRange *range;
557
  airArray *mop;
558
559
  if (!(nout && nmmap && nin)) {
560
    biffAddf(NRRD, "%s: got NULL pointer", me);
561
    return 1;
562
  }
563
  mop = airMopNew();
564
  if (_range) {
565
    range = nrrdRangeCopy(_range);
566
    nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState);
567
  } else {
568
    range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState);
569
  }
570
  airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways);
571
  if (_nrrdApply1DSetUp(nout, nin, range, nmmap, kindRmap, typeOut,
572
                        rescale, AIR_TRUE /* multi */)
573
      || _nrrdApply1DLutOrRegMap(nout, nin, range, nmmap, AIR_TRUE /* ramps */,
574
                                 rescale, AIR_TRUE /* multi */)) {
575
    biffAddf(NRRD, "%s:", me);
576
    airMopError(mop); return 1;
577
  }
578
  airMopOkay(mop);
579
  return 0;
580
}
581
582
/*
583
******** nrrd1DIrregMapCheck()
584
**
585
** return zero only for the valid forms of 1D irregular map.
586
** imap must be 2D, both sizes >= 2, non-block-type, no non-existent
587
** values in range.  If the first point's position is non-existent,
588
** than the first three points positions must be -inf, NaN, and +inf,
589
** and none of the other points locations can be non-existent, and
590
** they must increase monotonically.  There must be at least two
591
** points with existent positions.
592
*/
593
int
594
nrrd1DIrregMapCheck(const Nrrd *nmap) {
595
  static const char me[]="nrrd1DIrregMapCheck";
596
  double (*mapLup)(const void *v, size_t I);
597
  int i, entLen, mapLen, baseI;
598
  size_t min[2], max[2];
599
  Nrrd *nrange;
600
601
  if (!nmap) {
602
    biffAddf(NRRD, "%s: got NULL pointer", me);
603
    return 1;
604
  }
605
  if (nrrdCheck(nmap)) {
606
    biffAddf(NRRD, "%s: ", me);
607
    return 1;
608
  }
609
  if (nrrdTypeBlock == nmap->type) {
610
    biffAddf(NRRD, "%s: map is %s type, need scalar",
611
             me, airEnumStr(nrrdType, nrrdTypeBlock));
612
    return 1;
613
  }
614
  if (2 != nmap->dim) {
615
    biffAddf(NRRD, "%s: map needs to have dimension 2, not %d",
616
             me, nmap->dim);
617
    return 1;
618
  }
619
  entLen = AIR_CAST(unsigned int,nmap->axis[0].size);
620
  mapLen = AIR_CAST(unsigned int,nmap->axis[1].size);
621
  if (!( entLen >= 2 && mapLen >= 2 )) {
622
    biffAddf(NRRD, "%s: both map's axes sizes should be >= 2 (not %d,%d)",
623
             me, entLen, mapLen);
624
    return 1;
625
  }
626
  min[0] = 1; max[0] = nmap->axis[0].size-1;
627
  min[1] = 0; max[1] = nmap->axis[1].size-1;
628
  if (nrrdCrop(nrange=nrrdNew(), nmap, min, max)) {
629
    biffAddf(NRRD, "%s: couldn't crop to isolate range of map", me);
630
    nrrdNuke(nrange); return 1;
631
  }
632
  if (nrrdHasNonExist(nrange)) {
633
    biffAddf(NRRD, "%s: map has non-existent values in its range", me);
634
    nrrdNuke(nrange); return 1;
635
  }
636
  nrrdNuke(nrange);
637
  mapLup = nrrdDLookup[nmap->type];
638
  if (AIR_EXISTS(mapLup(nmap->data, 0))) {
639
    baseI = 0;
640
  } else {
641
    baseI = 3;
642
    if (!( mapLen >= 5 )) {
643
      biffAddf(NRRD, "%s: length of map w/ non-existent locations must "
644
               "be >= 5 (not %d)", me, mapLen);
645
      return 1;
646
    }
647
    if (!( airFP_NEG_INF == airFPClass_d(mapLup(nmap->data, 0*entLen)) &&
648
           airFP_QNAN    == airFPClass_d(mapLup(nmap->data, 1*entLen)) &&
649
           airFP_POS_INF == airFPClass_d(mapLup(nmap->data, 2*entLen)) )) {
650
      biffAddf(NRRD, "%s: 1st entry's position non-existent, but position "
651
               "of 1st three entries (%g:%d,%g:%d,%g:%d) not "
652
               "-inf, NaN, and +inf", me,
653
               mapLup(nmap->data, 0*entLen),
654
               airFPClass_d(mapLup(nmap->data, 0*entLen)),
655
               mapLup(nmap->data, 1*entLen),
656
               airFPClass_d(mapLup(nmap->data, 1*entLen)),
657
               mapLup(nmap->data, 2*entLen),
658
               airFPClass_d(mapLup(nmap->data, 2*entLen)));
659
      return 1;
660
    }
661
  }
662
  for (i=baseI; i<mapLen; i++) {
663
    if (!AIR_EXISTS(mapLup(nmap->data, i*entLen))) {
664
      biffAddf(NRRD, "%s: entry %d has non-existent position", me, i);
665
      return 1;
666
    }
667
  }
668
  for (i=baseI; i<mapLen-1; i++) {
669
    if (!( mapLup(nmap->data, i*entLen) < mapLup(nmap->data, (i+1)*entLen) )) {
670
      biffAddf(NRRD, "%s: map entry %d pos (%g) not < entry %d pos (%g)",
671
               me, i, mapLup(nmap->data, i*entLen),
672
               i+1, mapLup(nmap->data, (i+1)*entLen));
673
      return 1;
674
    }
675
  }
676
  return 0;
677
}
678
679
/*
680
******** nrrd1DIrregAclCheck()
681
**
682
** returns zero only on valid accelerators for 1D irregular mappings
683
*/
684
int
685
nrrd1DIrregAclCheck(const Nrrd *nacl) {
686
  static const char me[]="nrrd1DIrregAclCheck";
687
688
  if (!nacl) {
689
    biffAddf(NRRD, "%s: got NULL pointer", me);
690
    return 1;
691
  }
692
  if (nrrdCheck(nacl)) {
693
    biffAddf(NRRD, "%s: ", me);
694
    return 1;
695
  }
696
  if (nrrdTypeUShort != nacl->type) {
697
    biffAddf(NRRD, "%s: type should be %s, not %s", me,
698
             airEnumStr(nrrdType, nrrdTypeUShort),
699
             airEnumStr(nrrdType, nacl->type));
700
    return 1;
701
  }
702
  if (2 != nacl->dim) {
703
    biffAddf(NRRD, "%s: dimension should be 2, not %d", me, nacl->dim);
704
    return 1;
705
  }
706
  if (!( nacl->axis[0].size == 2 && nacl->axis[1].size >= 2 )) {
707
    char stmp1[AIR_STRLEN_SMALL], stmp2[AIR_STRLEN_SMALL];
708
    biffAddf(NRRD, "%s: sizes (%s,%s) not (2,>=2)", me,
709
             airSprintSize_t(stmp1, nacl->axis[0].size),
710
             airSprintSize_t(stmp2, nacl->axis[1].size));
711
    return 1;
712
  }
713
714
  return 0;
715
}
716
717
/*
718
** _nrrd1DIrregMapDomain()
719
**
720
** ALLOCATES an array of doubles storing the existent control point
721
** locations, and sets its length in *poslenP.  If there are the three
722
** points with non-existent locations, these are ignored.
723
**
724
** Assumes that nrrd1DIrregMapCheck has been called on "nmap".
725
*/
726
double *
727
_nrrd1DIrregMapDomain(int *posLenP, int *baseIP, const Nrrd *nmap) {
728
  static const char me[]="_nrrd1DIrregMapDomain";
729
  int i, entLen, baseI, posLen;
730
  double *pos, (*mapLup)(const void *v, size_t I);
731
732
  mapLup = nrrdDLookup[nmap->type];
733
  baseI = AIR_EXISTS(mapLup(nmap->data, 0)) ? 0 : 3;
734
  if (baseIP) {
735
    *baseIP = baseI;
736
  }
737
  entLen = AIR_CAST(unsigned int,nmap->axis[0].size);
738
  posLen = AIR_CAST(unsigned int,nmap->axis[1].size) - baseI;
739
  if (posLenP) {
740
    *posLenP = posLen;
741
  }
742
  pos = (double*)malloc(posLen * sizeof(double));
743
  if (!pos) {
744
    biffAddf(NRRD, "%s: couldn't allocate %d doubles\n", me, posLen);
745
    return NULL;
746
  }
747
  for (i=0; i<posLen; i++) {
748
    pos[i] = mapLup(nmap->data, (baseI+i)*entLen);
749
  }
750
  return pos;
751
}
752
753
/*
754
** _nrrd1DIrregFindInterval()
755
**
756
** The hard part of doing 1D irregular mapping: given an array of
757
** control point locations, and a value, find which interval the value
758
** lies in.  The lowest and highest possible indices are given in
759
** "loI" and "hiI".  Results are undefined if these do not in fact
760
** bound the location of correct interval, or if loI > hiI, or if the
761
** query positon "p" is not in the domain vector "pos".  Intervals are
762
** identified by the integral index of the LOWER of the two control
763
** points spanning the interval.
764
**
765
** This imposes the same structure of half-open intervals that
766
** is done by airIndex().  That is, a value p is in interval i
767
** if pos[i] <= p < pos[i+1] for all but the last interval, and
768
** pos[i] <= p <= pos[i+1] for the last interval (in which case
769
** i == hiI)
770
*/
771
int
772
_nrrd1DIrregFindInterval(const double *pos, double p, int loI, int hiI) {
773
  int midI;
774
775
  /*
776
  fprintf(stderr, "##%s(%g): hi: %d/%g-%g | %d/%g-%g\n",
777
          "_nrrd1DIrregFindInterval", p,
778
          loI, pos[loI], pos[loI+1],
779
          hiI, pos[hiI], pos[hiI+1]);
780
  */
781
  while (loI < hiI) {
782
    midI = (loI + hiI)/2;
783
    if ( pos[midI] <= p && ((midI <  hiI && p <  pos[midI+1]) ||
784
                            (midI == hiI && p <= pos[midI+1])) ) {
785
      /* p is between (pos[midI],pos[midI+1]): we're done */
786
      loI = hiI = midI;
787
    } else if (pos[midI] > p) {
788
      /* p is below interval midI: midI-1 is valid upper bound */
789
      hiI = midI-1;
790
    } else {
791
      /* p is above interval midI: midI+1 is valid lower bound */
792
      loI = midI+1;
793
    }
794
    /*
795
    fprintf(stderr, "##%s(%g): %d/%g-%g | %d/%g-%g | %d/%g-%g\n",
796
            "_nrrd1DIrregFindInterval", p,
797
            loI, pos[loI], pos[loI+1],
798
            midI, pos[midI], pos[midI+1],
799
            hiI, pos[hiI], pos[hiI+1]);
800
    */
801
  }
802
  return loI;
803
}
804
805
/*
806
******** nrrd1DIrregAclGenerate()
807
**
808
** Generates the "acl" that is used to speed up the action of
809
** nrrdApply1DIrregMap().  Basically, the domain of the map
810
** is quantized into "acllen" bins, and for each bin, the
811
** lowest and highest possible map interval is stored. This
812
** either obviates or speeds up the task of finding which
813
** interval contains a given value.
814
**
815
** Assumes that nrrd1DIrregMapCheck has been called on "nmap".
816
*/
817
int
818
nrrd1DIrregAclGenerate(Nrrd *nacl, const Nrrd *nmap, size_t aclLen) {
819
  static const char me[]="nrrd1DIrregAclGenerate";
820
  int posLen;
821
  unsigned int ii;
822
  unsigned short *acl;
823
  double lo, hi, min, max, *pos;
824
825
  if (!(nacl && nmap)) {
826
    biffAddf(NRRD, "%s: got NULL pointer", me);
827
    return 1;
828
  }
829
  if (!(aclLen >= 2)) {
830
    char stmp[AIR_STRLEN_SMALL];
831
    biffAddf(NRRD, "%s: given acl length (%s) is too small", me,
832
             airSprintSize_t(stmp, aclLen));
833
    return 1;
834
  }
835
  if (nrrdMaybeAlloc_va(nacl, nrrdTypeUShort, 2,
836
                        AIR_CAST(size_t, 2), AIR_CAST(size_t, aclLen))) {
837
    biffAddf(NRRD, "%s: ", me);
838
    return 1;
839
  }
840
  acl = (unsigned short *)nacl->data;
841
  pos = _nrrd1DIrregMapDomain(&posLen, NULL, nmap);
842
  if (!pos) {
843
    biffAddf(NRRD, "%s: couldn't determine domain", me);
844
    return 1;
845
  }
846
  nacl->axis[1].min = min = pos[0];
847
  nacl->axis[1].max = max = pos[posLen-1];
848
  for (ii=0; ii<=aclLen-1; ii++) {
849
    lo = AIR_AFFINE(0, ii, aclLen, min, max);
850
    hi = AIR_AFFINE(0, ii+1, aclLen, min, max);
851
    acl[0 + 2*ii] = _nrrd1DIrregFindInterval(pos, lo, 0, posLen-2);
852
    acl[1 + 2*ii] = _nrrd1DIrregFindInterval(pos, hi, 0, posLen-2);
853
  }
854
  free(pos);
855
856
  return 0;
857
}
858
859
/*
860
******** nrrdApply1DIrregMap()
861
**
862
** Linear interpolation between irregularly spaced control points.
863
** Obviously, the location of the control point has to be given
864
** explicitly.  The map nrrd must have dimension 2, and each
865
** control point is represented by a scanline along axis 0.  The
866
** first value is the position of the control point, and the remaining
867
** value(s) are linearly weighted according to the position of the
868
** input value among the control point locations.
869
**
870
** To allow "coloring" of non-existent values -inf, NaN, and +inf, if
871
** the very first value of the map (the location of the first control
872
** point) is non-existent, then the first three control point locations
873
** must be -inf, NaN, and +inf, in that order, and the information
874
** about these points will be used for corresponding input values.
875
** Doing this makes everything slower, however, because airFPClass_f()
876
** is called on every single value.
877
**
878
** This assumes that nrrd1DIrregMapCheck has been called on "nmap",
879
** and that nrrd1DIrregAclCheck has been called on "nacl" (if it is
880
** non-NULL).
881
*/
882
int
883
nrrdApply1DIrregMap(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range,
884
                    const Nrrd *nmap, const Nrrd *nacl,
885
                    int typeOut, int rescale) {
886
  static const char me[]="nrrdApply1DIrregMap";
887
  size_t N, I;
888
  int i, *acl, entLen, posLen, aclLen, mapIdx, aclIdx,
889
    entSize, colSize, inSize, lo, hi, baseI;
890
  double val, *pos, domMin, domMax, mapIdxFrac,
891
    (*mapLup)(const void *v, size_t I),
892
    (*inLoad)(const void *v), (*outInsert)(void *v, size_t I, double d);
893
  char *inData, *outData, *entData0, *entData1;
894
  NrrdRange *range;
895
  airArray *mop;
896
897
  /*
898
  fprintf(stderr, "!%s: rescale = %d\n", me, rescale);
899
  */
900
  if (!(nout && nmap && nin)) {
901
    biffAddf(NRRD, "%s: got NULL pointer", me);
902
    return 1;
903
  }
904
  mop = airMopNew();
905
  if (_range) {
906
    range = nrrdRangeCopy(_range);
907
    nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState);
908
  } else {
909
    range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState);
910
  }
911
  airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways);
912
  if (_nrrdApply1DSetUp(nout, nin, range, nmap,
913
                        kindImap, typeOut, rescale, AIR_FALSE /* multi */)) {
914
    biffAddf(NRRD, "%s:", me);
915
    airMopError(mop); return 1;
916
  }
917
  if (nacl && nrrd1DIrregAclCheck(nacl)) {
918
    biffAddf(NRRD, "%s: given acl isn't valid", me);
919
    airMopError(mop); return 1;
920
  }
921
922
  if (nacl) {
923
    acl = (int *)nacl->data;
924
    aclLen = AIR_CAST(unsigned int,nacl->axis[1].size);
925
  } else {
926
    acl = NULL;
927
    aclLen = 0;
928
  }
929
  pos = _nrrd1DIrregMapDomain(&posLen, &baseI, nmap);
930
  if (!pos) {
931
    biffAddf(NRRD, "%s: couldn't determine domain", me);
932
    airMopError(mop); return 1;
933
  }
934
  airMopAdd(mop, pos, airFree, airMopAlways);
935
  mapLup = nrrdDLookup[nmap->type];
936
937
  inData = (char *)nin->data;
938
  inLoad = nrrdDLoad[nin->type];
939
  inSize = AIR_CAST(unsigned int,nrrdElementSize(nin));
940
  mapLup = nrrdDLookup[nmap->type];
941
  entLen = AIR_CAST(unsigned int,nmap->axis[0].size);    /* entLen is really 1 + entry length */
942
  entSize = entLen*AIR_CAST(unsigned int,nrrdElementSize(nmap));
943
  colSize = (entLen-1)*AIR_CAST(unsigned int,nrrdTypeSize[typeOut]);
944
  outData = (char *)nout->data;
945
  outInsert = nrrdDInsert[nout->type];
946
  domMin = pos[0];
947
  domMax = pos[posLen-1];
948
  /*
949
  fprintf(stderr, "!%s: domMin, domMax = %g, %g\n", me, domMin, domMax);
950
  */
951
952
  N = nrrdElementNumber(nin);
953
  for (I=0;
954
       I<N;
955
       I++, inData += inSize, outData += colSize) {
956
    val = inLoad(inData);
957
    /*
958
    fprintf(stderr, "!%s: (%d) val = % 31.15f\n", me, (int)I, val);
959
    */
960
    if (!AIR_EXISTS(val)) {
961
      /* got a non-existent value */
962
      if (baseI) {
963
        /* and we know how to deal with them */
964
        switch (airFPClass_d(val)) {
965
        case airFP_NEG_INF:
966
          mapIdx = 0;
967
          break;
968
        case airFP_SNAN:
969
        case airFP_QNAN:
970
          mapIdx = 1;
971
          break;
972
        case airFP_POS_INF:
973
          mapIdx = 2;
974
          break;
975
        default:
976
          mapIdx = 0;
977
          fprintf(stderr, "%s: PANIC: non-existent value/class %g/%d "
978
                  "not handled\n",
979
                  me, val, airFPClass_d(val));
980
          exit(1);
981
        }
982
        entData0 = (char*)(nmap->data) + mapIdx*entSize;
983
        for (i=1; i<entLen; i++) {
984
          outInsert(outData, i-1, mapLup(entData0, i));
985
        }
986
        continue;  /* we're done! (with this value) */
987
      } else {
988
        /* we don't know how to properly deal with this non-existent value:
989
           we use the first entry, and then fall through to code below */
990
        mapIdx = 0;
991
        mapIdxFrac = 0.0;
992
      }
993
    } else {
994
      /* we have an existent value */
995
      if (rescale) {
996
        val = (range->min != range->max
997
               ? AIR_AFFINE(range->min, val, range->max, domMin, domMax)
998
               : domMin);
999
      }
1000
      val = AIR_CLAMP(domMin, val, domMax);
1001
      if (acl) {
1002
        aclIdx = airIndex(domMin, val, domMax, aclLen);
1003
        lo = acl[0 + 2*aclIdx];
1004
        hi = acl[1 + 2*aclIdx];
1005
      } else {
1006
        lo = 0;
1007
        hi = posLen-2;
1008
      }
1009
      if (lo < hi) {
1010
        mapIdx = _nrrd1DIrregFindInterval(pos, val, lo, hi);
1011
      } else {
1012
        /* acl did its job ==> lo == hi */
1013
        mapIdx = lo;
1014
      }
1015
      /*
1016
      fprintf(stderr, "!%s:   --> val = %g; lo,hi = %d,%d, mapIdx = %d\n",
1017
              me, val, lo, hi, mapIdx);
1018
      */
1019
    }
1020
    mapIdxFrac = AIR_AFFINE(pos[mapIdx], val, pos[mapIdx+1], 0.0, 1.0);
1021
    /*
1022
    fprintf(stderr, "!%s:    mapIdxFrac = %g\n", me, mapIdxFrac);
1023
    */
1024
    entData0 = (char*)(nmap->data) + (baseI+mapIdx)*entSize;
1025
    entData1 = (char*)(nmap->data) + (baseI+mapIdx+1)*entSize;
1026
    for (i=1; i<entLen; i++) {
1027
      val = ((1-mapIdxFrac)*mapLup(entData0, i) +
1028
             mapIdxFrac*mapLup(entData1, i));
1029
      /*
1030
      fprintf(stderr, "!%s: %g * %g   +   %g * %g = %g\n", me,
1031
              1-mapIdxFrac, mapLup(entData0, i),
1032
              mapIdxFrac, mapLup(entData1, i), val);
1033
      */
1034
      outInsert(outData, i-1, val);
1035
    }
1036
  }
1037
  airMopOkay(mop);
1038
  return 0;
1039
}
1040
1041
/*
1042
******** nrrdApply1DSubstitution
1043
**
1044
** A "subst" is a substitution table, i.e. a list of pairs that
1045
** describes what values should be substituted with what (substitution
1046
** rules).  So, nsubst must be a scalar 2xN array.  The output is a
1047
** copy of the input with values substituted using this table.
1048
**
1049
** Unlike with lookup tables and maps (irregular and regular), we
1050
** aren't at liberty to change the dimensionality of the data (can't
1051
** substitute a grayscale with a color).  The ability to apply
1052
** substitutions to non-scalar data will be in a different function.
1053
** Also unlike lookup tables and maps, the output type is the SAME as
1054
** the input type; the output does NOT inherit the type of the
1055
** substitution
1056
*/
1057
int
1058
nrrdApply1DSubstitution(Nrrd *nout, const Nrrd *nin, const Nrrd *_nsubst) {
1059
  static const char me[]="nrrdApply1DSubstitution";
1060
  double (*lup)(const void *, size_t);
1061
  double (*ins)(void *, size_t, double);
1062
  Nrrd *nsubst;
1063
  double val, *subst;
1064
  size_t ii, jj, num, asize0, asize1;
1065
  int changed;
1066
  airArray *mop;
1067
1068
  if (!(nout && _nsubst && nin)) {
1069
    biffAddf(NRRD, "%s: got NULL pointer", me);
1070
    return 1;
1071
  }
1072
  if (nrrdTypeBlock == nin->type || nrrdTypeBlock == _nsubst->type) {
1073
    biffAddf(NRRD, "%s: input or substitution type is %s, need scalar",
1074
             me, airEnumStr(nrrdType, nrrdTypeBlock));
1075
    return 1;
1076
  }
1077
  if (2 != _nsubst->dim) {
1078
    biffAddf(NRRD, "%s: substitution table has to be 2-D, not %d-D",
1079
             me, _nsubst->dim);
1080
    return 1;
1081
  }
1082
  /* nrrdAxisInfoGet_va(_nsubst, nrrdAxisInfoSize, &asize0, &asize1); */
1083
  asize0 = _nsubst->axis[0].size;
1084
  asize1 = _nsubst->axis[1].size;
1085
  if (2 != asize0) {
1086
    biffAddf(NRRD, "%s: substitution table has to be 2xN, not %uxN",
1087
             me, AIR_CAST(unsigned int, asize0));
1088
    return 1;
1089
  }
1090
  if (nout != nin) {
1091
    if (nrrdCopy(nout, nin)) {
1092
      biffAddf(NRRD, "%s: couldn't initialize by copy to output", me);
1093
      return 1;
1094
    }
1095
  }
1096
1097
  mop = airMopNew();
1098
  nsubst = nrrdNew();
1099
  airMopAdd(mop, nsubst, (airMopper)nrrdNuke, airMopAlways);
1100
  if (nrrdConvert(nsubst, _nsubst, nrrdTypeDouble)) {
1101
    biffAddf(NRRD, "%s: couldn't create double copy of substitution table",
1102
             me);
1103
    airMopError(mop); return 1;
1104
  }
1105
  lup = nrrdDLookup[nout->type];
1106
  ins = nrrdDInsert[nout->type];
1107
  subst = (double *)nsubst->data;
1108
  num = nrrdElementNumber(nout);
1109
  for (ii=0; ii<num; ii++) {
1110
    val = lup(nout->data, ii);
1111
    changed = AIR_FALSE;
1112
    for (jj=0; jj<asize1; jj++) {
1113
      if (val == subst[jj*2+0]) {
1114
        val = subst[jj*2+1];
1115
        changed = AIR_TRUE;
1116
      }
1117
    }
1118
    if (changed) {
1119
      ins(nout->data, ii, val);
1120
    }
1121
  }
1122
1123
  airMopOkay(mop);
1124
  return 0;
1125
}