1 /*
2 Teem: Tools to process and visualize scientific data and images .
3 Copyright (C) 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
nrrdHisto(Nrrd * nout,const Nrrd * nin,const NrrdRange * _range,const Nrrd * nwght,size_t bins,int type)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 if (!(nin && nout)) {
51 /* _range and nwght can be NULL */
52 biffAddf(NRRD, "%s: got NULL pointer", me);
53 return 1;
54 }
55 if (nout == nin) {
56 biffAddf(NRRD, "%s: nout==nin disallowed", me);
57 return 1;
58 }
59 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 if (airEnumValCheck(nrrdType, type) || nrrdTypeBlock == type) {
66 biffAddf(NRRD, "%s: invalid nrrd type %d", me, type);
67 return 1;
68 }
69 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 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 mop = airMopNew();
95 /* nout->axis[0].size set */
96 nout->axis[0].spacing = AIR_NAN;
97 nout->axis[0].thickness = AIR_NAN;
98 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 if (_range) {
107 range = nrrdRangeCopy(_range);
108 nrrdRangeSafeSet(range, nin, nrrdBlind8BitRangeState);
109 } else {
110 range = nrrdRangeNewSet(nin, nrrdBlind8BitRangeState);
111 }
112 airMopAdd(mop, range, (airMopper)nrrdRangeNix, airMopAlways);
113 min = range->min;
114 max = range->max;
115 nout->axis[0].min = min;
116 nout->axis[0].max = max;
117 }
118 eps = (min == max ? 1.0 : 0.0);
119 nout->axis[0].center = nrrdCenterCell;
120 /* nout->axis[0].label set below */
121
122 /* make histogram */
123 num = nrrdElementNumber(nin);
124 for (I=0; I<num; I++) {
125 val = nrrdDLookup[nin->type](nin->data, I);
126 if (AIR_EXISTS(val)) {
127 if (val < min || val > max+eps) {
128 /* value is outside range; ignore it */
129 continue;
130 }
131 if (AIR_IN_CL(min, val, max)) {
132 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 count = nrrdDLookup[nout->type](nout->data, idx);
140 incr = nwght ? lup(nwght->data, I) : 1;
141 count = nrrdDClamp[nout->type](count + incr);
142 nrrdDInsert[nout->type](nout->data, idx, count);
143 }
144 }
145 }
146
147 if (nrrdContentSet_va(nout, func, nin, "%d", bins)) {
148 biffAddf(NRRD, "%s:", me);
149 airMopError(mop); return 1;
150 }
151 nout->axis[0].label = (char *)airFree(nout->axis[0].label);
152 nout->axis[0].label = (char *)airStrdup(nout->content);
153 if (!nrrdStateKindNoop) {
154 nout->axis[0].kind = nrrdKindDomain;
155 }
156
157 airMopOkay(mop);
158 return 0;
159 }
160
161 int
nrrdHistoCheck(const Nrrd * nhist)162 nrrdHistoCheck(const Nrrd *nhist) {
163 static const char me[]="nrrdHistoCheck";
164
165 if (!nhist) {
166 biffAddf(NRRD, "%s: got NULL pointer", me);
167 return 1;
168 }
169 if (nrrdTypeBlock == nhist->type) {
170 biffAddf(NRRD, "%s: has non-scalar %s type",
171 me, airEnumStr(nrrdType, nrrdTypeBlock));
172 return 1;
173 }
174 if (nrrdHasNonExist(nhist)) {
175 biffAddf(NRRD, "%s: has non-existent values", me);
176 return 1;
177 }
178 if (1 != nhist->dim) {
179 biffAddf(NRRD, "%s: dim == %u != 1",
180 me, nhist->dim);
181 return 1;
182 }
183 if (!(nhist->axis[0].size > 1)) {
184 biffAddf(NRRD, "%s: has single sample along sole axis", me);
185 return 1;
186 }
187
188 return 0;
189 }
190
191 int
nrrdHistoDraw(Nrrd * nout,const Nrrd * nin,size_t sy,int showLog,double max)192 nrrdHistoDraw(Nrrd *nout, const Nrrd *nin,
193 size_t sy, int showLog, double max) {
194 static const char me[]="nrrdHistoDraw", func[]="dhisto";
195 char cmt[AIR_STRLEN_MED], stmp[AIR_STRLEN_SMALL];
196 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 if (!(nin && nout && sy > 0)) {
204 biffAddf(NRRD, "%s: invalid args", me);
205 return 1;
206 }
207 if (nout == nin) {
208 biffAddf(NRRD, "%s: nout==nin disallowed", me);
209 return 1;
210 }
211 if (nrrdHistoCheck(nin)) {
212 biffAddf(NRRD, "%s: input nrrd not a histogram", me);
213 return 1;
214 }
215 sx = nin->axis[0].size;
216 nrrdBasicInfoInit(nout, NRRD_BASIC_INFO_DATA_BIT);
217 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 nout->axis[0].spacing = nout->axis[1].spacing = AIR_NAN;
223 nout->axis[0].thickness = nout->axis[1].thickness = AIR_NAN;
224 nout->axis[0].min = nin->axis[0].min;
225 nout->axis[0].max = nin->axis[0].max;
226 nout->axis[0].center = nout->axis[1].center = nrrdCenterCell;
227 nout->axis[0].label = (char *)airStrdup(nin->axis[0].label);
228 nout->axis[1].label = (char *)airFree(nout->axis[1].label);
229 pgmData = (unsigned char *)nout->data;
230 maxhits = 0.0;
231 maxhitidx = 0;
232 for (xi=0; xi<sx; xi++) {
233 hits = nrrdDLookup[nin->type](nin->data, xi);
234 if (maxhits < hits) {
235 maxhits = hits;
236 maxhitidx = xi;
237 }
238 }
239 if (AIR_EXISTS(max) && max > 0) {
240 usemaxhits = max;
241 } else {
242 usemaxhits = maxhits;
243 }
244 nout->axis[1].min = usemaxhits;
245 nout->axis[1].max = 0;
246 numticks = AIR_CAST(unsigned int, log10(usemaxhits + 1));
247 mop = airMopNew();
248 ticks = AIR_CALLOC(numticks, unsigned int);
249 airMopMem(mop, &ticks, airMopAlways);
250 linY = AIR_CALLOC(sx, unsigned int);
251 airMopMem(mop, &linY, airMopAlways);
252 logY = AIR_CALLOC(sx, unsigned int);
253 airMopMem(mop, &logY, airMopAlways);
254 if (!(ticks && linY && logY)) {
255 biffAddf(NRRD, "%s: failed to allocate temp arrays", me);
256 airMopError(mop); return 1;
257 }
258 for (ki=0; ki<numticks; ki++) {
259 ticks[ki] = airIndex(0, log10(pow(10,ki+1) + 1), log10(usemaxhits+1),
260 AIR_CAST(unsigned int, sy));
261 }
262 for (xi=0; xi<sx; xi++) {
263 hits = nrrdDLookup[nin->type](nin->data, xi);
264 linY[xi] = airIndex(0, hits, usemaxhits,
265 AIR_CAST(unsigned int, sy));
266 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 for (yi=0; yi<sy; yi++) {
271 tick = 0;
272 for (ki=0; ki<numticks; ki++)
273 tick |= ticks[ki] == yi;
274 for (xi=0; xi<sx; xi++) {
275 pgmData[xi + sx*(sy-1-yi)] =
276 (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 : (!showLog
285 ? (yi >= linY[xi] ? 0 : 255)
286 : (yi >= logY[xi] /* above log curve */
287 ? (!tick ? 0 /* not on tick mark */
288 : 255) /* on tick mark */
289 : (yi >= linY[xi] /* below log curve, above normal curve */
290 ? (!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 sprintf(cmt, "min value: %g\n", nout->axis[0].min);
301 if (!E) E |= nrrdCommentAdd(nout, cmt);
302 sprintf(cmt, "max value: %g\n", nout->axis[0].max);
303 if (!E) E |= nrrdCommentAdd(nout, cmt);
304 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 if (!E) E |= nrrdCommentAdd(nout, cmt);
308 if (!E) E |= nrrdContentSet_va(nout, func, nin, "%s",
309 airSprintSize_t(stmp, sy));
310 if (E) {
311 biffAddf(NRRD, "%s:", me);
312 airMopError(mop); return 1;
313 }
314
315 /* bye */
316 airMopOkay(mop);
317 return 0;
318 }
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
nrrdHistoAxis(Nrrd * nout,const Nrrd * nin,const NrrdRange * _range,unsigned int hax,size_t bins,int type)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
nrrdHistoJoint(Nrrd * nout,const Nrrd * const * nin,const NrrdRange * const * _range,unsigned int numNin,const Nrrd * nwght,const size_t * bins,int type,const int * clamp)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 size_t Iin, Iout, numEl, coord[NRRD_DIM_MAX], totalContentStrlen;
458 airArray *mop;
459 NrrdRange **range;
460 unsigned int nii, ai;
461
462 /* error checking */
463 /* nwght can be NULL -> weighting is constant 1.0 */
464 if (!(nout && nin && bins && clamp)) {
465 biffAddf(NRRD, "%s: got NULL pointer", me);
466 return 1;
467 }
468 if (!(numNin >= 1)) {
469 biffAddf(NRRD, "%s: need numNin >= 1 (not %d)", me, numNin);
470 return 1;
471 }
472 if (numNin > NRRD_DIM_MAX) {
473 biffAddf(NRRD, "%s: can only deal with up to %d nrrds (not %d)", me,
474 NRRD_DIM_MAX, numNin);
475 return 1;
476 }
477 for (nii=0; nii<numNin; nii++) {
478 if (!(nin[nii])) {
479 biffAddf(NRRD, "%s: input nrrd #%u NULL", me, nii);
480 return 1;
481 }
482 if (nout==nin[nii]) {
483 biffAddf(NRRD, "%s: nout==nin[%d] disallowed", me, nii);
484 return 1;
485 }
486 if (nrrdTypeBlock == nin[nii]->type) {
487 biffAddf(NRRD, "%s: nin[%d] type %s invalid", me, nii,
488 airEnumStr(nrrdType, nrrdTypeBlock));
489 return 1;
490 }
491 }
492 if (airEnumValCheck(nrrdType, type) || nrrdTypeBlock == type) {
493 biffAddf(NRRD, "%s: invalid nrrd type %d", me, type);
494 return 1;
495 }
496 mop = airMopNew();
497 range = AIR_CALLOC(numNin, NrrdRange*);
498 airMopAdd(mop, range, airFree, airMopAlways);
499 for (ai=0; ai<numNin; ai++) {
500 if (_range && _range[ai]) {
501 range[ai] = nrrdRangeCopy(_range[ai]);
502 nrrdRangeSafeSet(range[ai], nin[ai], nrrdBlind8BitRangeState);
503 } else {
504 range[ai] = nrrdRangeNewSet(nin[ai], nrrdBlind8BitRangeState);
505 }
506 airMopAdd(mop, range[ai], (airMopper)nrrdRangeNix, airMopAlways);
507 }
508 for (ai=0; ai<numNin; ai++) {
509 if (!nin[ai]) {
510 biffAddf(NRRD, "%s: input nrrd[%d] NULL", me, ai);
511 return 1;
512 }
513 if (!(bins[ai] >= 1)) {
514 char stmp[AIR_STRLEN_SMALL];
515 biffAddf(NRRD, "%s: need bins[%u] >= 1 (not %s)", me, ai,
516 airSprintSize_t(stmp, bins[ai]));
517 return 1;
518 }
519 if (ai && !nrrdSameSize(nin[0], nin[ai], AIR_TRUE)) {
520 biffAddf(NRRD, "%s: nin[0] (n1) mismatch with nin[%u] (n2)", me, ai);
521 return 1;
522 }
523 }
524
525 /* check nwght */
526 if (nwght) {
527 if (nout==nwght) {
528 biffAddf(NRRD, "%s: nout==nwght disallowed", me);
529 return 1;
530 }
531 if (nrrdTypeBlock == nwght->type) {
532 biffAddf(NRRD, "%s: nwght type %s invalid", me,
533 airEnumStr(nrrdType, nrrdTypeBlock));
534 return 1;
535 }
536 if (!nrrdSameSize(nin[0], nwght, AIR_TRUE)) {
537 biffAddf(NRRD, "%s: nwght size mismatch with nin[0]", me);
538 return 1;
539 }
540 lup = nrrdDLookup[nwght->type];
541 } else {
542 lup = NULL;
543 }
544
545 /* allocate output nrrd */
546 if (nrrdMaybeAlloc_nva(nout, type, numNin, bins)) {
547 biffAddf(NRRD, "%s: couldn't allocate output histogram", me);
548 return 1;
549 }
550 hadContent = 0;
551 totalContentStrlen = 0;
552 for (ai=0; ai<numNin; ai++) {
553 nout->axis[ai].size = bins[ai];
554 nout->axis[ai].spacing = AIR_NAN;
555 nout->axis[ai].thickness = AIR_NAN;
556 nout->axis[ai].min = range[ai]->min;
557 nout->axis[ai].max = range[ai]->max;
558 nout->axis[ai].center = nrrdCenterCell;
559 if (!nrrdStateKindNoop) {
560 nout->axis[ai].kind = nrrdKindDomain;
561 }
562 if (nin[ai]->content) {
563 hadContent = 1;
564 totalContentStrlen += strlen(nin[ai]->content);
565 nout->axis[ai].label = AIR_CALLOC(strlen("histo(,)")
566 + strlen(nin[ai]->content)
567 + 11
568 + 1, char);
569 if (nout->axis[ai].label) {
570 char stmp[AIR_STRLEN_SMALL];
571 sprintf(nout->axis[ai].label, "histo(%s,%s)", nin[ai]->content,
572 airSprintSize_t(stmp, bins[ai]));
573 } else {
574 biffAddf(NRRD, "%s: couldn't allocate output label #%u", me, ai);
575 return 1;
576 }
577 } else {
578 nout->axis[ai].label = (char *)airFree(nout->axis[ai].label);
579 totalContentStrlen += 2;
580 }
581 }
582
583 /* the skinny */
584 numEl = nrrdElementNumber(nin[0]);
585 for (Iin=0; Iin<numEl; Iin++) {
586 skip = 0;
587 for (ai=0; ai<numNin; ai++) {
588 val = nrrdDLookup[nin[ai]->type](nin[ai]->data, Iin);
589 if (!AIR_EXISTS(val)) {
590 /* coordinate d in the joint histo can't be determined
591 if nin[ai] has a non-existent value here */
592 skip = 1;
593 break;
594 }
595 if (!AIR_IN_CL(range[ai]->min, val, range[ai]->max)) {
596 if (clamp[ai]) {
597 val = AIR_CLAMP(range[ai]->min, val, range[ai]->max);
598 } else {
599 skip = 1;
600 break;
601 }
602 }
603 coord[ai] = AIR_CAST(size_t, airIndexClampULL(range[ai]->min,
604 val,
605 range[ai]->max,
606 bins[ai]));
607 /* printf(" -> coord = %d; ", coord[d]); fflush(stdout); */
608 }
609 if (skip) {
610 continue;
611 }
612 NRRD_INDEX_GEN(Iout, coord, bins, numNin);
613 count = nrrdDLookup[nout->type](nout->data, Iout);
614 incr = nwght ? lup(nwght->data, Iin) : 1.0;
615 count = nrrdDClamp[nout->type](count + incr);
616 nrrdDInsert[nout->type](nout->data, Iout, count);
617 }
618
619 /* HEY: switch to nrrdContentSet_va? */
620 if (hadContent) {
621 nout->content = AIR_CALLOC(strlen(func) + strlen("()")
622 + numNin*strlen(",")
623 + totalContentStrlen
624 + 1, char);
625 if (nout->content) {
626 size_t len;
627 sprintf(nout->content, "%s(", func);
628 for (ai=0; ai<numNin; ai++) {
629 len = strlen(nout->content);
630 strcpy(nout->content + len,
631 nin[ai]->content ? nin[ai]->content : "?");
632 len = strlen(nout->content);
633 nout->content[len] = ai < numNin-1 ? ',' : ')';
634 }
635 nout->content[len+1] = '\0';
636 } else {
637 biffAddf(NRRD, "%s: couldn't allocate output content", me);
638 return 1;
639 }
640 }
641
642 airMopOkay(mop);
643 return 0;
644 }
645
646 /*
647 ******** nrrdHistoThresholdOtsu
648 **
649 ** does simple Otsu tresholding of a histogram, with a variable exponont.
650 ** When "expo" is 2.0, it computes variance; lower values probably represent
651 ** greater insensitivities to outliers. Idea from ...
652 */
653 int
nrrdHistoThresholdOtsu(double * threshP,const Nrrd * _nhist,double expo)654 nrrdHistoThresholdOtsu(double *threshP, const Nrrd *_nhist, double expo) {
655 static const char me[]="nrrdHistoThresholdOtsu";
656 unsigned int histLen, histIdx, maxIdx;
657 Nrrd *nhist, *nbvar;
658 double *hist, *bvar, thresh, num0, num1, mean0, mean1,
659 onum0, onum1, omean0, omean1, max;
660 airArray *mop;
661
662 if (!(threshP && _nhist)) {
663 biffAddf(NRRD, "%s: got NULL pointer", me);
664 return 1;
665 }
666 if (nrrdHistoCheck(_nhist)) {
667 biffAddf(NRRD, "%s: input nrrd not a histogram", me);
668 return 1;
669 }
670
671 mop = airMopNew();
672 airMopAdd(mop, nhist = nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
673 airMopAdd(mop, nbvar = nrrdNew(), (airMopper)nrrdNuke, airMopAlways);
674 if (nrrdConvert(nhist, _nhist, nrrdTypeDouble)
675 || nrrdCopy(nbvar, nhist)) {
676 biffAddf(NRRD, "%s: making local copies", me);
677 airMopError(mop); return 1;
678 }
679 hist = AIR_CAST(double*, nhist->data);
680 bvar = AIR_CAST(double*, nbvar->data);
681
682 histLen = AIR_CAST(unsigned int, nhist->axis[0].size);
683 num1 = mean1 = 0;
684 for (histIdx=0; histIdx<histLen; histIdx++) {
685 num1 += hist[histIdx];
686 mean1 += hist[histIdx]*histIdx;
687 }
688 if (num1) {
689 num0 = 0;
690 mean0 = 0;
691 mean1 /= num1;
692 for (histIdx=0; histIdx<histLen; histIdx++) {
693 if (histIdx) {
694 onum0 = num0;
695 onum1 = num1;
696 omean0 = mean0;
697 omean1 = mean1;
698 num0 = onum0 + hist[histIdx-1];
699 num1 = onum1 - hist[histIdx-1];
700 mean0 = (omean0*onum0 + hist[histIdx-1]*(histIdx-1)) / num0;
701 mean1 = (omean1*onum1 - hist[histIdx-1]*(histIdx-1)) / num1;
702 }
703 bvar[histIdx] = num0*num1*airSgnPow(mean1 - mean0, expo);
704 }
705 max = bvar[0];
706 maxIdx = 0;
707 for (histIdx=1; histIdx<histLen; histIdx++) {
708 if (bvar[histIdx] > max) {
709 max = bvar[histIdx];
710 maxIdx = histIdx;
711 }
712 }
713 thresh = maxIdx;
714 } else {
715 thresh = histLen/2;
716 }
717
718 if (AIR_EXISTS(nhist->axis[0].min) && AIR_EXISTS(nhist->axis[0].max)) {
719 thresh = NRRD_CELL_POS(nhist->axis[0].min, nhist->axis[0].max,
720 histLen, thresh);
721 }
722 *threshP = thresh;
723 airMopOkay(mop);
724 return 0;
725 }
726