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