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 |
|
|
******** nrrdHisto() |
29 |
|
|
** |
30 |
|
|
** makes a 1D histogram of a given size and type |
31 |
|
|
** |
32 |
|
|
** pre-NrrdRange policy: |
33 |
|
|
** this looks at nin->min and nin->max to see if they are both non-NaN. |
34 |
|
|
** If so, it uses these as the range of the histogram, otherwise it |
35 |
|
|
** finds the min and max present in the volume. If nin->min and nin->max |
36 |
|
|
** are being used as the histogram range, then values which fall outside |
37 |
|
|
** this are ignored (they don't contribute to the histogram). |
38 |
|
|
** |
39 |
|
|
** post-NrrdRange policy: |
40 |
|
|
*/ |
41 |
|
|
int |
42 |
|
|
nrrdHisto(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, |
43 |
|
|
const Nrrd *nwght, size_t bins, int type) { |
44 |
|
|
static const char me[]="nrrdHisto", func[]="histo"; |
45 |
|
|
size_t I, num, idx; |
46 |
|
|
airArray *mop; |
47 |
|
|
NrrdRange *range; |
48 |
|
|
double min, max, eps, val, count, incr, (*lup)(const void *v, size_t I); |
49 |
|
|
|
50 |
✗✓ |
2 |
if (!(nin && nout)) { |
51 |
|
|
/* _range and nwght can be NULL */ |
52 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
53 |
|
|
return 1; |
54 |
|
|
} |
55 |
✗✓ |
1 |
if (nout == nin) { |
56 |
|
|
biffAddf(NRRD, "%s: nout==nin disallowed", me); |
57 |
|
|
return 1; |
58 |
|
|
} |
59 |
✗✓ |
1 |
if (!(bins > 0)) { |
60 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
61 |
|
|
biffAddf(NRRD, "%s: bins value (%s) invalid", me, |
62 |
|
|
airSprintSize_t(stmp, bins)); |
63 |
|
|
return 1; |
64 |
|
|
} |
65 |
✗✓ |
1 |
if (airEnumValCheck(nrrdType, type) || nrrdTypeBlock == type) { |
66 |
|
|
biffAddf(NRRD, "%s: invalid nrrd type %d", me, type); |
67 |
|
|
return 1; |
68 |
|
|
} |
69 |
✗✓ |
1 |
if (nwght) { |
70 |
|
|
if (nout==nwght) { |
71 |
|
|
biffAddf(NRRD, "%s: nout==nwght disallowed", me); |
72 |
|
|
return 1; |
73 |
|
|
} |
74 |
|
|
if (nrrdTypeBlock == nwght->type) { |
75 |
|
|
biffAddf(NRRD, "%s: nwght type %s invalid", me, |
76 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
77 |
|
|
return 1; |
78 |
|
|
} |
79 |
|
|
if (!nrrdSameSize(nin, nwght, AIR_TRUE)) { |
80 |
|
|
biffAddf(NRRD, "%s: nwght size mismatch with nin", me); |
81 |
|
|
return 1; |
82 |
|
|
} |
83 |
|
|
lup = nrrdDLookup[nwght->type]; |
84 |
|
|
} else { |
85 |
|
|
lup = NULL; |
86 |
|
|
} |
87 |
|
|
|
88 |
✗✓ |
1 |
if (nrrdMaybeAlloc_va(nout, type, 1, bins)) { |
89 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
90 |
|
|
biffAddf(NRRD, "%s: failed to alloc histo array (len %s)", me, |
91 |
|
|
airSprintSize_t(stmp, bins)); |
92 |
|
|
return 1; |
93 |
|
|
} |
94 |
|
1 |
mop = airMopNew(); |
95 |
|
|
/* nout->axis[0].size set */ |
96 |
|
1 |
nout->axis[0].spacing = AIR_NAN; |
97 |
|
1 |
nout->axis[0].thickness = AIR_NAN; |
98 |
✓✗✗✓ ✗✗ |
2 |
if (nout && AIR_EXISTS(nout->axis[0].min) && AIR_EXISTS(nout->axis[0].max)) { |
99 |
|
|
/* HEY: total hack to externally nail down min and max of histogram: |
100 |
|
|
use the min and max already set on axis[0] */ |
101 |
|
|
/* HEY: shouldn't this blatent hack be further restricted by also |
102 |
|
|
checking the existence of range->min and range->max ? */ |
103 |
|
|
min = nout->axis[0].min; |
104 |
|
|
max = nout->axis[0].max; |
105 |
|
|
} else { |
106 |
✗✓ |
1 |
if (_range) { |
107 |
|
|
range = nrrdRangeCopy(_range); |
108 |
|
|
nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); |
109 |
|
|
} else { |
110 |
|
1 |
range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); |
111 |
|
|
} |
112 |
|
1 |
airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); |
113 |
|
1 |
min = range->min; |
114 |
|
1 |
max = range->max; |
115 |
|
1 |
nout->axis[0].min = min; |
116 |
|
1 |
nout->axis[0].max = max; |
117 |
|
|
} |
118 |
|
1 |
eps = (min == max ? 1.0 : 0.0); |
119 |
|
1 |
nout->axis[0].center = nrrdCenterCell; |
120 |
|
|
/* nout->axis[0].label set below */ |
121 |
|
|
|
122 |
|
|
/* make histogram */ |
123 |
|
1 |
num = nrrdElementNumber(nin); |
124 |
✓✓ |
80002 |
for (I=0; I<num; I++) { |
125 |
|
40000 |
val = nrrdDLookup[nin->type](nin->data, I); |
126 |
✓✗ |
40000 |
if (AIR_EXISTS(val)) { |
127 |
✓✗✓✗
|
80000 |
if (val < min || val > max+eps) { |
128 |
|
|
/* value is outside range; ignore it */ |
129 |
|
|
continue; |
130 |
|
|
} |
131 |
✓✗✓✗
|
80000 |
if (AIR_IN_CL(min, val, max)) { |
132 |
|
40000 |
idx = airIndex(min, val, max+eps, AIR_CAST(unsigned int, bins)); |
133 |
|
|
/* |
134 |
|
|
printf("!%s: %d: index(%g, %g, %g, %d) = %d\n", |
135 |
|
|
me, (int)I, min, val, max, bins, idx); |
136 |
|
|
*/ |
137 |
|
|
/* count is a double in order to simplify clamping the |
138 |
|
|
hit values to the representable range for nout->type */ |
139 |
|
40000 |
count = nrrdDLookup[nout->type](nout->data, idx); |
140 |
✗✓ |
80000 |
incr = nwght ? lup(nwght->data, I) : 1; |
141 |
|
40000 |
count = nrrdDClamp[nout->type](count + incr); |
142 |
|
40000 |
nrrdDInsert[nout->type](nout->data, idx, count); |
143 |
|
40000 |
} |
144 |
|
|
} |
145 |
|
|
} |
146 |
|
|
|
147 |
✗✓ |
1 |
if (nrrdContentSet_va(nout, func, nin, "%d", bins)) { |
148 |
|
|
biffAddf(NRRD, "%s:", me); |
149 |
|
|
airMopError(mop); return 1; |
150 |
|
|
} |
151 |
|
1 |
nout->axis[0].label = (char *)airFree(nout->axis[0].label); |
152 |
|
1 |
nout->axis[0].label = (char *)airStrdup(nout->content); |
153 |
✓✗ |
1 |
if (!nrrdStateKindNoop) { |
154 |
|
1 |
nout->axis[0].kind = nrrdKindDomain; |
155 |
|
1 |
} |
156 |
|
|
|
157 |
|
1 |
airMopOkay(mop); |
158 |
|
1 |
return 0; |
159 |
|
1 |
} |
160 |
|
|
|
161 |
|
|
int |
162 |
|
|
nrrdHistoCheck(const Nrrd *nhist) { |
163 |
|
|
static const char me[]="nrrdHistoCheck"; |
164 |
|
|
|
165 |
✗✓ |
2 |
if (!nhist) { |
166 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
167 |
|
|
return 1; |
168 |
|
|
} |
169 |
✗✓ |
1 |
if (nrrdTypeBlock == nhist->type) { |
170 |
|
|
biffAddf(NRRD, "%s: has non-scalar %s type", |
171 |
|
|
me, airEnumStr(nrrdType, nrrdTypeBlock)); |
172 |
|
|
return 1; |
173 |
|
|
} |
174 |
✗✓ |
1 |
if (nrrdHasNonExist(nhist)) { |
175 |
|
|
biffAddf(NRRD, "%s: has non-existent values", me); |
176 |
|
|
return 1; |
177 |
|
|
} |
178 |
✗✓ |
1 |
if (1 != nhist->dim) { |
179 |
|
|
biffAddf(NRRD, "%s: dim == %u != 1", |
180 |
|
|
me, nhist->dim); |
181 |
|
|
return 1; |
182 |
|
|
} |
183 |
✗✓ |
1 |
if (!(nhist->axis[0].size > 1)) { |
184 |
|
|
biffAddf(NRRD, "%s: has single sample along sole axis", me); |
185 |
|
|
return 1; |
186 |
|
|
} |
187 |
|
|
|
188 |
|
1 |
return 0; |
189 |
|
1 |
} |
190 |
|
|
|
191 |
|
|
int |
192 |
|
|
nrrdHistoDraw(Nrrd *nout, const Nrrd *nin, |
193 |
|
|
size_t sy, int showLog, double max) { |
194 |
|
|
static const char me[]="nrrdHistoDraw", func[]="dhisto"; |
195 |
|
2 |
char cmt[AIR_STRLEN_MED], stmp[AIR_STRLEN_SMALL]; |
196 |
|
1 |
unsigned int ki, numticks, *linY, *logY, tick, *ticks; |
197 |
|
|
double hits, maxhits, usemaxhits; |
198 |
|
|
unsigned char *pgmData; |
199 |
|
|
airArray *mop; |
200 |
|
|
int E; |
201 |
|
|
size_t sx, xi, yi, maxhitidx; |
202 |
|
|
|
203 |
✗✓ |
1 |
if (!(nin && nout && sy > 0)) { |
204 |
|
|
biffAddf(NRRD, "%s: invalid args", me); |
205 |
|
|
return 1; |
206 |
|
|
} |
207 |
✗✓ |
1 |
if (nout == nin) { |
208 |
|
|
biffAddf(NRRD, "%s: nout==nin disallowed", me); |
209 |
|
|
return 1; |
210 |
|
|
} |
211 |
✗✓ |
1 |
if (nrrdHistoCheck(nin)) { |
212 |
|
|
biffAddf(NRRD, "%s: input nrrd not a histogram", me); |
213 |
|
|
return 1; |
214 |
|
|
} |
215 |
|
1 |
sx = nin->axis[0].size; |
216 |
|
1 |
nrrdBasicInfoInit(nout, NRRD_BASIC_INFO_DATA_BIT); |
217 |
✗✓ |
1 |
if (nrrdMaybeAlloc_va(nout, nrrdTypeUChar, 2, sx, sy)) { |
218 |
|
|
biffAddf(NRRD, "%s: failed to allocate histogram image", me); |
219 |
|
|
return 1; |
220 |
|
|
} |
221 |
|
|
/* perhaps I should be using nrrdAxisInfoCopy */ |
222 |
|
1 |
nout->axis[0].spacing = nout->axis[1].spacing = AIR_NAN; |
223 |
|
1 |
nout->axis[0].thickness = nout->axis[1].thickness = AIR_NAN; |
224 |
|
1 |
nout->axis[0].min = nin->axis[0].min; |
225 |
|
1 |
nout->axis[0].max = nin->axis[0].max; |
226 |
|
1 |
nout->axis[0].center = nout->axis[1].center = nrrdCenterCell; |
227 |
|
1 |
nout->axis[0].label = (char *)airStrdup(nin->axis[0].label); |
228 |
|
1 |
nout->axis[1].label = (char *)airFree(nout->axis[1].label); |
229 |
|
1 |
pgmData = (unsigned char *)nout->data; |
230 |
|
|
maxhits = 0.0; |
231 |
|
|
maxhitidx = 0; |
232 |
✓✓ |
2002 |
for (xi=0; xi<sx; xi++) { |
233 |
|
1000 |
hits = nrrdDLookup[nin->type](nin->data, xi); |
234 |
✓✓ |
1000 |
if (maxhits < hits) { |
235 |
|
|
maxhits = hits; |
236 |
|
|
maxhitidx = xi; |
237 |
|
37 |
} |
238 |
|
|
} |
239 |
✗✓ |
1 |
if (AIR_EXISTS(max) && max > 0) { |
240 |
|
|
usemaxhits = max; |
241 |
|
|
} else { |
242 |
|
|
usemaxhits = maxhits; |
243 |
|
|
} |
244 |
|
1 |
nout->axis[1].min = usemaxhits; |
245 |
|
1 |
nout->axis[1].max = 0; |
246 |
|
1 |
numticks = AIR_CAST(unsigned int, log10(usemaxhits + 1)); |
247 |
|
1 |
mop = airMopNew(); |
248 |
|
1 |
ticks = AIR_CALLOC(numticks, unsigned int); |
249 |
|
1 |
airMopMem(mop, &ticks, airMopAlways); |
250 |
|
1 |
linY = AIR_CALLOC(sx, unsigned int); |
251 |
|
1 |
airMopMem(mop, &linY, airMopAlways); |
252 |
|
1 |
logY = AIR_CALLOC(sx, unsigned int); |
253 |
|
1 |
airMopMem(mop, &logY, airMopAlways); |
254 |
✗✓ |
1 |
if (!(ticks && linY && logY)) { |
255 |
|
|
biffAddf(NRRD, "%s: failed to allocate temp arrays", me); |
256 |
|
|
airMopError(mop); return 1; |
257 |
|
|
} |
258 |
✓✓ |
6 |
for (ki=0; ki<numticks; ki++) { |
259 |
|
4 |
ticks[ki] = airIndex(0, log10(pow(10,ki+1) + 1), log10(usemaxhits+1), |
260 |
|
2 |
AIR_CAST(unsigned int, sy)); |
261 |
|
|
} |
262 |
✓✓ |
2002 |
for (xi=0; xi<sx; xi++) { |
263 |
|
1000 |
hits = nrrdDLookup[nin->type](nin->data, xi); |
264 |
|
1000 |
linY[xi] = airIndex(0, hits, usemaxhits, |
265 |
|
1000 |
AIR_CAST(unsigned int, sy)); |
266 |
|
1000 |
logY[xi] = airIndex(0, log10(hits+1), log10(usemaxhits+1), |
267 |
|
|
AIR_CAST(unsigned int, sy)); |
268 |
|
|
/* printf("%d -> %d,%d", x, linY[x], logY[x]); */ |
269 |
|
|
} |
270 |
✓✓ |
2002 |
for (yi=0; yi<sy; yi++) { |
271 |
|
|
tick = 0; |
272 |
✓✓ |
6000 |
for (ki=0; ki<numticks; ki++) |
273 |
|
2000 |
tick |= ticks[ki] == yi; |
274 |
✓✓ |
2002000 |
for (xi=0; xi<sx; xi++) { |
275 |
|
1000000 |
pgmData[xi + sx*(sy-1-yi)] = |
276 |
✗✓ |
2000000 |
(2 == showLog /* HACK: draw log curve, but not log tick marks */ |
277 |
|
|
? (yi >= logY[xi] |
278 |
|
|
? 0 /* above log curve */ |
279 |
|
|
: (yi >= linY[xi] |
280 |
|
|
? 128 /* below log curve, above normal curve */ |
281 |
|
|
: 255 /* below log curve, below normal curve */ |
282 |
|
|
) |
283 |
|
|
) |
284 |
✗✓ |
1000000 |
: (!showLog |
285 |
|
|
? (yi >= linY[xi] ? 0 : 255) |
286 |
✓✓ |
1501084 |
: (yi >= logY[xi] /* above log curve */ |
287 |
|
501084 |
? (!tick ? 0 /* not on tick mark */ |
288 |
|
|
: 255) /* on tick mark */ |
289 |
✓✓ |
751284 |
: (yi >= linY[xi] /* below log curve, above normal curve */ |
290 |
|
252368 |
? (!tick ? 128 /* not on tick mark */ |
291 |
|
|
: 0) /* on tick mark */ |
292 |
|
|
:255 /* below log curve, below normal curve */ |
293 |
|
|
) |
294 |
|
|
) |
295 |
|
|
) |
296 |
|
|
); |
297 |
|
|
} |
298 |
|
|
} |
299 |
|
|
E = 0; |
300 |
|
1 |
sprintf(cmt, "min value: %g\n", nout->axis[0].min); |
301 |
✓✗ |
2 |
if (!E) E |= nrrdCommentAdd(nout, cmt); |
302 |
|
1 |
sprintf(cmt, "max value: %g\n", nout->axis[0].max); |
303 |
✓✗ |
2 |
if (!E) E |= nrrdCommentAdd(nout, cmt); |
304 |
|
1 |
sprintf(cmt, "max hits: %g, in bin %s, around value %g\n", maxhits, |
305 |
|
|
airSprintSize_t(stmp, maxhitidx), |
306 |
|
|
nrrdAxisInfoPos(nout, 0, AIR_CAST(double, maxhitidx))); |
307 |
✓✗ |
2 |
if (!E) E |= nrrdCommentAdd(nout, cmt); |
308 |
✓✗ |
2 |
if (!E) E |= nrrdContentSet_va(nout, func, nin, "%s", |
309 |
|
1 |
airSprintSize_t(stmp, sy)); |
310 |
✗✓ |
1 |
if (E) { |
311 |
|
|
biffAddf(NRRD, "%s:", me); |
312 |
|
|
airMopError(mop); return 1; |
313 |
|
|
} |
314 |
|
|
|
315 |
|
|
/* bye */ |
316 |
|
1 |
airMopOkay(mop); |
317 |
|
1 |
return 0; |
318 |
|
1 |
} |
319 |
|
|
|
320 |
|
|
/* |
321 |
|
|
******** nrrdHistoAxis |
322 |
|
|
** |
323 |
|
|
** replace scanlines along one scanline with a histogram of the scanline |
324 |
|
|
** |
325 |
|
|
** this looks at nin->min and nin->max to see if they are both non-NaN. |
326 |
|
|
** If so, it uses these as the range of the histogram, otherwise it |
327 |
|
|
** finds the min and max present in the volume |
328 |
|
|
** |
329 |
|
|
** By its very nature, and by the simplicity of this implemention, |
330 |
|
|
** this can be a slow process due to terrible memory locality. User |
331 |
|
|
** may want to permute axes before and after this, but that can be |
332 |
|
|
** slow too... |
333 |
|
|
*/ |
334 |
|
|
int |
335 |
|
|
nrrdHistoAxis(Nrrd *nout, const Nrrd *nin, const NrrdRange *_range, |
336 |
|
|
unsigned int hax, size_t bins, int type) { |
337 |
|
|
static const char me[]="nrrdHistoAxis", func[]="histax"; |
338 |
|
|
int map[NRRD_DIM_MAX]; |
339 |
|
|
unsigned int ai, hidx; |
340 |
|
|
size_t szIn[NRRD_DIM_MAX], szOut[NRRD_DIM_MAX], size[NRRD_DIM_MAX], |
341 |
|
|
coordIn[NRRD_DIM_MAX], coordOut[NRRD_DIM_MAX]; |
342 |
|
|
size_t I, hI, num; |
343 |
|
|
double val, count; |
344 |
|
|
airArray *mop; |
345 |
|
|
NrrdRange *range; |
346 |
|
|
|
347 |
|
|
if (!(nin && nout)) { |
348 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
349 |
|
|
return 1; |
350 |
|
|
} |
351 |
|
|
if (nout == nin) { |
352 |
|
|
biffAddf(NRRD, "%s: nout==nin disallowed", me); |
353 |
|
|
return 1; |
354 |
|
|
} |
355 |
|
|
if (!(bins > 0)) { |
356 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
357 |
|
|
biffAddf(NRRD, "%s: bins value (%s) invalid", me, |
358 |
|
|
airSprintSize_t(stmp, bins)); |
359 |
|
|
return 1; |
360 |
|
|
} |
361 |
|
|
if (airEnumValCheck(nrrdType, type) || nrrdTypeBlock == type) { |
362 |
|
|
biffAddf(NRRD, "%s: invalid nrrd type %d", me, type); |
363 |
|
|
return 1; |
364 |
|
|
} |
365 |
|
|
if (!( hax <= nin->dim-1 )) { |
366 |
|
|
biffAddf(NRRD, "%s: axis %d is not in range [0,%d]", |
367 |
|
|
me, hax, nin->dim-1); |
368 |
|
|
return 1; |
369 |
|
|
} |
370 |
|
|
mop = airMopNew(); |
371 |
|
|
if (_range) { |
372 |
|
|
range = nrrdRangeCopy(_range); |
373 |
|
|
nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState); |
374 |
|
|
} else { |
375 |
|
|
range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState); |
376 |
|
|
} |
377 |
|
|
airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways); |
378 |
|
|
nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, size); |
379 |
|
|
size[hax] = bins; |
380 |
|
|
if (nrrdMaybeAlloc_nva(nout, type, nin->dim, size)) { |
381 |
|
|
biffAddf(NRRD, "%s: failed to alloc output nrrd", me); |
382 |
|
|
airMopError(mop); return 1; |
383 |
|
|
} |
384 |
|
|
|
385 |
|
|
/* copy axis information */ |
386 |
|
|
for (ai=0; ai<nin->dim; ai++) { |
387 |
|
|
map[ai] = ai != hax ? (int)ai : -1; |
388 |
|
|
} |
389 |
|
|
nrrdAxisInfoCopy(nout, nin, map, NRRD_AXIS_INFO_NONE); |
390 |
|
|
/* axis hax now has to be set manually */ |
391 |
|
|
nout->axis[hax].size = bins; |
392 |
|
|
nout->axis[hax].spacing = AIR_NAN; /* min and max convey the information */ |
393 |
|
|
nout->axis[hax].thickness = AIR_NAN; |
394 |
|
|
nout->axis[hax].min = range->min; |
395 |
|
|
nout->axis[hax].max = range->max; |
396 |
|
|
nout->axis[hax].center = nrrdCenterCell; |
397 |
|
|
if (nin->axis[hax].label) { |
398 |
|
|
nout->axis[hax].label = AIR_CALLOC(strlen("histax()") |
399 |
|
|
+ strlen(nin->axis[hax].label) |
400 |
|
|
+ 1, char); |
401 |
|
|
if (nout->axis[hax].label) { |
402 |
|
|
sprintf(nout->axis[hax].label, "histax(%s)", nin->axis[hax].label); |
403 |
|
|
} else { |
404 |
|
|
biffAddf(NRRD, "%s: couldn't allocate output label", me); |
405 |
|
|
airMopError(mop); return 1; |
406 |
|
|
} |
407 |
|
|
} else { |
408 |
|
|
nout->axis[hax].label = NULL; |
409 |
|
|
} |
410 |
|
|
if (!nrrdStateKindNoop) { |
411 |
|
|
nout->axis[hax].kind = nrrdKindDomain; |
412 |
|
|
} |
413 |
|
|
|
414 |
|
|
/* the skinny: we traverse the input samples in linear order, and |
415 |
|
|
increment the bin in the histogram for the scanline we're in. |
416 |
|
|
This is not terribly clever, and the memory locality is a |
417 |
|
|
disaster */ |
418 |
|
|
nrrdAxisInfoGet_nva(nin, nrrdAxisInfoSize, szIn); |
419 |
|
|
nrrdAxisInfoGet_nva(nout, nrrdAxisInfoSize, szOut); |
420 |
|
|
memset(coordIn, 0, NRRD_DIM_MAX*sizeof(size_t)); |
421 |
|
|
num = nrrdElementNumber(nin); |
422 |
|
|
for (I=0; I<num; I++) { |
423 |
|
|
/* get input nrrd value and compute its histogram index */ |
424 |
|
|
val = nrrdDLookup[nin->type](nin->data, I); |
425 |
|
|
if (AIR_EXISTS(val) && AIR_IN_CL(range->min, val, range->max)) { |
426 |
|
|
hidx = airIndex(range->min, val, range->max, AIR_CAST(unsigned int, bins)); |
427 |
|
|
memcpy(coordOut, coordIn, nin->dim*sizeof(size_t)); |
428 |
|
|
coordOut[hax] = (unsigned int)hidx; |
429 |
|
|
NRRD_INDEX_GEN(hI, coordOut, szOut, nout->dim); |
430 |
|
|
count = nrrdDLookup[nout->type](nout->data, hI); |
431 |
|
|
count = nrrdDClamp[nout->type](count + 1); |
432 |
|
|
nrrdDInsert[nout->type](nout->data, hI, count); |
433 |
|
|
} |
434 |
|
|
NRRD_COORD_INCR(coordIn, szIn, nin->dim, 0); |
435 |
|
|
} |
436 |
|
|
|
437 |
|
|
if (nrrdContentSet_va(nout, func, nin, "%d,%d", hax, bins)) { |
438 |
|
|
biffAddf(NRRD, "%s:", me); |
439 |
|
|
airMopError(mop); return 1; |
440 |
|
|
} |
441 |
|
|
nrrdBasicInfoInit(nout, (NRRD_BASIC_INFO_DATA_BIT |
442 |
|
|
| NRRD_BASIC_INFO_TYPE_BIT |
443 |
|
|
| NRRD_BASIC_INFO_DIMENSION_BIT |
444 |
|
|
| 0 /* what? */ )); |
445 |
|
|
airMopOkay(mop); |
446 |
|
|
return 0; |
447 |
|
|
} |
448 |
|
|
|
449 |
|
|
int |
450 |
|
|
nrrdHistoJoint(Nrrd *nout, const Nrrd *const *nin, |
451 |
|
|
const NrrdRange *const *_range, unsigned int numNin, |
452 |
|
|
const Nrrd *nwght, const size_t *bins, |
453 |
|
|
int type, const int *clamp) { |
454 |
|
|
static const char me[]="nrrdHistoJoint", func[]="jhisto"; |
455 |
|
|
int skip, hadContent; |
456 |
|
|
double val, count, incr, (*lup)(const void *v, size_t I), |
457 |
|
|
rmin[NRRD_DIM_MAX], rmax[NRRD_DIM_MAX]; |
458 |
|
|
size_t Iin, Iout, numEl, coord[NRRD_DIM_MAX], totalContentStrlen; |
459 |
|
|
airArray *mop; |
460 |
|
|
NrrdRange **range; |
461 |
|
|
unsigned int nii, ai; |
462 |
|
|
|
463 |
|
|
/* error checking */ |
464 |
|
|
/* nwght can be NULL -> weighting is constant 1.0 */ |
465 |
|
|
if (!(nout && nin && bins && clamp)) { |
466 |
|
|
biffAddf(NRRD, "%s: got NULL pointer (%p, %p, %p, %p)", me, |
467 |
|
|
AIR_VOIDP(nout), AIR_CVOIDP(nin), |
468 |
|
|
AIR_CVOIDP(bins), AIR_CVOIDP(clamp)); |
469 |
|
|
return 1; |
470 |
|
|
} |
471 |
|
|
if (!(numNin >= 1)) { |
472 |
|
|
biffAddf(NRRD, "%s: need numNin >= 1 (not %d)", me, numNin); |
473 |
|
|
return 1; |
474 |
|
|
} |
475 |
|
|
if (numNin > NRRD_DIM_MAX) { |
476 |
|
|
biffAddf(NRRD, "%s: can only deal with up to %d nrrds (not %d)", me, |
477 |
|
|
NRRD_DIM_MAX, numNin); |
478 |
|
|
return 1; |
479 |
|
|
} |
480 |
|
|
for (nii=0; nii<numNin; nii++) { |
481 |
|
|
if (!(nin[nii])) { |
482 |
|
|
biffAddf(NRRD, "%s: input nrrd #%u NULL", me, nii); |
483 |
|
|
return 1; |
484 |
|
|
} |
485 |
|
|
if (nout==nin[nii]) { |
486 |
|
|
biffAddf(NRRD, "%s: nout==nin[%d] disallowed", me, nii); |
487 |
|
|
return 1; |
488 |
|
|
} |
489 |
|
|
if (nrrdTypeBlock == nin[nii]->type) { |
490 |
|
|
biffAddf(NRRD, "%s: nin[%d] type %s invalid", me, nii, |
491 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
492 |
|
|
return 1; |
493 |
|
|
} |
494 |
|
|
} |
495 |
|
|
if (airEnumValCheck(nrrdType, type) || nrrdTypeBlock == type) { |
496 |
|
|
biffAddf(NRRD, "%s: invalid nrrd type %d", me, type); |
497 |
|
|
return 1; |
498 |
|
|
} |
499 |
|
|
mop = airMopNew(); |
500 |
|
|
range = AIR_CALLOC(numNin, NrrdRange*); |
501 |
|
|
airMopAdd(mop, range, airFree, airMopAlways); |
502 |
|
|
for (ai=0; ai<numNin; ai++) { |
503 |
|
|
if (_range && _range[ai]) { |
504 |
|
|
range[ai] = nrrdRangeCopy(_range[ai]); |
505 |
|
|
nrrdRangeSafeSet(range[ai], nin[ai], nrrdBlind8BitRangeState); |
506 |
|
|
} else { |
507 |
|
|
range[ai] = nrrdRangeNewSet(nin[ai], nrrdBlind8BitRangeState); |
508 |
|
|
} |
509 |
|
|
airMopAdd(mop, range[ai], (airMopper)nrrdRangeNix, airMopAlways); |
510 |
|
|
} |
511 |
|
|
for (ai=0; ai<numNin; ai++) { |
512 |
|
|
if (!nin[ai]) { |
513 |
|
|
biffAddf(NRRD, "%s: input nrrd[%d] NULL", me, ai); |
514 |
|
|
return 1; |
515 |
|
|
} |
516 |
|
|
if (!(bins[ai] >= 1)) { |
517 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
518 |
|
|
biffAddf(NRRD, "%s: need bins[%u] >= 1 (not %s)", me, ai, |
519 |
|
|
airSprintSize_t(stmp, bins[ai])); |
520 |
|
|
return 1; |
521 |
|
|
} |
522 |
|
|
if (ai && !nrrdSameSize(nin[0], nin[ai], AIR_TRUE)) { |
523 |
|
|
biffAddf(NRRD, "%s: nin[0] (n1) mismatch with nin[%u] (n2)", me, ai); |
524 |
|
|
return 1; |
525 |
|
|
} |
526 |
|
|
} |
527 |
|
|
|
528 |
|
|
/* check nwght */ |
529 |
|
|
if (nwght) { |
530 |
|
|
char stmp1[AIR_STRLEN_SMALL], stmp2[AIR_STRLEN_SMALL]; |
531 |
|
|
if (nout==nwght) { |
532 |
|
|
biffAddf(NRRD, "%s: nout==nwght disallowed", me); |
533 |
|
|
return 1; |
534 |
|
|
} |
535 |
|
|
if (nrrdTypeBlock == nwght->type) { |
536 |
|
|
biffAddf(NRRD, "%s: nwght type %s invalid", me, |
537 |
|
|
airEnumStr(nrrdType, nrrdTypeBlock)); |
538 |
|
|
return 1; |
539 |
|
|
} |
540 |
|
|
if (nrrdElementNumber(nin[0]) != nrrdElementNumber(nwght)) { |
541 |
|
|
biffAddf(NRRD, "%s: element # in nwght %s != nin[0] %s", me, |
542 |
|
|
airSprintSize_t(stmp2, nrrdElementNumber(nin[0])), |
543 |
|
|
airSprintSize_t(stmp1, nrrdElementNumber(nwght))); |
544 |
|
|
return 1; |
545 |
|
|
} |
546 |
|
|
lup = nrrdDLookup[nwght->type]; |
547 |
|
|
} else { |
548 |
|
|
lup = NULL; |
549 |
|
|
} |
550 |
|
|
|
551 |
|
|
/* allocate output nrrd */ |
552 |
|
|
if (nrrdMaybeAlloc_nva(nout, type, numNin, bins)) { |
553 |
|
|
biffAddf(NRRD, "%s: couldn't allocate output histogram", me); |
554 |
|
|
return 1; |
555 |
|
|
} |
556 |
|
|
hadContent = 0; |
557 |
|
|
totalContentStrlen = 0; |
558 |
|
|
for (ai=0; ai<numNin; ai++) { |
559 |
|
|
nout->axis[ai].size = bins[ai]; |
560 |
|
|
nout->axis[ai].spacing = AIR_NAN; |
561 |
|
|
nout->axis[ai].thickness = AIR_NAN; |
562 |
|
|
nout->axis[ai].min = range[ai]->min; |
563 |
|
|
nout->axis[ai].max = range[ai]->max; |
564 |
|
|
nout->axis[ai].center = nrrdCenterCell; |
565 |
|
|
if (!nrrdStateKindNoop) { |
566 |
|
|
nout->axis[ai].kind = nrrdKindDomain; |
567 |
|
|
} |
568 |
|
|
if (nin[ai]->content) { |
569 |
|
|
hadContent = 1; |
570 |
|
|
totalContentStrlen += strlen(nin[ai]->content); |
571 |
|
|
nout->axis[ai].label = AIR_CALLOC(strlen("histo(,)") |
572 |
|
|
+ strlen(nin[ai]->content) |
573 |
|
|
+ 11 |
574 |
|
|
+ 1, char); |
575 |
|
|
if (nout->axis[ai].label) { |
576 |
|
|
char stmp[AIR_STRLEN_SMALL]; |
577 |
|
|
sprintf(nout->axis[ai].label, "histo(%s,%s)", nin[ai]->content, |
578 |
|
|
airSprintSize_t(stmp, bins[ai])); |
579 |
|
|
} else { |
580 |
|
|
biffAddf(NRRD, "%s: couldn't allocate output label #%u", me, ai); |
581 |
|
|
return 1; |
582 |
|
|
} |
583 |
|
|
} else { |
584 |
|
|
nout->axis[ai].label = (char *)airFree(nout->axis[ai].label); |
585 |
|
|
totalContentStrlen += 2; |
586 |
|
|
} |
587 |
|
|
} |
588 |
|
|
|
589 |
|
|
/* the skinny */ |
590 |
|
|
for (ai=0; ai<numNin; ai++) { |
591 |
|
|
if (range[ai]->min <= range[ai]->max) { |
592 |
|
|
rmin[ai] = range[ai]->min; |
593 |
|
|
rmax[ai] = range[ai]->max; |
594 |
|
|
} else { |
595 |
|
|
rmin[ai] = range[ai]->max; |
596 |
|
|
rmax[ai] = range[ai]->min; |
597 |
|
|
} |
598 |
|
|
} |
599 |
|
|
numEl = nrrdElementNumber(nin[0]); |
600 |
|
|
for (Iin=0; Iin<numEl; Iin++) { |
601 |
|
|
skip = 0; |
602 |
|
|
for (ai=0; ai<numNin; ai++) { |
603 |
|
|
val = nrrdDLookup[nin[ai]->type](nin[ai]->data, Iin); |
604 |
|
|
if (!AIR_EXISTS(val)) { |
605 |
|
|
/* coordinate d in the joint histo can't be determined |
606 |
|
|
if nin[ai] has a non-existent value here */ |
607 |
|
|
skip = 1; |
608 |
|
|
break; |
609 |
|
|
} |
610 |
|
|
if (!AIR_IN_CL(rmin[ai], val, rmax[ai])) { |
611 |
|
|
if (clamp[ai]) { |
612 |
|
|
val = AIR_CLAMP(rmin[ai], val, rmax[ai]); |
613 |
|
|
} else { |
614 |
|
|
skip = 1; |
615 |
|
|
break; |
616 |
|
|
} |
617 |
|
|
} |
618 |
|
|
coord[ai] = AIR_CAST(size_t, airIndexClampULL(range[ai]->min, |
619 |
|
|
val, |
620 |
|
|
range[ai]->max, |
621 |
|
|
bins[ai])); |
622 |
|
|
} |
623 |
|
|
if (skip) { |
624 |
|
|
continue; |
625 |
|
|
} |
626 |
|
|
NRRD_INDEX_GEN(Iout, coord, bins, numNin); |
627 |
|
|
count = nrrdDLookup[nout->type](nout->data, Iout); |
628 |
|
|
incr = nwght ? lup(nwght->data, Iin) : 1.0; |
629 |
|
|
count = nrrdDClamp[nout->type](count + incr); |
630 |
|
|
nrrdDInsert[nout->type](nout->data, Iout, count); |
631 |
|
|
} |
632 |
|
|
|
633 |
|
|
/* HEY: switch to nrrdContentSet_va? */ |
634 |
|
|
if (hadContent) { |
635 |
|
|
nout->content = AIR_CALLOC(strlen(func) + strlen("()") |
636 |
|
|
+ numNin*strlen(",") |
637 |
|
|
+ totalContentStrlen |
638 |
|
|
+ 1, char); |
639 |
|
|
if (nout->content) { |
640 |
|
|
size_t len; |
641 |
|
|
sprintf(nout->content, "%s(", func); |
642 |
|
|
for (ai=0; ai<numNin; ai++) { |
643 |
|
|
len = strlen(nout->content); |
644 |
|
|
strcpy(nout->content + len, |
645 |
|
|
nin[ai]->content ? nin[ai]->content : "?"); |
646 |
|
|
len = strlen(nout->content); |
647 |
|
|
nout->content[len] = ai < numNin-1 ? ',' : ')'; |
648 |
|
|
} |
649 |
|
|
nout->content[len+1] = '\0'; |
650 |
|
|
} else { |
651 |
|
|
biffAddf(NRRD, "%s: couldn't allocate output content", me); |
652 |
|
|
return 1; |
653 |
|
|
} |
654 |
|
|
} |
655 |
|
|
|
656 |
|
|
airMopOkay(mop); |
657 |
|
|
return 0; |
658 |
|
|
} |
659 |
|
|
|
660 |
|
|
/* |
661 |
|
|
******** nrrdHistoThresholdOtsu |
662 |
|
|
** |
663 |
|
|
** does simple Otsu tresholding of a histogram, with a variable exponont. |
664 |
|
|
** When "expo" is 2.0, it computes variance; lower values probably represent |
665 |
|
|
** greater insensitivities to outliers. Idea from ... |
666 |
|
|
*/ |
667 |
|
|
int |
668 |
|
|
nrrdHistoThresholdOtsu(double *threshP, const Nrrd *_nhist, double expo) { |
669 |
|
|
static const char me[]="nrrdHistoThresholdOtsu"; |
670 |
|
|
unsigned int histLen, histIdx, maxIdx; |
671 |
|
|
Nrrd *nhist, *nbvar; |
672 |
|
|
double *hist, *bvar, thresh, num0, num1, mean0, mean1, |
673 |
|
|
onum0, onum1, omean0, omean1, max; |
674 |
|
|
airArray *mop; |
675 |
|
|
|
676 |
|
|
if (!(threshP && _nhist)) { |
677 |
|
|
biffAddf(NRRD, "%s: got NULL pointer", me); |
678 |
|
|
return 1; |
679 |
|
|
} |
680 |
|
|
if (nrrdHistoCheck(_nhist)) { |
681 |
|
|
biffAddf(NRRD, "%s: input nrrd not a histogram", me); |
682 |
|
|
return 1; |
683 |
|
|
} |
684 |
|
|
|
685 |
|
|
mop = airMopNew(); |
686 |
|
|
airMopAdd(mop, nhist = nrrdNew(), (airMopper)nrrdNuke, airMopAlways); |
687 |
|
|
airMopAdd(mop, nbvar = nrrdNew(), (airMopper)nrrdNuke, airMopAlways); |
688 |
|
|
if (nrrdConvert(nhist, _nhist, nrrdTypeDouble) |
689 |
|
|
|| nrrdCopy(nbvar, nhist)) { |
690 |
|
|
biffAddf(NRRD, "%s: making local copies", me); |
691 |
|
|
airMopError(mop); return 1; |
692 |
|
|
} |
693 |
|
|
hist = AIR_CAST(double*, nhist->data); |
694 |
|
|
bvar = AIR_CAST(double*, nbvar->data); |
695 |
|
|
|
696 |
|
|
histLen = AIR_CAST(unsigned int, nhist->axis[0].size); |
697 |
|
|
num1 = mean1 = 0; |
698 |
|
|
for (histIdx=0; histIdx<histLen; histIdx++) { |
699 |
|
|
num1 += hist[histIdx]; |
700 |
|
|
mean1 += hist[histIdx]*histIdx; |
701 |
|
|
} |
702 |
|
|
if (num1) { |
703 |
|
|
num0 = 0; |
704 |
|
|
mean0 = 0; |
705 |
|
|
mean1 /= num1; |
706 |
|
|
for (histIdx=0; histIdx<histLen; histIdx++) { |
707 |
|
|
if (histIdx) { |
708 |
|
|
onum0 = num0; |
709 |
|
|
onum1 = num1; |
710 |
|
|
omean0 = mean0; |
711 |
|
|
omean1 = mean1; |
712 |
|
|
num0 = onum0 + hist[histIdx-1]; |
713 |
|
|
num1 = onum1 - hist[histIdx-1]; |
714 |
|
|
mean0 = (omean0*onum0 + hist[histIdx-1]*(histIdx-1)) / num0; |
715 |
|
|
mean1 = (omean1*onum1 - hist[histIdx-1]*(histIdx-1)) / num1; |
716 |
|
|
} |
717 |
|
|
bvar[histIdx] = num0*num1*airSgnPow(mean1 - mean0, expo); |
718 |
|
|
} |
719 |
|
|
max = bvar[0]; |
720 |
|
|
maxIdx = 0; |
721 |
|
|
for (histIdx=1; histIdx<histLen; histIdx++) { |
722 |
|
|
if (bvar[histIdx] > max) { |
723 |
|
|
max = bvar[histIdx]; |
724 |
|
|
maxIdx = histIdx; |
725 |
|
|
} |
726 |
|
|
} |
727 |
|
|
thresh = maxIdx; |
728 |
|
|
} else { |
729 |
|
|
thresh = histLen/2; |
730 |
|
|
} |
731 |
|
|
|
732 |
|
|
if (AIR_EXISTS(nhist->axis[0].min) && AIR_EXISTS(nhist->axis[0].max)) { |
733 |
|
|
thresh = NRRD_CELL_POS(nhist->axis[0].min, nhist->axis[0].max, |
734 |
|
|
histLen, thresh); |
735 |
|
|
} |
736 |
|
|
*threshP = thresh; |
737 |
|
|
airMopOkay(mop); |
738 |
|
|
return 0; |
739 |
|
|
} |