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 
25 #include "pull.h"
26 #include "privatePull.h"
27 
28 pullTrace *
pullTraceNew(void)29 pullTraceNew(void) {
30   pullTrace *ret;
31 
32   ret = AIR_CALLOC(1, pullTrace);
33   if (ret) {
34     ret->seedPos[0] = ret->seedPos[1] = AIR_NAN;
35     ret->seedPos[2] = ret->seedPos[3] = AIR_NAN;
36     ret->nvert = nrrdNew();
37     ret->nstrn = nrrdNew();
38     ret->nvelo = nrrdNew();
39     ret->seedIdx = 0;
40     ret->whyStop[0] = ret->whyStop[1] = pullTraceStopUnknown;
41     ret->whyNowhere = pullTraceStopUnknown;
42   }
43   return ret;
44 }
45 
46 pullTrace *
pullTraceNix(pullTrace * pts)47 pullTraceNix(pullTrace *pts) {
48 
49   if (pts) {
50     nrrdNuke(pts->nvert);
51     nrrdNuke(pts->nstrn);
52     nrrdNuke(pts->nvelo);
53     free(pts);
54   }
55   return NULL;
56 }
57 
58 
59 int
pullTraceSet(pullContext * pctx,pullTrace * pts,int recordStrength,double scaleDelta,double halfScaleWin,double velocityMax,unsigned int arrIncr,const double seedPos[4])60 pullTraceSet(pullContext *pctx, pullTrace *pts,
61              int recordStrength,
62              double scaleDelta, double halfScaleWin,
63              double velocityMax, unsigned int arrIncr,
64              const double seedPos[4]) {
65   static const char me[]="pullTraceSet";
66   pullPoint *point;
67   airArray *mop, *trceArr[2], *hstrnArr[2];
68   double *trce[2], ssrange[2], *vert, *hstrn[2], *strn, *velo, travmax;
69   int constrFail;
70   unsigned int dirIdx, lentmp, tidx, oidx, vertNum;
71 
72   if (!( pctx && pts && seedPos )) {
73     biffAddf(PULL, "%s: got NULL pointer", me);
74     return 1;
75   }
76   if (!( AIR_EXISTS(scaleDelta) && scaleDelta > 0.0 )) {
77     biffAddf(PULL, "%s: need existing scaleDelta > 0 (not %g)",
78              me, scaleDelta);
79     return 1;
80   }
81   if (!( halfScaleWin > 0 )) {
82     biffAddf(PULL, "%s: need halfScaleWin > 0", me);
83     return 1;
84   }
85   if (!(pctx->constraint)) {
86     biffAddf(PULL, "%s: given context doesn't have constraint set", me);
87     return 1;
88   }
89   if (recordStrength && !pctx->ispec[pullInfoStrength]) {
90     biffAddf(PULL, "%s: want to record strength but %s not set in context",
91              me, airEnumStr(pullInfo, pullInfoStrength));
92     return 1;
93   }
94   if (pullConstraintScaleRange(pctx, ssrange)) {
95     biffAddf(PULL, "%s: trouble getting scale range", me);
96     return 1;
97   }
98 
99   /* re-initialize termination descriptions (in case of trace re-use) */
100   pts->whyStop[0] = pts->whyStop[1] = pullTraceStopUnknown;
101   pts->whyNowhere = pullTraceStopUnknown;
102 
103   /* save seedPos in any case */
104   ELL_4V_COPY(pts->seedPos, seedPos);
105 
106   mop = airMopNew();
107   point = pullPointNew(pctx); /* we'll want to decrement idtagNext later */
108   airMopAdd(mop, point, (airMopper)pullPointNix, airMopAlways);
109 
110   ELL_4V_COPY(point->pos, seedPos);
111   if (_pullConstraintSatisfy(pctx->task[0], point, 2, &constrFail)) {
112     biffAddf(PULL, "%s: constraint sat on seed point", me);
113     airMopError(mop);
114     return 1;
115   }
116   /*
117   fprintf(stderr, "!%s: seed=(%g,%g,%g,%g) -> %s (%g,%g,%g,%g)\n", me,
118           seedPos[0], seedPos[1], seedPos[2], seedPos[3],
119           constrFail ? "!NO!" : "(yes)",
120           point->pos[0] - seedPos[0], point->pos[1] - seedPos[1],
121           point->pos[2] - seedPos[2], point->pos[3] - seedPos[3]);
122   */
123   if (constrFail) {
124     pts->whyNowhere = pullTraceStopConstrFail;
125     airMopOkay(mop);
126     pctx->idtagNext -= 1; /* HACK */
127     return 0;
128   }
129 
130   /* else constraint sat worked at seed point; we have work to do */
131   /* travmax is passed to _pullConstraintSatisfy; the intention is
132      that constraint satisfaction should not fail because the point
133      traveled too far (so make travmax large); the limiting of the
134      trace based on velocity is handled here, not by
135      _pullConstraintSatisfy */
136   travmax = 10.0*scaleDelta*velocityMax/pctx->voxelSizeSpace;
137 
138   for (dirIdx=0; dirIdx<2; dirIdx++) {
139     trceArr[dirIdx] = airArrayNew((void**)(trce + dirIdx), NULL,
140                                   4*sizeof(double), arrIncr);
141     airMopAdd(mop, trceArr[dirIdx], (airMopper)airArrayNuke, airMopAlways);
142     if (recordStrength) {
143       hstrnArr[dirIdx] = airArrayNew((void**)(hstrn + dirIdx), NULL,
144                                      sizeof(double), arrIncr);
145       airMopAdd(mop, hstrnArr[dirIdx], (airMopper)airArrayNuke, airMopAlways);
146     } else {
147       hstrnArr[dirIdx] = NULL;
148       hstrn[dirIdx] = NULL;
149     }
150   }
151   for (dirIdx=0; dirIdx<2; dirIdx++) {
152     unsigned int step;
153     double dscl;
154     dscl = (!dirIdx ? -1 : +1)*scaleDelta;
155     step = 0;
156     while (1) {
157       if (!step) {
158         /* first step in both directions requires special tricks */
159         if (0 == dirIdx) {
160           /* save constraint sat of seed point */
161           tidx = airArrayLenIncr(trceArr[0], 1);
162           ELL_4V_COPY(trce[0] + 4*tidx, point->pos);
163           if (recordStrength) {
164             tidx = airArrayLenIncr(hstrnArr[0], 1);
165             hstrn[0][0] = pullPointScalar(pctx, point, pullInfoStrength,
166                                           NULL, NULL);
167           }
168         } else {
169           /* re-set position from constraint sat of seed pos */
170           ELL_4V_COPY(point->pos, trce[0] + 4*0);
171         }
172       }
173       /* nudge position along scale */
174       point->pos[3] += dscl;
175       if (!AIR_IN_OP(ssrange[0], point->pos[3], ssrange[1])) {
176         /* if we've stepped outside the range of scale for the volume
177            containing the constraint manifold, we're done */
178         pts->whyStop[dirIdx] = pullTraceStopBounds;
179         break;
180       }
181       if (AIR_ABS(point->pos[3] - seedPos[3]) > halfScaleWin) {
182         /* we've moved along scale as far as allowed */
183         pts->whyStop[dirIdx] = pullTraceStopLength;
184         break;
185       }
186       /* re-assert constraint */
187       /*
188       fprintf(stderr, "%s(%u): pos = %g %g %g %g.... \n", me,
189               point->idtag, point->pos[0], point->pos[1],
190               point->pos[2], point->pos[3]);
191       */
192       if (_pullConstraintSatisfy(pctx->task[0], point,
193                                  travmax, &constrFail)) {
194         biffAddf(PULL, "%s: dir %u, step %u", me, dirIdx, step);
195         airMopError(mop);
196         return 1;
197       }
198       /*
199       fprintf(stderr, "%s(%u): ... %s(%d); pos = %g %g %g %g\n", me,
200               point->idtag,
201               constrFail ? "FAIL" : "(ok)",
202               constrFail, point->pos[0], point->pos[1],
203               point->pos[2], point->pos[3]);
204       */
205       if (constrFail) {
206         /* constraint sat failed; no error, we're just done
207            with stepping for this direction */
208         pts->whyStop[dirIdx] = pullTraceStopConstrFail;
209         break;
210       }
211       if (trceArr[dirIdx]->len >= 2) {
212         /* see if we're moving too fast, by comparing with previous point */
213         double pos0[3], pos1[3], diff[3], vv;
214         unsigned int ii;
215 
216         ii = trceArr[dirIdx]->len-2;
217         ELL_3V_COPY(pos0, trce[dirIdx] + 4*(ii+0));
218         ELL_3V_COPY(pos1, trce[dirIdx] + 4*(ii+1));
219         ELL_3V_SUB(diff, pos1, pos0);
220         vv = ELL_3V_LEN(diff)/scaleDelta;
221         /*
222         fprintf(stderr, "%s(%u): velo %g %s velocityMax %g => %s\n", me,
223                 point->idtag, vv,
224                 vv > velocityMax ? ">" : "<=",
225                 velocityMax,
226                 vv > velocityMax ? "FAIL" : "(ok)");
227         */
228         if (vv > velocityMax) {
229           pts->whyStop[dirIdx] = pullTraceStopSpeeding;
230           break;
231         }
232       }
233       /* else save new point on trace */
234       tidx = airArrayLenIncr(trceArr[dirIdx], 1);
235       ELL_4V_COPY(trce[dirIdx] + 4*tidx, point->pos);
236       if (recordStrength) {
237         tidx = airArrayLenIncr(hstrnArr[dirIdx], 1);
238         hstrn[dirIdx][tidx] = pullPointScalar(pctx, point, pullInfoStrength,
239                                               NULL, NULL);
240       }
241       step++;
242     }
243   }
244 
245   /* transfer trace halves to pts->nvert */
246   vertNum = trceArr[0]->len + trceArr[1]->len;
247   if (0 == vertNum || 1 == vertNum || 2 == vertNum) {
248     pts->whyNowhere = pullTraceStopStub;
249     airMopOkay(mop);
250     pctx->idtagNext -= 1; /* HACK */
251     return 0;
252   }
253 
254   if (nrrdMaybeAlloc_va(pts->nvert, nrrdTypeDouble, 2,
255                         AIR_CAST(size_t, 4),
256                         AIR_CAST(size_t, vertNum))
257       || nrrdMaybeAlloc_va(pts->nvelo, nrrdTypeDouble, 1,
258                            AIR_CAST(size_t, vertNum))) {
259     biffMovef(PULL, NRRD, "%s: allocating output", me);
260     airMopError(mop);
261     return 1;
262   }
263   if (recordStrength) {
264     if (nrrdSlice(pts->nstrn, pts->nvert, 0 /* axis */, 0 /* pos */)) {
265       biffMovef(PULL, NRRD, "%s: allocating output", me);
266       airMopError(mop);
267       return 1;
268     }
269   }
270   vert = AIR_CAST(double *, pts->nvert->data);
271   if (recordStrength) {
272     strn = AIR_CAST(double *, pts->nstrn->data);
273   } else {
274     strn = NULL;
275   }
276   velo = AIR_CAST(double *, pts->nvelo->data);
277   lentmp = trceArr[0]->len;
278   oidx = 0;
279   for (tidx=0; tidx<lentmp; tidx++) {
280     ELL_4V_COPY(vert + 4*oidx, trce[0] + 4*(lentmp - 1 - tidx));
281     if (strn) {
282       strn[oidx] = hstrn[0][lentmp - 1 - tidx];
283     }
284     oidx++;
285   }
286   /* the last index written to (before oidx++) was the seed index */
287   pts->seedIdx = oidx-1;
288   lentmp = trceArr[1]->len;
289   for (tidx=0; tidx<lentmp; tidx++) {
290     ELL_4V_COPY(vert + 4*oidx, trce[1] + 4*tidx);
291     if (strn) {
292       strn[oidx] = hstrn[1][tidx];
293     }
294     oidx++;
295   }
296   lentmp = pts->nvelo->axis[0].size;
297   if (1 == lentmp) {
298     velo[0] = 0.0;
299   } else {
300     for (tidx=0; tidx<lentmp; tidx++) {
301       double *p0, *p1, *p2, diff[3];
302       if (!tidx) {
303         /* first */
304         p1 = vert + 4*tidx;
305         p2 = vert + 4*(tidx+1);
306         ELL_3V_SUB(diff, p2, p1);
307         velo[tidx] = ELL_3V_LEN(diff)/(p2[3]-p1[3]);
308       } else if (tidx < lentmp-1) {
309         /* middle */
310         p0 = vert + 4*(tidx-1);
311         p2 = vert + 4*(tidx+1);
312         ELL_3V_SUB(diff, p2, p0);
313         velo[tidx] = ELL_3V_LEN(diff)/(p2[3]-p0[3]);
314       } else {
315         /* last */
316         p0 = vert + 4*(tidx-1);
317         p1 = vert + 4*tidx;
318         ELL_3V_SUB(diff, p1, p0);
319         velo[tidx] = ELL_3V_LEN(diff)/(p1[3]-p0[3]);
320       }
321     }
322   }
323 
324   airMopOkay(mop);
325   pctx->idtagNext -= 1; /* HACK */
326   return 0;
327 }
328 
329 typedef union {
330   pullTrace ***trace;
331   void **v;
332 } blahblahUnion;
333 
334 pullTraceMulti *
pullTraceMultiNew(void)335 pullTraceMultiNew(void) {
336   /* static const char me[]="pullTraceMultiNew"; */
337   pullTraceMulti *ret;
338   blahblahUnion bbu;
339 
340   ret = AIR_CALLOC(1, pullTraceMulti);
341   if (ret) {
342     ret->trace = NULL;
343     ret->traceNum = 0;
344     ret->traceArr = airArrayNew((bbu.trace = &(ret->trace), bbu.v),
345                                 &(ret->traceNum), sizeof(pullTrace*),
346                                 _PULL_TRACE_MULTI_INCR);
347     airArrayPointerCB(ret->traceArr,
348                       NULL, /* because we get handed pullTrace structs
349                                that have already been allocated
350                                (and then we own them) */
351                       (void *(*)(void *))pullTraceNix);
352   }
353   return ret;
354 }
355 
356 int
pullTraceMultiAdd(pullTraceMulti * mtrc,pullTrace * trc,int * addedP)357 pullTraceMultiAdd(pullTraceMulti *mtrc, pullTrace *trc, int *addedP) {
358   static const char me[]="pullTraceMultiAdd";
359   unsigned int indx;
360 
361   if (!(mtrc && trc && addedP)) {
362     biffAddf(PULL, "%s: got NULL pointer", me);
363     return 1;
364   }
365   if (!(trc->nvert->data && trc->nvert->axis[1].size >= 3)) {
366     /*  for now getting a stub trace is not an error
367     biffAddf(PULL, "%s: got stub trace", me);
368     return 1; */
369     *addedP = AIR_FALSE;
370     return 0;
371   }
372   if (!(trc->nvelo->data
373         && trc->nvelo->axis[0].size == trc->nvert->axis[1].size)) {
374     biffAddf(PULL, "%s: velo data inconsistent", me);
375     return 1;
376   }
377   *addedP = AIR_TRUE;
378   indx = airArrayLenIncr(mtrc->traceArr, 1);
379   if (!mtrc->trace) {
380     biffAddf(PULL, "%s: alloc error", me);
381     return 1;
382   }
383   mtrc->trace[indx] = trc;
384   return 0;
385 }
386 
387 int
pullTraceMultiFilterConcaveDown(Nrrd * nfilt,const pullTraceMulti * mtrc,double winLenFrac)388 pullTraceMultiFilterConcaveDown(Nrrd *nfilt, const pullTraceMulti *mtrc,
389                                 double winLenFrac) {
390   static const char me[]="pullTraceMultiFilterConcaveDown";
391   unsigned int ti;
392   int *filt;
393 
394   if (!(nfilt && mtrc)) {
395     biffAddf(PULL, "%s: got NULL pointer (%p %p)", me,
396              AIR_VOIDP(nfilt), AIR_CVOIDP(mtrc));
397     return 1;
398   }
399   if (!(AIR_EXISTS(winLenFrac) && AIR_IN_OP(0.0, winLenFrac, 1.0))) {
400     biffAddf(PULL, "%s: winLenFrac %g doesn't exist or not in [0,1]",
401              me, winLenFrac);
402     return 1;
403   }
404   if (nrrdMaybeAlloc_va(nfilt, nrrdTypeInt, 1, mtrc->traceNum)) {
405     biffMovef(PULL, NRRD, "%s: trouble creating output", me);
406     return 1;
407   }
408   filt = AIR_CAST(int *, nfilt->data);
409   for (ti=0; ti<mtrc->traceNum; ti++) {
410     unsigned winLen;
411     const pullTrace *trc;
412     const double *velo;
413     unsigned int schange, pidx, lentmp;
414     double dv, dv0=0.0, rdv, dv1;
415 
416     trc = mtrc->trace[ti];
417     lentmp = trc->nvert->axis[1].size;
418     velo = AIR_CAST(const double *, trc->nvelo->data);
419     winLen = AIR_CAST(unsigned int, winLenFrac*lentmp);
420     if (winLen < 3) {
421       continue;
422     }
423     schange = 0;
424     rdv = 0.0;
425     for (pidx=0; pidx<lentmp-1; pidx++) {
426       /* normalizing by scaleDelta isn't needed for detecting sign changes */
427       dv = velo[pidx+1] - velo[pidx];
428       if (pidx < winLen) {
429         rdv += dv;
430       } else {
431         double tmp;
432         if (pidx == winLen) {
433           dv0 = rdv;
434         }
435         tmp = rdv;
436         rdv += dv;
437         rdv -= velo[pidx-winLen+1] - velo[pidx-winLen];
438         schange += (rdv*tmp < 0);
439       }
440     }
441     dv1 = rdv;
442     filt[ti] = (1 == schange) && dv0 < 0.0 && dv1 > 0.0;
443   }
444   return 0;
445 }
446 
447 int
pullTraceMultiPlotAdd(Nrrd * nplot,const pullTraceMulti * mtrc,const Nrrd * nfilt,unsigned int trcIdxMin,unsigned int trcNum)448 pullTraceMultiPlotAdd(Nrrd *nplot, const pullTraceMulti *mtrc,
449                       const Nrrd *nfilt,
450                       unsigned int trcIdxMin,unsigned int trcNum) {
451   static const char me[]="pullTraceMultiPlot";
452   double ssRange[2], vRange[2], velHalf, *plot;
453   unsigned int sizeS, sizeV, trcIdx, trcIdxMax;
454   int *filt;
455 
456   if (!(nplot && mtrc)) {
457     biffAddf(PULL, "%s: got NULL pointer", me);
458     return 1;
459   }
460   if (nrrdCheck(nplot)) {
461     biffMovef(PULL, NRRD, "%s: trouble with nplot", me);
462     return 1;
463   }
464   if (nfilt) {
465     if (nrrdCheck(nfilt)) {
466       biffMovef(PULL, NRRD, "%s: trouble with nfilt", me);
467       return 1;
468     }
469     if (!(1 == nfilt->dim && nrrdTypeInt == nfilt->type)) {
470       biffAddf(PULL, "%s: didn't get 1-D array of %s (got %u-D of %s)", me,
471                airEnumStr(nrrdType, nrrdTypeInt), nfilt->dim,
472                airEnumStr(nrrdType, nfilt->type));
473       return 1;
474     }
475   }
476   if (!(2 == nplot->dim && nrrdTypeDouble == nplot->type)) {
477     biffAddf(PULL, "%s: didn't get 2-D array of %s (got %u-D of %s)", me,
478              airEnumStr(nrrdType, nrrdTypeDouble), nplot->dim,
479              airEnumStr(nrrdType, nplot->type));
480     return 1;
481   }
482   if (!(trcIdxMin < mtrc->traceNum)) {
483     biffAddf(PULL, "%s: trcIdxMin %u not < traceNum %u", me,
484              trcIdxMin, mtrc->traceNum);
485     return 1;
486   }
487   if (trcNum) {
488     trcIdxMax = trcIdxMin + trcNum-1;
489     if (!(trcIdxMax < mtrc->traceNum)) {
490       biffAddf(PULL, "%s: trcIdxMax %u = %u+%u-1 not < traceNum %u", me,
491                trcIdxMax, trcIdxMin, trcNum, mtrc->traceNum);
492       return 1;
493     }
494   } else {
495     trcIdxMax = mtrc->traceNum-1;
496   }
497   ssRange[0] = nplot->axis[0].min;
498   ssRange[1] = nplot->axis[0].max;
499   vRange[0] = nplot->axis[1].min;
500   vRange[1] = nplot->axis[1].max;
501   if (!( AIR_EXISTS(ssRange[0]) && AIR_EXISTS(ssRange[1]) &&
502          AIR_EXISTS(vRange[0]) && AIR_EXISTS(vRange[1]) )) {
503     biffAddf(PULL, "%s: need both axis 0 (%g,%g) and 1 (%g,%g) min,max", me,
504              ssRange[0], ssRange[1], vRange[0], vRange[1]);
505     return 1;
506   }
507   if (0 != vRange[0]) {
508     biffAddf(PULL, "%s: expected vRange[0] == 0 not %g", me, vRange[0]);
509     return 1;
510   }
511   /* HEY: this is a sneaky hack; the non-linear encoding of velocity along
512      this axis means that the max velocity is actually infinite, but this
513      seems like the least wrong way of storing this information in the place
514      where it belongs (in the output plot nrrd) instead of assuming it will
515      always be passed the same in successive calls */
516   velHalf = vRange[1]/2.0;
517   plot = AIR_CAST(double *, nplot->data);
518   filt = (nfilt
519           ? AIR_CAST(int *, nfilt->data)
520           : NULL);
521   sizeS = AIR_CAST(unsigned int, nplot->axis[0].size);
522   sizeV = AIR_CAST(unsigned int, nplot->axis[1].size);
523   for (trcIdx=trcIdxMin; trcIdx<=trcIdxMax; trcIdx++) {
524     unsigned int pntIdx, pntNum;
525     const pullTrace *trc;
526     const double *vert, *velo;
527     if (filt && !filt[trcIdx]) {
528       continue;
529     }
530     trc = mtrc->trace[trcIdx];
531     if (pullTraceStopStub == trc->whyNowhere) {
532       continue;
533     }
534     vert = AIR_CAST(double *, trc->nvert->data);
535     velo = AIR_CAST(double *, trc->nvelo->data);
536     pntNum = trc->nvert->axis[1].size;
537     for (pntIdx=0; pntIdx<pntNum; pntIdx++) {
538       const double *pp;
539       unsigned int sidx, vidx;
540       pp = vert + 4*pntIdx;
541       if (!(AIR_IN_OP(ssRange[0], pp[3], ssRange[1]))) {
542         continue;
543       }
544       if (velo[pntIdx] <= 0.0) {
545         continue;
546       }
547       sidx = airIndex(ssRange[0], pp[3], ssRange[1], sizeS);
548       /* HEY weird that Clamp is needed, but it is, as this atan()
549          does sometime return a negative value (?) */
550       vidx = airIndexClamp(0.0, atan(velo[pntIdx]/velHalf), AIR_PI/2, sizeV);
551       plot[sidx + sizeS*vidx] += 1;
552     }
553   }
554   return 0;
555 }
556 
557 static size_t
nsizeof(const Nrrd * nrrd)558 nsizeof(const Nrrd *nrrd) {
559   return (nrrd
560           ? nrrdElementSize(nrrd)*nrrdElementNumber(nrrd)
561           : 0);
562 }
563 
564 size_t
pullTraceMultiSizeof(const pullTraceMulti * mtrc)565 pullTraceMultiSizeof(const pullTraceMulti *mtrc) {
566   size_t ret;
567   unsigned int ti;
568 
569   if (!mtrc) {
570     return 0;
571   }
572   ret = 0;
573   for (ti=0; ti<mtrc->traceNum; ti++) {
574     ret += sizeof(pullTrace);
575     ret += nsizeof(mtrc->trace[ti]->nvert);
576     ret += nsizeof(mtrc->trace[ti]->nstrn);
577     ret += nsizeof(mtrc->trace[ti]->nvelo);
578   }
579   ret += sizeof(pullTrace*)*(mtrc->traceArr->size);
580   return ret;
581 }
582 
583 pullTraceMulti *
pullTraceMultiNix(pullTraceMulti * mtrc)584 pullTraceMultiNix(pullTraceMulti *mtrc) {
585 
586   if (mtrc) {
587     airArrayNuke(mtrc->traceArr);
588     free(mtrc);
589   }
590   return NULL;
591 }
592 
593 
594 #define PULL_MTRC_MAGIC "PULLMTRC0001"
595 #define DEMARK_STR "======"
596 
597 static int
tracewrite(FILE * file,const pullTrace * trc,unsigned int ti)598 tracewrite(FILE *file, const pullTrace *trc, unsigned int ti) {
599   static const char me[]="tracewrite";
600 
601   fprintf(file, "%s %u\n", DEMARK_STR, ti);
602   ell_4v_print_d(file, trc->seedPos);
603 #define WRITE(FF) \
604   if (trc->FF && trc->FF->data) { \
605     if (nrrdWrite(file, trc->FF, NULL)) { \
606       biffMovef(PULL, NRRD, "%s: trouble with " #FF , me); \
607       return 1; \
608     } \
609   } else { \
610     fprintf(file, "NULL"); \
611   } \
612   fprintf(file, "\n")
613   fprintf(file, "nrrds: vert strn velo = %d %d %d\n",
614           trc->nvert && trc->nvert->data,
615           trc->nstrn && trc->nstrn->data,
616           trc->nvelo && trc->nvelo->data);
617   WRITE(nvert);
618   WRITE(nstrn);
619   WRITE(nvelo);
620   fprintf(file, "%u\n", trc->seedIdx);
621   fprintf(file, "%s %s %s\n",
622           airEnumStr(pullTraceStop, trc->whyStop[0]),
623           airEnumStr(pullTraceStop, trc->whyStop[1]),
624           airEnumStr(pullTraceStop, trc->whyNowhere));
625 #undef WRITE
626   return 0;
627 }
628 
629 int
pullTraceMultiWrite(FILE * file,const pullTraceMulti * mtrc)630 pullTraceMultiWrite(FILE *file, const pullTraceMulti *mtrc) {
631   static const char me[]="pullTraceMultiWrite";
632   unsigned int ti;
633 
634   if (!(file && mtrc)) {
635     biffAddf(PULL, "%s: got NULL pointer", me);
636     return 1;
637   }
638   fprintf(file, "%s\n", PULL_MTRC_MAGIC);
639   fprintf(file, "%u traces\n", mtrc->traceNum);
640 
641   for (ti=0; ti<mtrc->traceNum; ti++) {
642     if (tracewrite(file, mtrc->trace[ti], ti)) {
643       biffAddf(PULL, "%s: trace %u/%u", me, ti, mtrc->traceNum);
644       return 1;
645     }
646   }
647   return 0;
648 }
649 
650 static int
traceread(pullTrace * trc,FILE * file,unsigned int _ti)651 traceread(pullTrace *trc, FILE *file, unsigned int _ti) {
652   static const char me[]="traceread";
653   char line[AIR_STRLEN_MED], name[AIR_STRLEN_MED];
654   unsigned int ti, lineLen;
655   int stops[3], hackhack, vertHN, strnHN, veloHN; /* HN == have nrrd */
656 
657   sprintf(name, "separator");
658   lineLen = airOneLine(file, line, AIR_STRLEN_MED);
659   if (!lineLen) {
660     biffAddf(PULL, "%s: didn't get %s line", me, name);
661     return 1;
662   }
663   if (1 != sscanf(line, DEMARK_STR " %u", &ti)) {
664     biffAddf(PULL, "%s: \"%s\" doesn't look like %s line", me, line, name);
665     return 1;
666   }
667   if (ti != _ti) {
668     biffAddf(PULL, "%s: read trace index %u but wanted %u", me, ti, _ti);
669     return 1;
670   }
671   sprintf(name, "seed pos");
672   lineLen = airOneLine(file, line, AIR_STRLEN_MED);
673   if (!lineLen) {
674     biffAddf(PULL, "%s: didn't get %s line", me, name);
675     return 1;
676   }
677   if (4 != sscanf(line, "%lg %lg %lg %lg", trc->seedPos + 0,
678                   trc->seedPos + 1, trc->seedPos + 2, trc->seedPos + 3)) {
679     biffAddf(PULL, "%s: couldn't parse %s line \"%s\" as 4 doubles",
680              me, name, line);
681     return 1;
682   }
683   sprintf(name, "have nrrds");
684   lineLen = airOneLine(file, line, AIR_STRLEN_MED);
685   if (!lineLen) {
686     biffAddf(PULL, "%s: didn't get %s line", me, name);
687     return 1;
688   }
689   if (3 != sscanf(line, "nrrds: vert strn velo = %d %d %d",
690                   &vertHN, &strnHN, &veloHN)) {
691     biffAddf(PULL, "%s: couldn't parse %s line", me, name);
692     return 1;
693   }
694 #define READ(FF) \
695   if (FF##HN) {                         \
696     if (nrrdRead(trc->n##FF, file, NULL)) {        \
697       biffMovef(PULL, NRRD, "%s: trouble with " #FF , me); \
698       return 1; \
699     } \
700     fgetc(file); \
701   } else {                                      \
702     airOneLine(file, line, AIR_STRLEN_MED); \
703   }
704   hackhack = nrrdStateVerboseIO;  /* should be fixed in Teem v2 */
705   nrrdStateVerboseIO = 0;
706   READ(vert);
707   READ(strn);
708   READ(velo);
709   nrrdStateVerboseIO = hackhack;
710 
711   sprintf(name, "seed idx");
712   lineLen = airOneLine(file, line, AIR_STRLEN_MED);
713   if (!lineLen) {
714     biffAddf(PULL, "%s: didn't get %s line", me, name);
715     return 1;
716   }
717   if (1 != sscanf(line, "%u", &(trc->seedIdx))) {
718     biffAddf(PULL, "%s: didn't parse uint from %s line \"%s\"",
719              me, name, line);
720     return 1;
721   }
722   sprintf(name, "stops");
723   lineLen = airOneLine(file, line, AIR_STRLEN_MED);
724   if (!lineLen) {
725     biffAddf(PULL, "%s: didn't get %s line", me, name);
726     return 1;
727   }
728   if (3 != airParseStrE(stops, line, " ", 3, pullTraceStop)) {
729     biffAddf(PULL, "%s: didn't see 3 %s on %s line \"%s\"", me,
730              pullTraceStop->name, name, line);
731     return 1;
732   }
733 
734   return 0;
735 }
736 int
pullTraceMultiRead(pullTraceMulti * mtrc,FILE * file)737 pullTraceMultiRead(pullTraceMulti *mtrc, FILE *file) {
738   static const char me[]="pullTraceMultiRead";
739   char line[AIR_STRLEN_MED], name[AIR_STRLEN_MED];
740   unsigned int lineLen, ti, tnum;
741   pullTrace *trc;
742 
743   if (!(mtrc && file)) {
744     biffAddf(PULL, "%s: got NULL pointer", me);
745     return 1;
746   }
747   airArrayLenSet(mtrc->traceArr, 0);
748   sprintf(name, "magic");
749   lineLen = airOneLine(file, line, AIR_STRLEN_MED);
750   if (!lineLen) {
751     biffAddf(PULL, "%s: didn't get %s line", me, name);
752     return 1;
753   }
754   if (strcmp(line, PULL_MTRC_MAGIC)) {
755     biffAddf(PULL, "%s: %s line \"%s\" not expected \"%s\"",
756              me, name, line, PULL_MTRC_MAGIC);
757     return 1;
758   }
759 
760   sprintf(name, "# of traces");
761   lineLen = airOneLine(file, line, AIR_STRLEN_MED);
762   if (!lineLen) {
763     biffAddf(PULL, "%s: didn't get %s line", me, name);
764     return 1;
765   }
766   if (1 != sscanf(line, "%u traces", &tnum)) {
767     biffAddf(PULL, "%s: \"%s\" doesn't look like %s line", me, line, name);
768     return 1;
769   }
770   for (ti=0; ti<tnum; ti++) {
771     int added;
772     trc = pullTraceNew();
773     if (traceread(trc, file, ti)) {
774       biffAddf(PULL, "%s: on trace %u/%u", me, ti, tnum);
775       return 1;
776     }
777     if (pullTraceMultiAdd(mtrc, trc, &added)) {
778       biffAddf(PULL, "%s: adding trace %u/%u", me, ti, tnum);
779       return 1;
780     }
781     if (!added) {
782       trc = pullTraceNix(trc);
783     }
784   }
785 
786   return 0;
787 }
788