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 |
|
|
** RETIRED: nrrdMinMaxSet() |
29 |
|
|
** |
30 |
|
|
** Sets nrrd->min and nrrd->max to the extremal (existent) values in |
31 |
|
|
** the given nrrd, by calling the appropriate member of nrrdMinMaxExactFind[] |
32 |
|
|
** |
33 |
|
|
** calling this function will result in nrrd->hasNonExist being set |
34 |
|
|
** (because of the nrrdFindMinMax[] functions) |
35 |
|
|
** |
36 |
|
|
** decided NOT to use biff, so that this is a more distinct alternative to |
37 |
|
|
** nrrdMinMaxCleverSet(). |
38 |
|
|
|
39 |
|
|
void |
40 |
|
|
nrrdMinMaxSet(Nrrd *nrrd) { |
41 |
|
|
NRRD_TYPE_BIGGEST _min, _max; |
42 |
|
|
|
43 |
|
|
if (nrrd) { |
44 |
|
|
if (!airEnumValCheck(nrrdType, nrrd->type) |
45 |
|
|
&& nrrdTypeBlock != nrrd->type) { |
46 |
|
|
nrrdFindMinMax[nrrd->type](&_min, &_max, nrrd); |
47 |
|
|
nrrd->min = nrrdDLoad[nrrd->type](&_min); |
48 |
|
|
nrrd->max = nrrdDLoad[nrrd->type](&_max); |
49 |
|
|
} else { |
50 |
|
|
nrrd->min = nrrd->max = AIR_NAN; |
51 |
|
|
} |
52 |
|
|
} |
53 |
|
|
return; |
54 |
|
|
} |
55 |
|
|
*/ |
56 |
|
|
|
57 |
|
|
/* |
58 |
|
|
** RETIRED: nrrdMinMaxCleverSet() |
59 |
|
|
** |
60 |
|
|
** basically a wrapper around nrrdMinMaxSet(), with bells + whistles: |
61 |
|
|
** 1) will call nrrdMinMaxSet only when one of nrrd->min and nrrd->max |
62 |
|
|
** are non-existent, with the end result that only the non-existent |
63 |
|
|
** values are over-written |
64 |
|
|
** 2) obeys the nrrdStateClever8BitMinMax global state to short-cut |
65 |
|
|
** finding min and max for 8-bit data. Values for nrrd->min or |
66 |
|
|
** nrrd->max which were existent to start with are untouched. |
67 |
|
|
** 3) reports error if there are no existent values in nrrd (AIR_EXISTS() |
68 |
|
|
** fails on every value) |
69 |
|
|
** |
70 |
|
|
** Like nrrdMinMaxSet(), this will always set nrrd->hasNonExist. |
71 |
|
|
** |
72 |
|
|
** Uses biff. |
73 |
|
|
|
74 |
|
|
int |
75 |
|
|
nrrdMinMaxCleverSet(Nrrd *nrrd) { |
76 |
|
|
static const char me[]="nrrdMinMaxCleverSet"; |
77 |
|
|
double min, max; |
78 |
|
|
|
79 |
|
|
if (!nrrd) { |
80 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
81 |
|
|
return 1; |
82 |
|
|
} |
83 |
|
|
if (airEnumValCheck(nrrdType, nrrd->type)) { |
84 |
|
|
biffAddf(NRRD, "%s: input nrrd has invalid type (%d)", me, nrrd->type); |
85 |
|
|
return 1; |
86 |
|
|
} |
87 |
|
|
if (nrrdTypeBlock == nrrd->type) { |
88 |
|
|
sprintf(err, "%s: can't find min/max of type %s", me, |
89 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
90 |
|
|
} |
91 |
|
|
if (AIR_EXISTS(nrrd->min) && AIR_EXISTS(nrrd->max)) { |
92 |
|
|
/ * both of min and max already set, so we won't look for those, but |
93 |
|
|
we have to comply with stated behavior of always setting hasNonExist * / |
94 |
|
|
nrrdHasNonExistSet(nrrd); |
95 |
|
|
return 0; |
96 |
|
|
} |
97 |
|
|
if (nrrdStateClever8BitMinMax |
98 |
|
|
&& (nrrdTypeChar == nrrd->type || nrrdTypeUChar == nrrd->type)) { |
99 |
|
|
if (nrrdTypeChar == nrrd->type) { |
100 |
|
|
if (!AIR_EXISTS(nrrd->min)) |
101 |
|
|
nrrd->min = SCHAR_MIN; |
102 |
|
|
if (!AIR_EXISTS(nrrd->max)) |
103 |
|
|
nrrd->max = SCHAR_MAX; |
104 |
|
|
} else { |
105 |
|
|
if (!AIR_EXISTS(nrrd->min)) |
106 |
|
|
nrrd->min = 0; |
107 |
|
|
if (!AIR_EXISTS(nrrd->max)) |
108 |
|
|
nrrd->max = UCHAR_MAX; |
109 |
|
|
} |
110 |
|
|
nrrdHasNonExistSet(nrrd); |
111 |
|
|
return 0; |
112 |
|
|
} |
113 |
|
|
|
114 |
|
|
/ * at this point we need to find either min and/or max (at least |
115 |
|
|
one of them was non-existent on the way in) * / |
116 |
|
|
|
117 |
|
|
/ * save incoming values in case they exist * / |
118 |
|
|
min = nrrd->min; |
119 |
|
|
max = nrrd->max; |
120 |
|
|
/ * this will set nrrd->min, nrrd->max, and hasNonExist * / |
121 |
|
|
nrrdMinMaxSet(nrrd); |
122 |
|
|
if (!( AIR_EXISTS(nrrd->min) && AIR_EXISTS(nrrd->max) )) { |
123 |
|
|
biffAddf(NRRD, "%s: no existent values!", me); |
124 |
|
|
return 1; |
125 |
|
|
} |
126 |
|
|
/ * re-enstate the existent incoming min and/or max values * / |
127 |
|
|
if (AIR_EXISTS(min)) |
128 |
|
|
nrrd->min = min; |
129 |
|
|
if (AIR_EXISTS(max)) |
130 |
|
|
nrrd->max = max; |
131 |
|
|
|
132 |
|
|
return 0; |
133 |
|
|
} |
134 |
|
|
*/ |
135 |
|
|
|
136 |
|
|
static int |
137 |
|
|
clampRoundConvert(Nrrd *nout, const Nrrd *nin, int type, |
138 |
|
|
int doClamp, int roundDir) { |
139 |
|
|
static const char me[]="clampRoundConvert"; |
140 |
|
132 |
char typeS[AIR_STRLEN_SMALL]; |
141 |
|
66 |
size_t num, size[NRRD_DIM_MAX]; |
142 |
|
|
|
143 |
✓✗✗✓
|
132 |
if (!( nin && nout |
144 |
|
66 |
&& !nrrdCheck(nin) |
145 |
✓✗ |
132 |
&& !airEnumValCheck(nrrdType, type) )) { |
146 |
|
|
biffAddf(NRRD, "%s: invalid args", me); |
147 |
|
|
return 1; |
148 |
|
|
} |
149 |
✗✓ |
66 |
if (nin->type == nrrdTypeBlock || type == nrrdTypeBlock) { |
150 |
|
|
biffAddf(NRRD, "%s: can't convert to or from nrrd type %s", me, |
151 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
152 |
|
|
return 1; |
153 |
|
|
} |
154 |
✓✓✗✓
|
74 |
if (nout == nin && nrrdTypeSize[type] != nrrdTypeSize[nin->type]) { |
155 |
|
|
biffAddf(NRRD, "%s: nout==nin but input,output type sizes unequal", me); |
156 |
|
|
return 1; |
157 |
|
|
} |
158 |
✗✓ |
122 |
if (nrrdStateDisallowIntegerNonExist |
159 |
✓✗✓✓
|
188 |
&& !nrrdTypeIsIntegral[nin->type] && nrrdTypeIsIntegral[type]) { |
160 |
|
|
/* there's a risk of non-existent values getting converted to |
161 |
|
|
non-sensical integral values */ |
162 |
|
|
if (nrrdHasNonExist(nin)) { |
163 |
|
|
biffAddf(NRRD, "%s: can't convert to integral values (%s) with " |
164 |
|
|
"non-existent values in input", me, |
165 |
|
|
airEnumStr(nrrdType, type)); |
166 |
|
|
return 1; |
167 |
|
|
} |
168 |
|
|
} |
169 |
|
|
|
170 |
|
|
/* if we're actually converting to the same type, just do a copy */ |
171 |
✓✓ |
66 |
if (type == nin->type) { |
172 |
✓✓ |
41 |
if (nout == nin) { |
173 |
|
|
/* nout == nin is allowed if the input and output type are |
174 |
|
|
of the same size, which will certainly be the case if the |
175 |
|
|
input and output types are identical, so there's actually |
176 |
|
|
no work to do */ |
177 |
|
|
} else { |
178 |
✗✓ |
33 |
if (nrrdCopy(nout, nin)) { |
179 |
|
|
biffAddf(NRRD, "%s: couldn't copy input to output", me); |
180 |
|
|
return 1; |
181 |
|
|
} |
182 |
|
|
} |
183 |
|
|
} else { |
184 |
|
|
/* allocate space if necessary */ |
185 |
|
25 |
nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, size); |
186 |
|
|
/* MUST be nrrdMaybeAlloc_nva (not nrrd Alloc_nva) because we allow |
187 |
|
|
nout==nin if type sizes match */ |
188 |
✗✓ |
25 |
if (nrrdMaybeAlloc_nva(nout, type, nin->dim, size)) { |
189 |
|
|
biffAddf(NRRD, "%s: failed to allocate output", me); |
190 |
|
|
return 1; |
191 |
|
|
} |
192 |
|
|
|
193 |
|
|
/* call the appropriate converter */ |
194 |
|
25 |
num = nrrdElementNumber(nin); |
195 |
✗✓ |
25 |
if (roundDir) { |
196 |
|
|
_nrrdCastClampRound[nout->type][nin->type](nout->data, nin->data, num, |
197 |
|
|
doClamp, roundDir); |
198 |
✗✓✗✓
|
50 |
} else if (doClamp) { |
199 |
|
25 |
_nrrdClampConv[nout->type][nin->type](nout->data, nin->data, num); |
200 |
|
|
} else { |
201 |
|
25 |
_nrrdConv[nout->type][nin->type](nout->data, nin->data, num); |
202 |
|
|
} |
203 |
|
25 |
nout->blockSize = 0; |
204 |
|
|
|
205 |
|
|
/* copy peripheral information */ |
206 |
|
25 |
nrrdAxisInfoCopy(nout, nin, NULL, NRRD_AXIS_INFO_NONE); |
207 |
|
25 |
sprintf(typeS, "(%s)", airEnumStr(nrrdType, nout->type)); |
208 |
✗✓ |
25 |
if (nrrdContentSet_va(nout, typeS, nin, "")) { |
209 |
|
|
biffAddf(NRRD, "%s:", me); |
210 |
|
|
return 1; |
211 |
|
|
} |
212 |
|
|
/* the min and max have probably changed if there was a conversion |
213 |
|
|
to integral values, or to a lower precision representation */ |
214 |
✗✓ |
25 |
if (nrrdBasicInfoCopy(nout, nin, |
215 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
216 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
217 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
218 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
219 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
220 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
221 |
|
25 |
| (nrrdStateKeyValuePairsPropagate |
222 |
|
|
? 0 |
223 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
224 |
|
|
biffAddf(NRRD, "%s:", me); |
225 |
|
|
return 1; |
226 |
|
|
} |
227 |
|
|
} |
228 |
|
66 |
return 0; |
229 |
|
66 |
} |
230 |
|
|
|
231 |
|
|
/* |
232 |
|
|
******** nrrdConvert() |
233 |
|
|
** |
234 |
|
|
** copies values from one type of nrrd to another, without any |
235 |
|
|
** transformation, except what you get with a cast. The point is to |
236 |
|
|
** make available on Nrrds the exact same behavior as you have in C |
237 |
|
|
** with casts and assignments. |
238 |
|
|
*/ |
239 |
|
|
int |
240 |
|
|
nrrdConvert(Nrrd *nout, const Nrrd *nin, int type) { |
241 |
|
|
static const char me[]="nrrdConvert"; |
242 |
|
|
|
243 |
✗✓ |
132 |
if (clampRoundConvert(nout, nin, type, |
244 |
|
|
AIR_FALSE /* clamp */, |
245 |
|
|
0 /* round */)) { |
246 |
|
|
biffAddf(NRRD, "%s: trouble", me); |
247 |
|
|
return 1; |
248 |
|
|
} |
249 |
|
66 |
return 0; |
250 |
|
66 |
} |
251 |
|
|
|
252 |
|
|
/* |
253 |
|
|
******** nrrdClampConvert() |
254 |
|
|
** |
255 |
|
|
** same as nrrdConvert, but with clamping to output value representation range |
256 |
|
|
** so as to avoid wrap-around artifacts |
257 |
|
|
** |
258 |
|
|
** HEY: WARNING: may have loss of data when processing long long |
259 |
|
|
** (either signed or unsigned) |
260 |
|
|
*/ |
261 |
|
|
int |
262 |
|
|
nrrdClampConvert(Nrrd *nout, const Nrrd *nin, int type) { |
263 |
|
|
static const char me[]="nrrdClampConvert"; |
264 |
|
|
|
265 |
|
|
if (clampRoundConvert(nout, nin, type, |
266 |
|
|
AIR_TRUE /* clamp */, |
267 |
|
|
0 /* round */)) { |
268 |
|
|
biffAddf(NRRD, "%s: trouble", me); |
269 |
|
|
return 1; |
270 |
|
|
} |
271 |
|
|
return 0; |
272 |
|
|
} |
273 |
|
|
|
274 |
|
|
/* |
275 |
|
|
******** nrrdCastClampRound() |
276 |
|
|
** |
277 |
|
|
** For (maybe) doing rounding to integer, then (maybe) clamping |
278 |
|
|
** and then casting. The maybe-ness of rounding and clamping |
279 |
|
|
** in this function is inconsistent with the certainty of clamping |
280 |
|
|
** nrrdClampConvert, so this function name may be regretted. |
281 |
|
|
** |
282 |
|
|
** NOTE! Rounding is not performed when outType is for float |
283 |
|
|
** or double! That logic is implemented here. |
284 |
|
|
** |
285 |
|
|
** And warning same as above: |
286 |
|
|
** HEY: WARNING: may have loss of data when processing long long |
287 |
|
|
** (either signed or unsigned) |
288 |
|
|
*/ |
289 |
|
|
int |
290 |
|
|
nrrdCastClampRound(Nrrd *nout, const Nrrd *nin, int outType, |
291 |
|
|
int doClamp, int roundDir) { |
292 |
|
|
static const char me[]="nrrdCastClampRound"; |
293 |
|
|
|
294 |
|
|
if (clampRoundConvert(nout, nin, outType, doClamp, |
295 |
|
|
nrrdTypeIsIntegral[outType] ? roundDir : 0)) { |
296 |
|
|
biffAddf(NRRD, "%s: trouble", me); |
297 |
|
|
return 1; |
298 |
|
|
} |
299 |
|
|
return 0; |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
/* |
303 |
|
|
******** nrrdQuantize() |
304 |
|
|
** |
305 |
|
|
** convert values to 8, 16, or 32 bit unsigned quantities |
306 |
|
|
** by mapping the value range delimited by the nrrd's min |
307 |
|
|
** and max to the representable range |
308 |
|
|
** |
309 |
|
|
** NOTE: for the time being, this uses a "double" as the intermediate |
310 |
|
|
** value holder, which may mean needless loss of precision |
311 |
|
|
*/ |
312 |
|
|
int |
313 |
|
|
nrrdQuantize(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, |
314 |
|
|
unsigned int bits) { |
315 |
|
|
static const char me[]="nrrdQuantize", func[]="quantize"; |
316 |
|
|
double valIn, minIn, maxIn, eps; |
317 |
|
|
int type=nrrdTypeUnknown; |
318 |
|
2 |
size_t I, num, size[NRRD_DIM_MAX]; |
319 |
|
|
unsigned char *outUC; |
320 |
|
|
unsigned short *outUS; |
321 |
|
|
unsigned int *outUI; |
322 |
|
|
airArray *mop; |
323 |
|
|
NrrdRange *range; |
324 |
|
|
|
325 |
✗✓ |
1 |
if (!(nin && nout)) { |
326 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
327 |
|
|
return 1; |
328 |
|
|
} |
329 |
✗✓ |
1 |
if (nrrdTypeBlock == nin->type) { |
330 |
|
|
biffAddf(NRRD, "%s: can't quantize type %s", me, |
331 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
332 |
|
|
return 1; |
333 |
|
|
} |
334 |
|
|
|
335 |
|
|
/* determine nrrd type from number of bits */ |
336 |
✓✗✗✗
|
1 |
switch (bits) { |
337 |
|
1 |
case 8: type = nrrdTypeUChar; break; |
338 |
|
|
case 16: type = nrrdTypeUShort; break; |
339 |
|
|
case 32: type = nrrdTypeUInt; break; |
340 |
|
|
default: |
341 |
|
|
biffAddf(NRRD, "%s: bits has to be 8, 16, or 32 (not %d)", me, bits); |
342 |
|
|
return 1; |
343 |
|
|
} |
344 |
✗✓✗✗
|
1 |
if (nout == nin && nrrdTypeSize[type] != nrrdTypeSize[nin->type]) { |
345 |
|
|
biffAddf(NRRD, "%s: nout==nin but input,output type sizes unequal", me); |
346 |
|
|
return 1; |
347 |
|
|
} |
348 |
|
1 |
mop = airMopNew(); |
349 |
✗✓ |
1 |
if (_range) { |
350 |
|
|
range = nrrdRangeCopy(_range); |
351 |
|
|
nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); |
352 |
|
|
} else { |
353 |
|
1 |
range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); |
354 |
|
|
} |
355 |
|
1 |
airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); |
356 |
✓✗✗✓
|
2 |
if (nrrdStateDisallowIntegerNonExist && range->hasNonExist) { |
357 |
|
|
biffAddf(NRRD, "%s: can't quantize non-existent values " |
358 |
|
|
"(NaN, +/-inf)", me); |
359 |
|
|
airMopError(mop); return 1; |
360 |
|
|
} |
361 |
|
|
|
362 |
|
|
/* allocate space if necessary */ |
363 |
|
1 |
nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, size); |
364 |
|
|
/* MUST be nrrdMaybeAlloc_nva (not nrrd Alloc_nva) because we allow |
365 |
|
|
nout==nin if type sizes match */ |
366 |
✗✓ |
1 |
if (nrrdMaybeAlloc_nva(nout, type, nin->dim, size)) { |
367 |
|
|
biffAddf(NRRD, "%s: failed to create output", me); |
368 |
|
|
airMopError(mop); return 1; |
369 |
|
|
} |
370 |
|
|
|
371 |
|
|
/* the skinny */ |
372 |
|
2 |
num = nrrdElementNumber(nin); |
373 |
|
2 |
minIn = range->min; |
374 |
|
2 |
maxIn = range->max; |
375 |
|
2 |
eps = (minIn == maxIn ? 1.0 : 0.0); |
376 |
|
2 |
outUC = (unsigned char*)nout->data; |
377 |
|
2 |
outUS = (unsigned short*)nout->data; |
378 |
|
2 |
outUI = (unsigned int*)nout->data; |
379 |
✓✗✗✓
|
2 |
switch(bits) { |
380 |
|
|
case 8: |
381 |
✓✓ |
256202 |
for (I=0; I<num; I++) { |
382 |
|
128100 |
valIn = nrrdDLookup[nin->type](nin->data, I); |
383 |
|
128100 |
outUC[I] = airIndexClamp(minIn, valIn, maxIn+eps, 1 << 8); |
384 |
|
|
} |
385 |
|
|
break; |
386 |
|
|
case 16: |
387 |
|
|
for (I=0; I<num; I++) { |
388 |
|
|
valIn = nrrdDLookup[nin->type](nin->data, I); |
389 |
|
|
outUS[I] = airIndexClamp(minIn, valIn, maxIn+eps, 1 << 16); |
390 |
|
|
} |
391 |
|
|
break; |
392 |
|
|
case 32: |
393 |
|
|
for (I=0; I<num; I++) { |
394 |
|
|
valIn = nrrdDLookup[nin->type](nin->data, I); |
395 |
|
|
outUI[I] = AIR_CAST(unsigned int, |
396 |
|
|
airIndexClampULL(minIn, valIn, maxIn+eps, |
397 |
|
|
AIR_ULLONG(1) << 32)); |
398 |
|
|
} |
399 |
|
|
break; |
400 |
|
|
} |
401 |
|
|
|
402 |
|
|
/* set information in new volume */ |
403 |
✓✗ |
1 |
if (nout != nin) { |
404 |
|
1 |
nrrdAxisInfoCopy(nout, nin, NULL, NRRD_AXIS_INFO_NONE); |
405 |
|
1 |
} |
406 |
✗✓ |
1 |
if (nrrdContentSet_va(nout, func, nin, "%d", bits)) { |
407 |
|
|
biffAddf(NRRD, "%s:", me); |
408 |
|
|
airMopError(mop); return 1; |
409 |
|
|
} |
410 |
✗✓ |
1 |
if (nrrdBasicInfoCopy(nout, nin, |
411 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
412 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
413 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
414 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
415 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
416 |
|
|
| NRRD_BASIC_INFO_OLDMIN_BIT |
417 |
|
|
| NRRD_BASIC_INFO_OLDMAX_BIT |
418 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
419 |
|
1 |
| (nrrdStateKeyValuePairsPropagate |
420 |
|
|
? 0 |
421 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
422 |
|
|
biffAddf(NRRD, "%s:", me); |
423 |
|
|
airMopError(mop); return 1; |
424 |
|
|
} |
425 |
|
1 |
nout->oldMin = minIn; |
426 |
|
1 |
nout->oldMax = maxIn; |
427 |
|
1 |
nout->blockSize = 0; |
428 |
|
|
|
429 |
|
1 |
airMopOkay(mop); |
430 |
|
1 |
return 0; |
431 |
|
1 |
} |
432 |
|
|
|
433 |
|
|
/* |
434 |
|
|
** _nrrdTypeNumberOfValues[] |
435 |
|
|
** |
436 |
|
|
** only meaningful for integral values, and only correct for |
437 |
|
|
** 32-bit values; tells the number of different integral values that |
438 |
|
|
** can be represented by the type |
439 |
|
|
** |
440 |
|
|
** HEY: this used to be public, but that was stopped when it was clear |
441 |
|
|
** that only the following function was using the array. The reason |
442 |
|
|
** for the array is questionable, and the implementation below |
443 |
|
|
** should be re-evaluated. |
444 |
|
|
*/ |
445 |
|
|
static const double |
446 |
|
|
_nrrdTypeNumberOfValues[NRRD_TYPE_MAX+1] = { |
447 |
|
|
0, /* unknown */ |
448 |
|
|
UCHAR_MAX+1, /* char */ |
449 |
|
|
UCHAR_MAX+1, /* unsigned char */ |
450 |
|
|
USHRT_MAX+1, /* short */ |
451 |
|
|
USHRT_MAX+1, /* unsigned short */ |
452 |
|
|
(double)UINT_MAX+1, /* int */ |
453 |
|
|
(double)UINT_MAX+1, /* unsigned int */ |
454 |
|
|
(double)NRRD_ULLONG_MAX+1, /* long long */ |
455 |
|
|
(double)NRRD_ULLONG_MAX+1, /* unsigned long long */ |
456 |
|
|
0, /* float */ |
457 |
|
|
0, /* double */ |
458 |
|
|
0 /* punt */ |
459 |
|
|
}; |
460 |
|
|
|
461 |
|
|
/* |
462 |
|
|
******** nrrdUnquantize() |
463 |
|
|
** |
464 |
|
|
** try to recover floating point values from a quantized nrrd, |
465 |
|
|
** using the oldMin and oldMax values, if they exist. If they |
466 |
|
|
** don't exist, the output range will be 0.0 to 1.0. However, |
467 |
|
|
** because we're using NRRD_CELL_POS to recover values, |
468 |
|
|
** the output values will never be exactly 0.0 to 1.0 (or oldMin |
469 |
|
|
** to oldMax). In unsigned char data, for instance, the value |
470 |
|
|
** V will be mapped to: |
471 |
|
|
** NRRD_CELL_POS(0.0, 1.0, 256, V) == |
472 |
|
|
** AIR_AFFINE(0, V + 0.5, 256, 0.0, 1.0) == |
473 |
|
|
** ((double)(1.0)-(0.0))*((double)(V+0.5)-(0))/((double)(256)-(0)) + (0.0) == |
474 |
|
|
** (1.0)*(V+0.5) / (256.0) + (0.0) == |
475 |
|
|
** (V+0.5)/256 |
476 |
|
|
** so a 0 will be mapped to 1/512 = 0.00195 |
477 |
|
|
*/ |
478 |
|
|
int |
479 |
|
|
nrrdUnquantize(Nrrd *nout, const Nrrd *nin, int type) { |
480 |
|
|
static const char me[]="nrrdUnquantize", func[]="unquantize"; |
481 |
|
|
float *outF; |
482 |
|
|
double *outD, minIn, numValIn, minOut, maxOut, valIn; |
483 |
|
2 |
size_t NN, II, size[NRRD_DIM_MAX]; |
484 |
|
|
|
485 |
✗✓ |
1 |
if (!(nout && nin)) { |
486 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
487 |
|
|
return 1; |
488 |
|
|
} |
489 |
✗✓ |
1 |
if (airEnumValCheck(nrrdType, type)) { |
490 |
|
|
biffAddf(NRRD, "%s: don't recognize type %d\n", me, type); |
491 |
|
|
return 1; |
492 |
|
|
} |
493 |
✗✓ |
1 |
if (!( type == nrrdTypeFloat || type == nrrdTypeDouble )) { |
494 |
|
|
biffAddf(NRRD, "%s: output type must be %s or %s (not %s)", me, |
495 |
|
|
airEnumStr(nrrdType, nrrdTypeFloat), |
496 |
|
|
airEnumStr(nrrdType, nrrdTypeDouble), |
497 |
|
|
airEnumStr(nrrdType, type)); |
498 |
|
|
return 1; |
499 |
|
|
} |
500 |
✗✓ |
1 |
if (nrrdTypeBlock == nin->type) { |
501 |
|
|
biffAddf(NRRD, "%s: can't unquantize type %s", me, |
502 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
503 |
|
|
return 1; |
504 |
|
|
} |
505 |
✗✓ |
1 |
if (!nrrdTypeIsIntegral[nin->type]) { |
506 |
|
|
biffAddf(NRRD, "%s: can only unquantize integral types, not %s", me, |
507 |
|
|
airEnumStr(nrrdType, nin->type)); |
508 |
|
|
return 1; |
509 |
|
|
} |
510 |
✗✓✗✗
|
1 |
if (nout == nin && nrrdTypeSize[type] != nrrdTypeSize[nin->type]) { |
511 |
|
|
biffAddf(NRRD, "%s: nout==nin but input,output type sizes unequal", me); |
512 |
|
|
return 1; |
513 |
|
|
} |
514 |
|
|
|
515 |
|
1 |
nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, size); |
516 |
✗✓ |
1 |
if (nrrdMaybeAlloc_nva(nout, type, nin->dim, size)) { |
517 |
|
|
biffAddf(NRRD, "%s: failed to create output", me); |
518 |
|
|
return 1; |
519 |
|
|
} |
520 |
|
1 |
minIn = nrrdTypeMin[nin->type]; |
521 |
|
1 |
numValIn = _nrrdTypeNumberOfValues[nin->type]; |
522 |
✓✗✓✗
|
2 |
if (AIR_EXISTS(nin->oldMin) && AIR_EXISTS(nin->oldMax)) { |
523 |
|
|
minOut = nin->oldMin; |
524 |
|
|
maxOut = nin->oldMax; |
525 |
|
1 |
} else { |
526 |
|
|
minOut = 0.0; |
527 |
|
|
maxOut = 1.0; |
528 |
|
|
} |
529 |
|
2 |
outF = (float*)nout->data; |
530 |
|
2 |
outD = (double*)nout->data; |
531 |
|
2 |
NN = nrrdElementNumber(nin); |
532 |
✗✓✓ |
2 |
switch(type) { |
533 |
|
|
case nrrdTypeFloat: |
534 |
|
|
for (II=0; II<NN; II++) { |
535 |
|
|
valIn = minIn + nrrdDLookup[nin->type](nin->data, II); |
536 |
|
|
outF[II] = AIR_CAST(float, |
537 |
|
|
NRRD_CELL_POS(minOut, maxOut, numValIn, valIn)); |
538 |
|
|
} |
539 |
|
|
break; |
540 |
|
|
case nrrdTypeDouble: |
541 |
✓✓ |
256202 |
for (II=0; II<NN; II++) { |
542 |
|
128100 |
valIn = minIn + nrrdDLookup[nin->type](nin->data, II); |
543 |
|
128100 |
outD[II] = NRRD_CELL_POS(minOut, maxOut, numValIn, valIn); |
544 |
|
|
} |
545 |
|
|
break; |
546 |
|
|
} |
547 |
|
|
|
548 |
|
|
/* set information in new volume */ |
549 |
✓✗ |
1 |
if (nout != nin) { |
550 |
|
1 |
nrrdAxisInfoCopy(nout, nin, NULL, NRRD_AXIS_INFO_NONE); |
551 |
|
1 |
} |
552 |
✗✓ |
1 |
if (nrrdContentSet_va(nout, func, nin, "")) { |
553 |
|
|
biffAddf(NRRD, "%s:", me); |
554 |
|
|
return 1; |
555 |
|
|
} |
556 |
✗✓ |
1 |
if (nrrdBasicInfoCopy(nout, nin, |
557 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
558 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
559 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
560 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
561 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
562 |
|
|
| NRRD_BASIC_INFO_OLDMIN_BIT |
563 |
|
|
| NRRD_BASIC_INFO_OLDMAX_BIT |
564 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
565 |
|
1 |
| (nrrdStateKeyValuePairsPropagate |
566 |
|
|
? 0 |
567 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
568 |
|
|
biffAddf(NRRD, "%s:", me); |
569 |
|
|
return 1; |
570 |
|
|
} |
571 |
|
1 |
nout->oldMin = nout->oldMax = AIR_NAN; |
572 |
|
1 |
nout->blockSize = 0; |
573 |
|
1 |
return 0; |
574 |
|
1 |
} |
575 |
|
|
|
576 |
|
|
|
577 |
|
|
/* |
578 |
|
|
** _nrrdHistoEqCompare() |
579 |
|
|
** |
580 |
|
|
** used by nrrdHistoEq in smart mode to sort the "steady" array |
581 |
|
|
** in _descending_ order |
582 |
|
|
*/ |
583 |
|
|
int |
584 |
|
|
_nrrdHistoEqCompare(const void *a, const void *b) { |
585 |
|
|
|
586 |
|
|
return *((const unsigned int*)b) - *((const unsigned int*)a); |
587 |
|
|
} |
588 |
|
|
|
589 |
|
|
/* |
590 |
|
|
******** nrrdHistoEq() |
591 |
|
|
** |
592 |
|
|
** performs histogram equalization on given nrrd, treating it as a |
593 |
|
|
** big one-dimensional array. The procedure is as follows: |
594 |
|
|
** - create a histogram of nrrd (using "bins" bins) |
595 |
|
|
** - integrate the histogram, and normalize and shift this so it is |
596 |
|
|
** a monotonically increasing function from min to max, where |
597 |
|
|
** (min,max) is the range of values in the nrrd |
598 |
|
|
** - map the values in the nrrd through the adjusted histogram integral |
599 |
|
|
** |
600 |
|
|
** If the histogram of the given nrrd is already as flat as can be, |
601 |
|
|
** the histogram integral will increase linearly, and the adjusted |
602 |
|
|
** histogram integral should be close to the identity function, so |
603 |
|
|
** the values shouldn't change much. |
604 |
|
|
** |
605 |
|
|
** If the nhistP arg is non-NULL, then it is set to point to |
606 |
|
|
** the histogram that was used for calculation. Otherwise this |
607 |
|
|
** histogram is deleted on return. |
608 |
|
|
** |
609 |
|
|
** This is all that is done normally, when "smart" is == 0. In |
610 |
|
|
** "smart" mode (activated by setting "smart" to something greater |
611 |
|
|
** than 0), the histogram is analyzed during its creation to detect if |
612 |
|
|
** there are a few bins which keep getting hit with the same value |
613 |
|
|
** over and over. It may be desirable to ignore these bins in the |
614 |
|
|
** histogram integral because they may not contain any useful |
615 |
|
|
** information, and so they should not effect how values are |
616 |
|
|
** re-mapped. The value of "smart" is the number of bins that will be |
617 |
|
|
** ignored. For instance, use the value 1 if the problem with naive |
618 |
|
|
** histogram equalization is a large amount of background (which is |
619 |
|
|
** exactly one fixed value). |
620 |
|
|
*/ |
621 |
|
|
int |
622 |
|
|
nrrdHistoEq(Nrrd *nout, const Nrrd *nin, Nrrd **nmapP, |
623 |
|
|
unsigned int bins, unsigned int smart, float amount) { |
624 |
|
|
static const char me[]="nrrdHistoEq", func[]="heq"; |
625 |
|
|
Nrrd *nhist, *nmap; |
626 |
|
|
double val, min, max, *last = NULL, *ycoord = NULL; |
627 |
|
|
int *respect = NULL, lort; |
628 |
|
|
unsigned int *hist, *steady = NULL, idx, hirt; |
629 |
|
|
size_t I, num; |
630 |
|
|
airArray *mop; |
631 |
|
|
NrrdRange *range; |
632 |
|
|
unsigned bii; |
633 |
|
|
|
634 |
|
|
if (!(nout && nin)) { |
635 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
636 |
|
|
return 1; |
637 |
|
|
} |
638 |
|
|
if (nrrdTypeBlock == nin->type) { |
639 |
|
|
biffAddf(NRRD, "%s: can't histogram equalize type %s", me, |
640 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
641 |
|
|
return 1; |
642 |
|
|
} |
643 |
|
|
if (!(bins > 4)) { |
644 |
|
|
biffAddf(NRRD, "%s: need # bins > 4 (not %d)", me, bins); |
645 |
|
|
return 1; |
646 |
|
|
} |
647 |
|
|
/* we start by simply copying, because the only thing we're |
648 |
|
|
changing is the values themselves, and all peripheral |
649 |
|
|
information is unchanged by this value remapping */ |
650 |
|
|
if (nout != nin) { |
651 |
|
|
if (nrrdCopy(nout, nin)) { |
652 |
|
|
biffAddf(NRRD, "%s:", me); |
653 |
|
|
return 1; |
654 |
|
|
} |
655 |
|
|
} |
656 |
|
|
|
657 |
|
|
mop = airMopNew(); |
658 |
|
|
if (nmapP) { |
659 |
|
|
airMopAdd(mop, nmapP, (airMopper)airSetNull, airMopOnError); |
660 |
|
|
} |
661 |
|
|
num = nrrdElementNumber(nin); |
662 |
|
|
if (smart <= 0) { |
663 |
|
|
nhist = nrrdNew(); |
664 |
|
|
if (nrrdHisto(nhist, nin, NULL, NULL, bins, nrrdTypeUInt)) { |
665 |
|
|
biffAddf(NRRD, "%s: failed to create histogram", me); |
666 |
|
|
airMopError(mop); return 1; |
667 |
|
|
} |
668 |
|
|
airMopAdd(mop, nhist, (airMopper)nrrdNuke, airMopAlways); |
669 |
|
|
hist = (unsigned int*)nhist->data; |
670 |
|
|
min = nhist->axis[0].min; |
671 |
|
|
max = nhist->axis[0].max; |
672 |
|
|
} else { |
673 |
|
|
/* for "smart" mode, we have to some extra work while creating the |
674 |
|
|
histogram to look for bins incessantly hit with the exact same |
675 |
|
|
value */ |
676 |
|
|
if (nrrdMaybeAlloc_va(nhist=nrrdNew(), nrrdTypeUInt, 1, |
677 |
|
|
AIR_CAST(size_t, bins))) { |
678 |
|
|
biffAddf(NRRD, "%s: failed to allocate histogram", me); |
679 |
|
|
airMopError(mop); return 1; |
680 |
|
|
} |
681 |
|
|
airMopAdd(mop, nhist, (airMopper)nrrdNuke, airMopAlways); |
682 |
|
|
hist = (unsigned int*)nhist->data; |
683 |
|
|
nhist->axis[0].size = bins; |
684 |
|
|
/* allocate the respect, steady, and last arrays */ |
685 |
|
|
respect = (int*)calloc(bins, sizeof(int)); |
686 |
|
|
steady = (unsigned int*)calloc(2*bins, sizeof(unsigned int)); |
687 |
|
|
last = (double*)calloc(bins, sizeof(double)); |
688 |
|
|
airMopMem(mop, &respect, airMopAlways); |
689 |
|
|
airMopMem(mop, &steady, airMopAlways); |
690 |
|
|
airMopMem(mop, &last, airMopAlways); |
691 |
|
|
if (!(respect && steady && last)) { |
692 |
|
|
biffAddf(NRRD, "%s: couldn't allocate smart arrays", me); |
693 |
|
|
airMopError(mop); return 1; |
694 |
|
|
} |
695 |
|
|
/* steady[0 + 2*bii] == how many times has bin bii seen the same value |
696 |
|
|
steady[1 + 2*bii] == bii (steady will be rearranged by qsort()) */ |
697 |
|
|
for (bii=0; bii<bins; bii++) { |
698 |
|
|
last[bii] = AIR_NAN; |
699 |
|
|
respect[bii] = 1; |
700 |
|
|
steady[1 + 2*bii] = bii; |
701 |
|
|
} |
702 |
|
|
/* now create the histogram */ |
703 |
|
|
range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); |
704 |
|
|
airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); |
705 |
|
|
if (range->min == range->max) { |
706 |
|
|
biffAddf(NRRD, "%s: invalid min and max in nrrd. " |
707 |
|
|
"Min and max are equivalent (min,max = %g).", me, range->min); |
708 |
|
|
airMopError(mop); return 1; |
709 |
|
|
} |
710 |
|
|
min = range->min; |
711 |
|
|
max = range->max; |
712 |
|
|
for (I=0; I<num; I++) { |
713 |
|
|
val = nrrdDLookup[nin->type](nin->data, I); |
714 |
|
|
if (AIR_EXISTS(val)) { |
715 |
|
|
idx = airIndex(min, val, max, bins); |
716 |
|
|
++hist[idx]; |
717 |
|
|
if (AIR_EXISTS(last[idx])) { |
718 |
|
|
steady[0 + 2*idx] = (last[idx] == val |
719 |
|
|
? 1 + steady[0 + 2*idx] |
720 |
|
|
: 0); |
721 |
|
|
} |
722 |
|
|
last[idx] = val; |
723 |
|
|
} |
724 |
|
|
} |
725 |
|
|
/* now sort the steady array */ |
726 |
|
|
qsort(steady, bins, 2*sizeof(unsigned int), _nrrdHistoEqCompare); |
727 |
|
|
/* we ignore some of the bins according to "smart" arg */ |
728 |
|
|
for (bii=0; bii<smart; bii++) { |
729 |
|
|
respect[steady[1+2*bii]] = 0; |
730 |
|
|
/* printf("%s: disrespecting bin %d\n", me, steady[1+2*bii]); */ |
731 |
|
|
} |
732 |
|
|
} |
733 |
|
|
if (nrrdMaybeAlloc_va(nmap=nrrdNew(), nrrdTypeDouble, 1, |
734 |
|
|
AIR_CAST(size_t, bins+1))) { |
735 |
|
|
biffAddf(NRRD, "%s: failed to create map nrrd", me); |
736 |
|
|
airMopError(mop); return 1; |
737 |
|
|
} |
738 |
|
|
airMopAdd(mop, nmap, (airMopper)nrrdNuke, |
739 |
|
|
nmapP ? airMopOnError : airMopAlways); |
740 |
|
|
ycoord = AIR_CAST(double*, nmap->data); |
741 |
|
|
nmap->axis[0].min = min; |
742 |
|
|
nmap->axis[0].max = max; |
743 |
|
|
|
744 |
|
|
/* integrate the histogram then normalize it */ |
745 |
|
|
for (bii=0; bii<=bins; bii++) { |
746 |
|
|
if (bii == 0) { |
747 |
|
|
ycoord[bii] = 0; |
748 |
|
|
} else { |
749 |
|
|
ycoord[bii] = ycoord[bii-1] + hist[bii-1]*(smart |
750 |
|
|
? respect[bii-1] |
751 |
|
|
: 1); |
752 |
|
|
} |
753 |
|
|
} |
754 |
|
|
/* if we've done smart, the integral will have little flat spots |
755 |
|
|
where we ignored hits in the histogram. That means the mapping |
756 |
|
|
will actually be lower at that value than it should be. In |
757 |
|
|
truth, we should be using an irregular mapping for this, and the |
758 |
|
|
control points at the ignored bins should just be missing. So we |
759 |
|
|
have to do this silliness to raise those control points in the |
760 |
|
|
regular map. */ |
761 |
|
|
if (smart) { |
762 |
|
|
/* there are bins+1 control points, with indices 0 to bins. |
763 |
|
|
We'll fix control points 1 to bins-1. ycoord[bii] is too low |
764 |
|
|
if hist[bii-1] was not respected (!respect[bii-1]) */ |
765 |
|
|
for (bii=1; bii<=bins-1; bii++) { |
766 |
|
|
if (!respect[bii-1]) { |
767 |
|
|
/* lort and hirt will bracket the index of the bad control point |
768 |
|
|
with points corresponding either to respected bins or the |
769 |
|
|
endpoints of the histogram */ |
770 |
|
|
for (lort=bii; lort>=1 && !respect[lort-1]; lort--) |
771 |
|
|
; |
772 |
|
|
for (hirt=bii; hirt<bins && !respect[hirt-1]; hirt++) |
773 |
|
|
; |
774 |
|
|
ycoord[bii] = AIR_AFFINE(lort, bii, hirt, |
775 |
|
|
ycoord[lort], ycoord[hirt]); |
776 |
|
|
} |
777 |
|
|
} |
778 |
|
|
/* the very last control point has to be handled differently */ |
779 |
|
|
if (!respect[bins-1]) { |
780 |
|
|
ycoord[bins] += ycoord[bins-1] - ycoord[bins-2]; |
781 |
|
|
} |
782 |
|
|
} |
783 |
|
|
/* rescale the histogram integration to span the original |
784 |
|
|
value range, and affect the influence of "amount" */ |
785 |
|
|
for (bii=0; bii<=bins; bii++) { |
786 |
|
|
ycoord[bii] = AIR_AFFINE(0.0, ycoord[bii], ycoord[bins], min, max); |
787 |
|
|
ycoord[bii] = AIR_AFFINE(0.0, amount, 1.0, |
788 |
|
|
AIR_AFFINE(0, bii, bins, min, max), |
789 |
|
|
ycoord[bii]); |
790 |
|
|
} |
791 |
|
|
|
792 |
|
|
/* map the nrrd values through the normalized histogram integral */ |
793 |
|
|
if (nrrdApply1DRegMap(nout, nin, NULL, nmap, nin->type, AIR_FALSE)) { |
794 |
|
|
biffAddf(NRRD, "%s: problem remapping", me); |
795 |
|
|
airMopError(mop); return 1; |
796 |
|
|
} |
797 |
|
|
/* |
798 |
|
|
for (I=0; I<num; I++) { |
799 |
|
|
val = nrrdDLookup[nin->type](nin->data, I); |
800 |
|
|
if (AIR_EXISTS(val)) { |
801 |
|
|
AIR_INDEX(min, val, max, bins, idx); |
802 |
|
|
val = AIR_AFFINE(xcoord[idx], val, xcoord[idx+1], |
803 |
|
|
ycoord[idx], ycoord[idx+1]); |
804 |
|
|
} |
805 |
|
|
nrrdDInsert[nout->type](nout->data, I, val); |
806 |
|
|
} |
807 |
|
|
*/ |
808 |
|
|
|
809 |
|
|
/* if user is interested, set pointer to map nrrd, |
810 |
|
|
otherwise it will be nixed by airMop */ |
811 |
|
|
if (nmapP) { |
812 |
|
|
*nmapP = nmap; |
813 |
|
|
} |
814 |
|
|
|
815 |
|
|
/* fiddling with content is the only thing we'll do */ |
816 |
|
|
if (nrrdContentSet_va(nout, func, nin, "%d,%d", bins, smart)) { |
817 |
|
|
biffAddf(NRRD, "%s:", me); |
818 |
|
|
airMopError(mop); return 1; |
819 |
|
|
} |
820 |
|
|
if (nrrdBasicInfoCopy(nout, nin, |
821 |
|
|
NRRD_BASIC_INFO_DATA_BIT |
822 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
823 |
|
|
| NRRD_BASIC_INFO_BLOCKSIZE_BIT |
824 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
825 |
|
|
| NRRD_BASIC_INFO_CONTENT_BIT |
826 |
|
|
| NRRD_BASIC_INFO_COMMENTS_BIT |
827 |
|
|
| (nrrdStateKeyValuePairsPropagate |
828 |
|
|
? 0 |
829 |
|
|
: NRRD_BASIC_INFO_KEYVALUEPAIRS_BIT))) { |
830 |
|
|
biffAddf(NRRD, "%s:", me); |
831 |
|
|
airMopError(mop); return 1; |
832 |
|
|
} |
833 |
|
|
|
834 |
|
|
airMopOkay(mop); |
835 |
|
|
return 0; |
836 |
|
|
} |