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 "teem/gage.h"
25 
26 #define PROBE "probePolynomial"
27 
28 /*
29 ** Tests:
30 **
31 */
32 
33 /* the hope is that without too much work it would be possible to
34    increase this to 4th-order polynomials and 4th-order derivatives */
35 #define POWER_MAX 3
36 
37 /*
38 ** polyeval takes a polynomial defined by coef --
39 ** coef[i][j][k] is the coefficient for (x^i)*(y^j)*(z^k) --
40 ** and evaluates at position pos[] the dx-th derivative along X,
41 ** the dy-th derivative along Y, and the dz-th derivative along Z,
42 ** dx = dy = dz = 0 to evaluate the polynomial itself
43 */
44 static double
polyeval(double coef[POWER_MAX+1][POWER_MAX+1][POWER_MAX+1],unsigned int dx,unsigned int dy,unsigned int dz,const double pos[3])45 polyeval(double coef[POWER_MAX+1][POWER_MAX+1][POWER_MAX+1],
46          unsigned int dx, unsigned int dy, unsigned int dz,
47          const double pos[3]) {
48   unsigned int xi, yi, zi;
49   double tmp, ret,
50     xp[POWER_MAX+1], yp[POWER_MAX+1], zp[POWER_MAX+1],
51     dc[POWER_MAX+1][POWER_MAX+1] = {
52     {1.0, 1.0, 1.0, 1.0},  /* how coeffs are scaled for 0th derivative */
53     {0.0, 1.0, 2.0, 3.0},  /* how coeffs are scaled for 1st derivative */
54     {0.0, 0.0, 2.0, 6.0},  /* how coeffs are scaled for 2nd derivative */
55     {0.0, 0.0, 0.0, 6.0}}; /* how coeffs are scaled for 3rd derivative */
56 
57   tmp = 1.0;
58   for (xi=0; xi<=POWER_MAX; xi++) {
59     xp[xi] = tmp;
60     tmp *= pos[0];
61   }
62   tmp = 1.0;
63   for (yi=0; yi<=POWER_MAX; yi++) {
64     yp[yi] = tmp;
65     tmp *= pos[1];
66   }
67   tmp = 1.0;
68   for (zi=0; zi<=POWER_MAX; zi++) {
69     zp[zi] = tmp;
70     tmp *= pos[2];
71   }
72 
73   ret = 0.0;
74   for (xi=dx; xi<=POWER_MAX; xi++) {
75     for (yi=dy; yi<=POWER_MAX; yi++) {
76       for (zi=dz; zi<=POWER_MAX; zi++) {
77         ret += (coef[xi][yi][zi]*xp[xi-dx]*yp[yi-dy]*zp[zi-dz]
78                 *dc[dx][xi]*dc[dy][yi]*dc[dz][zi]);
79       }
80     }
81   }
82 
83   return ret;
84 }
85 
86 static void
randvec(double vec[NRRD_SPACE_DIM_MAX],double lenexp,double lensig,airRandMTState * rng)87 randvec(double vec[NRRD_SPACE_DIM_MAX], double lenexp, double lensig,
88         airRandMTState *rng) {
89   double tmp, clen[2], len;
90 
91   airNormalRand_r(vec + 0, vec + 1, rng);
92   airNormalRand_r(vec + 2, NULL, rng);
93   ELL_3V_NORM(vec, vec, tmp);
94   airNormalRand_r(clen + 0, clen + 1, rng);
95   /* rician noise, actually */
96   clen[0] = lenexp + lensig*clen[0];
97   clen[1] = lensig*clen[1];
98   len = ELL_2V_LEN(clen);
99   ELL_3V_SCALE(vec, len, vec);
100 }
101 
102 /*
103 ** makeVolume allocates inside nin a new randomly oriented volume of
104 ** size sx-by-sy-sz; the spaceDirection vectors are random but enforced
105 ** to not be too parallel
106 */
107 static gageContext *
makeVolume(Nrrd * nin,unsigned int sx,unsigned int sy,unsigned int sz,double coef[POWER_MAX+1][POWER_MAX+1][POWER_MAX+1],airRandMTState * rng)108 makeVolume(Nrrd *nin, unsigned int sx, unsigned int sy, unsigned int sz,
109            double coef[POWER_MAX+1][POWER_MAX+1][POWER_MAX+1],
110            airRandMTState *rng) {
111   static const char me[]="makeVolume";
112   double spcOrig[NRRD_SPACE_DIM_MAX], spcVec[3][NRRD_SPACE_DIM_MAX],
113     angle10, angle20, angle21, ooff[3], aperm=0.6, lexp=0.1, lstdv=0.04,
114     kparm[NRRD_KERNEL_PARMS_NUM];
115   gageContext *gctx;
116   gagePerVolume *gpvl;
117   unsigned int xi, yi, zi;
118   airArray *submop;
119   int EE;
120 
121   submop = airMopNew();
122   randvec(spcVec[0], lexp, lstdv, rng);
123   do {
124     randvec(spcVec[1], lexp, lstdv, rng);
125     angle10 = ell_3v_angle_d(spcVec[1], spcVec[0]);
126   } while (!( AIR_IN_OP(AIR_PI*(1-aperm)/2, angle10, AIR_PI*(1+aperm)/2) ));
127   do {
128     randvec(spcVec[2], lexp, lstdv, rng);
129     angle20 = ell_3v_angle_d(spcVec[2], spcVec[0]);
130     angle21 = ell_3v_angle_d(spcVec[2], spcVec[1]);
131   } while (!( AIR_IN_OP(AIR_PI*(1-aperm)/2, angle20, AIR_PI*(1+aperm)/2) &&
132               AIR_IN_OP(AIR_PI*(1-aperm)/2, angle21, AIR_PI*(1+aperm)/2) ));
133 
134   /* center volume near origin */
135   airNormalRand_r(ooff + 0, ooff + 1, rng);
136   airNormalRand_r(ooff + 2, NULL, rng);
137   ELL_3V_SET(spcOrig, 0.0, 0.0, 0.0);
138   ELL_3V_SCALE_INCR(spcOrig, -AIR_CAST(double, sx)/2.0 + 2*ooff[0], spcVec[0]);
139   ELL_3V_SCALE_INCR(spcOrig, -AIR_CAST(double, sy)/2.0 + 2*ooff[1], spcVec[1]);
140   ELL_3V_SCALE_INCR(spcOrig, -AIR_CAST(double, sz)/2.0 + 2*ooff[1], spcVec[2]);
141 
142   /* allocate data */
143   if (nrrdMaybeAlloc_va(nin, nrrdTypeDouble, 3,
144                         AIR_CAST(size_t, sx),
145                         AIR_CAST(size_t, sy),
146                         AIR_CAST(size_t, sz))
147       || nrrdSpaceSet(nin, nrrdSpaceRightAnteriorSuperior)
148       || nrrdSpaceOriginSet(nin, spcOrig)) {
149     biffMovef(PROBE, NRRD, "%s: trouble setting volume", me);
150     airMopError(submop); return NULL;
151   }
152   nrrdAxisInfoSet_va(nin, nrrdAxisInfoSpaceDirection,
153                      spcVec[0],
154                      spcVec[1],
155                      spcVec[2]);
156   nrrdAxisInfoSet_va(nin, nrrdAxisInfoCenter,
157                      nrrdCenterCell,
158                      nrrdCenterCell,
159                      nrrdCenterCell);
160 
161   /* set data */
162   {
163     double *ddata = AIR_CAST(double *, nin->data);
164     for (zi=0; zi<sz; zi++) {
165       double pos[3];
166       for (yi=0; yi<sy; yi++) {
167         for (xi=0; xi<sx; xi++) {
168           ELL_3V_SCALE_ADD2(pos, 1.0, spcOrig, xi, spcVec[0]);
169           ELL_3V_SCALE_ADD2(pos, 1.0, pos, yi, spcVec[1]);
170           ELL_3V_SCALE_ADD2(pos, 1.0, pos, zi, spcVec[2]);
171           ddata[xi + sx*(yi + sy*zi)] = polyeval(coef, 0, 0, 0, pos);
172         }
173       }
174     }
175   }
176 
177   /* create context, using the c4hexic kernel and its derivatives.  c4hexic
178      is can reconstruct cubics without pre-filtering, and its analytical
179      derivatives are readily available. tmf:n,3,4 can also reconstruct
180      cubics but its not clear which other kernels are its derivatives */
181   gctx = gageContextNew();
182   airMopAdd(submop, gctx, (airMopper)gageContextNix, airMopOnError);
183   gageParmSet(gctx, gageParmRenormalize, AIR_FALSE);
184   gageParmSet(gctx, gageParmCheckIntegrals, AIR_TRUE);
185   EE = 0;
186   if (!EE) EE |= gageKernelSet(gctx, gageKernel00, nrrdKernelC4Hexic, kparm);
187   if (!EE) EE |= gageKernelSet(gctx, gageKernel11, nrrdKernelC4HexicD, kparm);
188   if (!EE) EE |= gageKernelSet(gctx, gageKernel22, nrrdKernelC4HexicDD, kparm);
189   if (!EE) EE |= !(gpvl = gagePerVolumeNew(gctx, nin, gageKindScl));
190   if (!EE) EE |= gagePerVolumeAttach(gctx, gpvl);
191   if (!EE) EE |= gageQueryItemOn(gctx, gpvl, gageSclValue);
192   if (!EE) EE |= gageQueryItemOn(gctx, gpvl, gageSclGradVec);
193   if (!EE) EE |= gageQueryItemOn(gctx, gpvl, gageSclHessian);
194   if (!EE) EE |= gageUpdate(gctx);
195   if (EE) {
196     biffMovef(PROBE, GAGE, "%s: trouble setting up gage", me);
197     airMopError(submop); return NULL;
198   }
199 
200   airMopOkay(submop);
201   return gctx;
202 }
203 
204 int
main(int argc,const char ** argv)205 main(int argc, const char **argv) {
206   airArray *mop;
207   char *err;
208 
209   airRandMTState *rng;
210   Nrrd *nin;
211   unsigned int xi, yi, zi, runNum, runIdx;
212   double coef[POWER_MAX+1][POWER_MAX+1][POWER_MAX+1], pos[3], epsilon;
213   const double *vmeas, *gmeas, *hmeas;
214   gageContext *gctx;
215 
216   AIR_UNUSED(argc);
217   mop = airMopNew();
218 
219   rng = airRandMTStateNew(429);
220   airMopAdd(mop, rng, (airMopper)airRandMTStateNix, airMopAlways);
221 
222   nin = nrrdNew();
223   airMopAdd(mop, nin, (airMopper)nrrdNuke, airMopAlways);
224   epsilon = 1e-8;
225   runNum = 30;
226 
227   for (runIdx=0; runIdx<runNum; runIdx++) {
228     unsigned int sx, sy, sz, probeNum;
229     double avgDiff;
230     airArray *submop;
231 
232     submop = airMopNew();
233     /* set random coefficients in polynomial */
234     for (xi=0; xi<=POWER_MAX; xi++) {
235       for (yi=0; yi<=POWER_MAX; yi++) {
236         for (zi=0; zi<=POWER_MAX; zi++) {
237           if (xi + yi + zi > POWER_MAX) {
238             coef[xi][yi][zi] = 0.0;
239           } else {
240             airNormalRand_r(&(coef[xi][yi][zi]), NULL, rng);
241           }
242         }
243       }
244     }
245 
246     sx = 20 + airRandInt_r(rng, 120);
247     sy = 20 + airRandInt_r(rng, 120);
248     sz = 20 + airRandInt_r(rng, 120);
249     fprintf(stderr, "%u: %u %u %u: ", runIdx, sx, sy, sz); fflush(stderr);
250     if (!(gctx = makeVolume(nin, sx, sy, sz, coef, rng))) {
251       airMopAdd(mop, err = biffGetDone(PROBE), airFree, airMopAlways);
252       fprintf(stderr, "trouble creating volume:\n%s", err);
253       airMopError(submop); airMopError(mop); return 1;
254     }
255     airMopAdd(submop, gctx, (airMopper)gageContextNix, airMopAlways);
256     vmeas = gageAnswerPointer(gctx, gctx->pvl[0], gageSclValue);
257     gmeas = gageAnswerPointer(gctx, gctx->pvl[0], gageSclGradVec);
258     hmeas = gageAnswerPointer(gctx, gctx->pvl[0], gageSclHessian);
259     ELL_3V_SET(pos, 0, 0, 0);
260     gageProbeSpace(gctx, pos[0], pos[1], pos[2],
261                    AIR_FALSE /* indexSpace */,
262                    AIR_TRUE /* clamp */);
263     probeNum = 0;
264     avgDiff = 0.0;
265     /* take a random walk starting at (0,0,0), making sure that at the
266        visited positions the gage-measured values, gradients, and
267        hessians are the same as the analytical ones */
268     do {
269       double vanal, ganal[3], hanal[9], vdiff, gdiff[3], hdiff[9],
270         step[3], stepSize = 0.2;
271       probeNum++;
272       vanal = polyeval(coef, 0, 0, 0, pos);
273       ganal[0] = polyeval(coef, 1, 0, 0, pos);
274       ganal[1] = polyeval(coef, 0, 1, 0, pos);
275       ganal[2] = polyeval(coef, 0, 0, 1, pos);
276       hanal[0] = polyeval(coef, 2, 0, 0, pos);
277       hanal[1] = polyeval(coef, 1, 1, 0, pos);
278       hanal[2] = polyeval(coef, 1, 0, 1, pos);
279       hanal[3] = hanal[1];
280       hanal[4] = polyeval(coef, 0, 2, 0, pos);
281       hanal[5] = polyeval(coef, 0, 1, 1, pos);
282       hanal[6] = hanal[2];
283       hanal[7] = hanal[5];
284       hanal[8] = polyeval(coef, 0, 0, 2, pos);
285       vdiff = fabs(vmeas[0] - vanal);
286       ELL_3V_SUB(gdiff, gmeas, ganal);
287       ELL_3M_SUB(hdiff, hmeas, hanal);
288       if (vdiff > epsilon ||
289           ELL_3V_LEN(gdiff) > epsilon ||
290           ELL_3M_FROB(hdiff) > epsilon) {
291         fprintf(stderr, "at (%g,%g,%g) one diff %.17g %.17g %.17g "
292                 "> eps %.17g\n",
293                 pos[0], pos[1], pos[2], vdiff,
294                 ELL_3V_LEN(gdiff), ELL_3M_FROB(hdiff), epsilon);
295         airMopError(submop); airMopError(mop); return 1;
296       }
297       avgDiff += vdiff + ELL_3V_LEN(gdiff) + ELL_3M_FROB(hdiff);
298       airNormalRand_r(step + 0, step + 1, rng);
299       airNormalRand_r(step + 2, NULL, rng);
300       ELL_3V_SCALE_INCR(pos, stepSize, step);
301       gageProbeSpace(gctx, pos[0], pos[1], pos[2],
302                      AIR_FALSE /* indexSpace */,
303                      AIR_TRUE /* clamp */);
304     } while (!gctx->edgeFrac);
305     avgDiff /= probeNum;
306     fprintf(stderr, "%u (%.17g)\n", probeNum, avgDiff);
307     airMopOkay(submop);
308   }
309 
310   airMopOkay(mop);
311   return 0;
312 }
313