Isis 3 Programmer Reference
ApolloPanIO.cpp
1 #include "ApolloPanIO.h"
2 
3 #include <math.h>
4 #include <iostream>
5 
6 #include "Ransac.h"
7 #include "stdio.h"
8 
9 
10 #define FIDL 5344.186 //nominal line spread of fiducials in 5 micro pixels
11 #define FIDS 22980 //nominal sample spread of fiducials in 5 micron pixels
12 
13 using namespace std;
14 
15 namespace Isis {
16 
20  ApolloPanIO::ApolloPanIO() {
21  this->initialize();
22  }
23 
27  ApolloPanIO::~ApolloPanIO() {
28  //empty destructor....
29  }
30 
34  void ApolloPanIO::initialize() {
35  int i;
36 
37  //the number of affines to be calculated
38  n=0;
39 
40  //zero solved flags in discrete affines
41  for (i=0; i<44; i++)
42  affines[i].flag=0;
43 
44  //initial residuals statistic variable to nonsense values
45  maxR = -1.0;
46  meanR = -1.0;
47  stdevR= -1.0;
48 
49  //zero observed flags in fiducial observations
50  for (i=0; i<90; i++) {
51  obs[i].flag=0;
52 
53  //also calculate theoretical image observation of the fiducial marks
54  if (i%2 == 0)
55  obs[i].image[0] = FIDS/2;
56  else
57  obs[i].image[0] = -FIDS/2;
58 
59  obs[i].image[1] = (-21.5 + double(int(i/2)))*FIDL;
60 
61  //adjustment to account for the half spacing among the 22nd, 23rd, and 24th
62  // fiducials on each side
63  if (int(i/2)==22)
64  obs[i].image[1] -= FIDL/2;
65  else if ( int(i/2) > 22)
66  obs[i].image[1] -= FIDL;
67 
68  obs[i].image[1] = -obs[i].image[1]; //sign reversal to match camera layout
69  }
70 
71  }
72 
88  int ApolloPanIO::fiducialObservation(int fiducialNumber, double machineX, double machineY) {
89  /*
90  used to enter/overwrite observations of fiducial marks
91 
92  fiducial_number -> number indicating which fiducial mark, see diagram at the top of the file
93  machineX -> x machine coordinate
94  machineY -> y machine coordinate
95 
96  returns 1 if the input data is reasonable otherwise -1
97  */
98 
99 
100  //first check the range of the fiducial number
101  if (fiducialNumber < 0 || fiducialNumber > 89)
102  return -1;
103 
104  //next check the range of the machine coordinates
105  // (this is just a nonsense number check)
106  if (fabs(machineX) > 1e20)
107  return -1;
108 
109  if (fabs(machineY) > 1e20)
110  return -1;
111 
112  //if all looks OK record the data
113  obs[fiducialNumber].mach[0] = machineX;
114  obs[fiducialNumber].mach[1] = machineY;
115 
116  //and mark the point observed
117  obs[fiducialNumber].flag = 1;
118 
119  return 1;
120  }
121 
132  int ApolloPanIO::clearFiducialObservation(int fiducialNumber) {
133  //first check the range of the fiducial number
134  if (fiducialNumber < 0 || fiducialNumber > 89)
135  return -1;
136 
137  //and mark the point unobserved
138  obs[fiducialNumber].flag = 0;
139 
140  return 1;
141 
142  }
143 
152  int ApolloPanIO::computeInteriorOrienation()
153  {
154  int i,j,k,l,m; //indeces-counters for linear algebra computations
155 
156  double cdot[43][4][6],
157  angle, //angle of rotation to vertical for determining rotation coefficients
158  det, //determinate of 2x2 affine matrices used for matrix inversions
159  //wcdot[43][4], //all conditions are identically zero.. no need to store that..
160  ndot[44][21],
161  cndot[43][4][12],
162  cxstar[172], //cX* matrix, Uotila course notes page 122
163  cnct[14878], //CNCt matrix, Uotila course notes page 122
164  atw[44][6]; //right side of normal equation (see Uotila course notes)
165  // NOTE: I store w with reversed signs compared to Uotila's notes
166 
167  double adot[2][6],wdot[2];
168 
169  //adot is a sub matrix of A (see Uotila Course notes page 122).
170  // It is composed of partials WRT to unknowns.
171  //some affine partials are the same for every point so might as well hard code them now...
172  adot[0][2] = 0.0;
173  adot[0][3] = 0.0;
174 
175  adot[0][4] = 1.0;
176  adot[0][5] = 0.0;
177 
178  adot[1][0] = 0.0;
179  adot[1][1] = 0.0;
180 
181  adot[1][4] = 0.0;
182  adot[1][5] = 1.0;
183 
184  //first order of business...
185  // figure out how many affines there will be and which fiducial measures belong to each
186 
187  //the first affine always begins with point zero
188  affines[0].indeces[0] =0;
189  affines[0].flag = 1;
190 
191  for (i=2, n=0; i<90; i+=2) {
192  // i is counting the fiducials
193  // n is counting the affines
194 
195  //every time both fiducials in a horizontal (equal x coordinate) pair have been measured start
196  // a new affine
197  if (obs[i].flag && obs[i+1].flag) {
198  affines[n ].indeces[1] = i+1;
199  affines[n+1].indeces[0] = i ;
200  affines[n+1].flag=1;
201  n++;
202  }
203  }
204 
205  //check for sufficient data
206  if (n ==0) return -1;
207 
208  //now that we know how many affines will be needed we can get started
209 
210  //solve each affine individually
211 
212  //for each affine
213  for (i =0; i<n; i++) {
214  //initialize the appropriate ndot and atw matrices
215  for (j=0; j<21;j++)
216  ndot[i][j] =0.0;
217 
218  for (j=0; j<6; j++)
219  atw[i][j]=0.0;
220 
221  //for each point pertaining to each affine
222  for (j=affines[i].indeces[0]; j<=affines[i].indeces[1]; j++) {
223  //if the point has been observed use the observation to build the normal
224  if(obs[j].flag) {
225  //store the partials that change...
226  adot[0][0] = obs[j].mach[0];
227  adot[0][1] = obs[j].mach[1];
228 
229  adot[1][2] = obs[j].mach[0];
230  adot[1][3] = obs[j].mach[1];
231 
232  wdot[0] = obs[j].image[0];
233  wdot[1] = obs[j].image[1];
234 
235 
236  //build up the appropriate normal equations
237  for (k=0; k<6; k++)
238  for (l=0; l<=k; l++)
239  for (m=0; m<2; m++)
240  ndot[i][Isis::isymp(k,l)] += adot[m][k]*adot[m][l];
241 
242  for (k=0; k<6; k++)
243  atw[i][k] += adot[0][k]*wdot[0] + adot[1][k]*wdot[1];
244  }
245  }
246 
247  //copy the atw matrix into the affine class structure so that it isn't lost when the solution
248  // is done
249  for (j=0; j<6; j++)
250  affines[i].A2I[j] = atw[i][j];
251 
252  //solve this affine
253  //NOTE: it must also be inverted for later calculations, hence three instead of two for the
254  // last argument
255  if (!Isis::choleski_solve(ndot[i],affines[i].A2I,6,3))
256  j=0;
257  }
258 
259 
260  //now the contributions of the continuity conditions must be added....
261 
262  //cdot[i] are sub matrices of C matrix (Uotila page 122)
263  //Note: the cdot matrices are 4x12 (4 continuity conditions between 2 six paramter affines)
264  // however the left 4x6 portion of the cdot matrix and right 4x6 portion are equal but
265  // opposite so it's only necessary to calc/store one of them
266  for (i=0; i<n-1; i++) {
267  //build cdot matrix for continuity condition between affine i and i+1
268  cdot[i][0][0 ] = obs[affines[i].indeces[1]-1].mach[0];
269  cdot[i][0][1 ] = obs[affines[i].indeces[1]-1].mach[1];
270  cdot[i][0][2 ] = 0.0;
271  cdot[i][0][3 ] = 0.0;
272  cdot[i][0][4 ] = 1.0;
273  cdot[i][0][5 ] = 0.0;
274 
275  cdot[i][1][0 ] = 0.0;
276  cdot[i][1][1 ] = 0.0;
277  cdot[i][1][2 ] = obs[affines[i].indeces[1]-1].mach[0];
278  cdot[i][1][3 ] = obs[affines[i].indeces[1]-1].mach[1];
279  cdot[i][1][4 ] = 0.0;
280  cdot[i][1][5 ] = 1.0;
281 
282  cdot[i][2][0 ] = obs[affines[i].indeces[1] ].mach[0];
283  cdot[i][2][1 ] = obs[affines[i].indeces[1] ].mach[1];
284  cdot[i][2][2 ] = 0.0;
285  cdot[i][2][3 ] = 0.0;
286  cdot[i][2][4 ] = 1.0;
287  cdot[i][2][5 ] = 0.0;
288 
289  cdot[i][3][0 ] = 0.0;
290  cdot[i][3][1 ] = 0.0;
291  cdot[i][3][2 ] = obs[affines[i].indeces[1] ].mach[0];
292  cdot[i][3][3 ] = obs[affines[i].indeces[1] ].mach[1];
293  cdot[i][3][4 ] = 0.0;
294  cdot[i][3][5 ] = 1.0;
295  }
296 
297  //calculate cndot sub matrices of Uotila's C(AtM-A)- page122
298  for (i=0; i<n-1; i++) {
299  //initialize the matrix
300  for (j=0; j<4; j++)
301  for (k=0; k<12; k++)
302  cndot[i][j][k] = 0.0;
303 
304  for (j=0; j<4; j++)
305  for (k=0; k<6; k++)
306  for (l=0; l<6; l++) {
307  cndot[i][j][k ] += cdot[i][j][l] * ndot[i ][Isis::isymp(l,k)];
308  cndot[i][j][k+6] -= cdot[i][j][l] * ndot[i+1][Isis::isymp(l,k)];
309  }
310  }
311 
312  //calculate the CX* matrix -page 122
313  for (i=0; i<(n-1)*4; i++) {
314  j= int(i/4); //which c sub matrix
315  k= i%4; //row of the c sub matrix
316 
317  cxstar[i] = 0;
318 
319  for (l=0; l<6; l++) {
320  cxstar[i] -= cdot[j][k][l]*affines[j ].A2I[l];
321  cxstar[i] += cdot[j][k][l]*affines[j+1].A2I[l];
322  }
323  }
324 
325  //initialize CNCt matrix
326  for (i=0; i<14878; i++)
327  cnct[i] = 0.0;
328 
329  //calculate all of the diagonal portions
330  for (i=0; i<n-1; i++) {
331  for (j=0; j<4; j++)
332  for (k=0; k<=j; k++) //remember that this matrix is a memory optimized symmetric matrix
333  for (l=0; l<12; l++) {
334  if(l<6)
335  cnct[Isis::isymp(j+4*i,k+4*i)] += cndot[i][j][l] * cdot[i][k][l];
336  else
337  cnct[Isis::isymp(j+4*i,k+4*i)] -= cndot[i][j][l] * cdot[i][k][l-6];
338  }
339  }
340 
341  //calculate all of the diagonal portions
342  for (i=0; i<n-2; i++) {
343  for (j=0; j<4; j++)
344  for (k=0; k<4; k++)
345  for (l=0; l<6; l++)
346  cnct[Isis::isymp(j+i*4,k+4+i*4)] += cndot[i][j][l+6]*cdot[i+1][k][l];
347  }
348 
349 
350  Isis::choleski_solve(cnct,cxstar,4*(n-1),2);
351 
352  //cxstar are now Kc Lagrange multipliers
353  //now (CN)tKc = NCtKc are the secondary corrections to the unknowns and they can be added
354  // directly to the previously calculated values
355 
356  //corrections to the first affine
357  for (i=0; i<6; i++) {
358  for (j=0; j<4;j ++)
359  affines[0].A2I[i] += cxstar[j]*cndot[0][j][i];
360  }
361 
362  //for all the middle affines
363  for (i=1; i<n-1; i++) {
364  for (j=0; j<6; j++) {
365  for (k=0; k<4; k++)
366  affines[i].A2I[j] += cxstar[(i-1)*4+k]*cndot[i-1][k][j+6];
367  for (k=0; k<4; k++)
368  affines[i].A2I[j] += cxstar[(i )*4+k]*cndot[i ][k][j ];
369  }
370  }
371 
372  //corrections for the last affine
373  for (i=0; i<6; i++) {
374  for (j=0; j<4; j++)
375  affines[n-1].A2I[i] += cxstar[4*(n-2)+j]*cndot[n-2][j][i+6];
376  }
377 
378 
379  //now compute the reverse affines
380  for (i=0; i<n; i++) {
381  det = affines[i].A2I[0]*affines[i].A2I[3] - affines[i].A2I[1]*affines[i].A2I[2];
382 
383  affines[i].A2M[0] = affines[i].A2I[3]/det;
384  affines[i].A2M[3] = affines[i].A2I[0]/det;
385 
386  affines[i].A2M[1] = -affines[i].A2I[1]/det;
387  affines[i].A2M[2] = -affines[i].A2I[2]/det;
388 
389  affines[i].A2M[4] = - affines[i].A2M[0]*affines[i].A2I[4]
390  - affines[i].A2M[1]*affines[i].A2I[5];
391  affines[i].A2M[5] = - affines[i].A2M[2]*affines[i].A2I[4]
392  - affines[i].A2M[3]*affines[i].A2I[5];
393  }
394 
395  //now define the rotation coefficients that will transform any image/machine y coordinate into
396  // a reference frame where the two right most fiducials are have the exact same y value and
397  // it is thus easy to determine where a point lies on the left side of the line
398 
399  double pt1[2], pt2[2],v[2];
400 
401  for (i=0; i<n; i++) {
402  //rotations in machine space,
403 
404  //the two points obs[affines[i].indeces[1]-1] and obs[affines[i].indeces[1]] define a line
405 
406  pt1[0] = obs[affines[i].indeces[1]-1].mach[0];
407  pt1[1] = obs[affines[i].indeces[1]-1].mach[1];
408 
409  pt2[0] = obs[affines[i].indeces[1] ].mach[0];
410  pt2[1] = obs[affines[i].indeces[1] ].mach[1];
411 
412 
413  //find the point on the line closest to the origin
414 
415  //vector in the direction of a line
416  v[0] = pt1[0] - pt2[0];
417  v[1] = pt1[1] - pt2[1];
418 
419  //using det as the scale factor along the line from pt1 to the point nearest the origin
420  det = -(pt1[0]*v[0] + pt1[1]*v[1])/(v[0]*v[0]+v[1]*v[1]);
421 
422  //pt1 + det*v is the point closest to the origin and the angle needed to rotate that
423  // point so that it lies on the y axis is...
424  angle = atan2((pt1[0]+det*v[0]),(pt1[1]+det*v[1]));
425 
426  //and since we're only ever going to be concerned with rotating the y coordinates and z's
427  // are alwyas zero there are only two values we need to save to elements of the rotation
428  // matrix
429  affines[i].rotM[0] = sin(angle);
430  affines[i].rotM[1] = cos(angle);
431 
432  //any machine coordinate [u,v] can be rotated into a system where the greatest y values of
433  // the fiducial marks are the same, and therefore the greatest y value in a particular
434  // affine is:
435  affines[i].mM = pt1[0]*affines[i].rotM[0] + pt1[1]*affines[i].rotM[1];
436 
437  //or equivalently...
438  //affines[i].mM = pt2[0]*affines[i].rotM[0] + pt2[1]*affines[i].rotM[1];
439 
440 
441 
442  //rotations in image space
443 
444  //for this calculation the transformed Machine (measured coordinates) are used rather than
445  // the theoretical/expected coordinates of the fiducial marks. They should be close to
446  // identical, but it is the affines and therefore the transformed points that define the
447  // actual work space.
448  pt1[0] = affines[i].A2I[0] * obs[affines[i].indeces[1]-1].mach[0] +
449  affines[i].A2I[1] * obs[affines[i].indeces[1]-1].mach[1] +
450  affines[i].A2I[4];
451 
452  pt1[1] = affines[i].A2I[2] * obs[affines[i].indeces[1]-1].mach[0] +
453  affines[i].A2I[3] * obs[affines[i].indeces[1]-1].mach[1] +
454  affines[i].A2I[5];
455 
456  pt2[0] = affines[i].A2I[0] * obs[affines[i].indeces[1] ].mach[0] +
457  affines[i].A2I[1] * obs[affines[i].indeces[1] ].mach[1] +
458  affines[i].A2I[4];
459 
460  pt2[1] = affines[i].A2I[2] * obs[affines[i].indeces[1] ].mach[0] +
461  affines[i].A2I[3] * obs[affines[i].indeces[1] ].mach[1] +
462  affines[i].A2I[5];
463 
464 
465  //find the point on the line closest to the origin
466 
467  //vector in the direction of a line
468  v[0] = pt1[0] - pt2[0];
469  v[1] = pt1[1] - pt2[1];
470 
471  //using det as the scale factor along the line from pt1 to the point nearest the origin
472  det = -(pt1[0]*v[0] + pt1[1]*v[1])/(v[0]*v[0]+v[1]*v[1]);
473 
474  //pt1 + det*v is the point closest to the origin and the angle needed to rotate that point
475  // so that it lies on the y axis is...
476  angle = atan((pt1[0]+det*v[0])/(pt1[1]+det*v[1]));
477 
478  //and since we're only ever going to be concerned with rotating the y coordinates and z's
479  // are alwyas zero there are only two values we need to save to elements of the rotation
480  // matrix
481  affines[i].rotI[0] = sin(angle);
482  affines[i].rotI[1] = cos(angle);
483 
484  //any machine coordinate [u,v] can be rotated into a system where greatest y values of the
485  // affine border are the same that is the border is a line parrallel to the image x axis,
486  // and therefore the greatest y value in a particular affine is
487  affines[i].mI = pt1[0]*affines[i].rotI[0] + pt1[1]*affines[i].rotI[1];
488 
489  //or equivalently...
491  }
492 
493 
494 
495 
496 
497 
498  //all the parameters necessary for the machine2image and image2machin coordinate
499  // transformations are now computed and stored
500 
501 
502 
503 
504 
505 
506  //calculate and store residuals
507  for (i=0; i<90; i++) {
508  if (obs[i].flag) { //if the point has been observed...
509  //convert the theoretical image coordinate to machine space and compare it to the
510  // measured coordinate--this way residuals are in pixels
511  image2Machine(obs[i].image[0],obs[i].image[1],&pt1[0],&pt1[1]);
512 
513  obs[i].residuals[0] = pt1[0] - obs[i].mach[0];
514  obs[i].residuals[1] = pt1[1] - obs[i].mach[1];
515  }
516  }
517 
518  calc_residual_stats(); //DEBUG
519 
520  return 1;
521  }
522 
538  int ApolloPanIO::machine2Image(double *machineX, double *machineY) {
539 
540  double temp,npt[2];
541  int i;
542 
543  //first do a domain check to be sure this point is in the last affine
544  temp = *machineX * affines[n-2].rotM[0] + *machineY * affines[n-2].rotM[1];
545 
546  if (temp > affines[n-2].mM) {
547  i=n-1;
548  }
549  else {//now step through the affines to determine in the domain of which one this point lies
550  for (i=0; i<n-1; i++) {
551  temp = *machineX * affines[i].rotM[0] + *machineY * affines[i].rotM[1];
552 
553  //if the point is in the domain of this affine do the conversion...
554  if (temp <= affines[i].mM)
555  break;
556  }
557  }
558  //cout << "DEBUG: affine index: " << i << endl;
559  //cout << "affine " << affines[i].A2I[0] << " " << affines[i].A2I[1] << " "<< affines[i].A2I[2] << " "<< affines[i].A2I[3] << " "<< affines[i].A2I[4] << " "<< affines[i].A2I[5];
560  npt[0] = affines[i].A2I[0] * *machineX + affines[i].A2I[1] * *machineY + affines[i].A2I[4];
561  npt[1] = affines[i].A2I[2] * *machineX + affines[i].A2I[3] * *machineY + affines[i].A2I[5];
562 
563  *machineX = npt[0]; //overwrite the input
564  *machineY = npt[1]; //overwrite the input
565 
566  return 1; //point successfully converted
567  }
568 
591  int ApolloPanIO::machine2Image(double machineX,
592  double machineY,
593  double *imageX,
594  double *imageY) {
595 
596  int i;
597 
598  *imageX = machineX;
599  *imageY = machineY;
600 
601  i = machine2Image(imageX,imageY);
602 
603  return i;
604  }
605 
621  int ApolloPanIO::image2Machine(double *imageX, double *imageY) {
622 
623  double temp, npt[2];
624  int i;
625 
626  //first do a domain check if this point is in the last affine
627  temp = *imageX * affines[n-2].rotI[0] + *imageY * affines[n-2].rotI[1];
628 
629  if( temp < affines[n-2].mI)
630  i = n-1;
631  else {
632  //now step through the affines to determine in the domain of which one this point lies
633  for (i=0; i<n-1; i++) {
634  temp = *imageX * affines[i].rotI[0] + *imageY * affines[i].rotI[1];
635 
636  //if the point is in the domain of this affine do the conversion...
637  if( temp >= affines[i].mI)
638  break;
639  }
640  }
641  //cout << "DEBUG: affine index: " << i << endl;
642  //cout << "affine " << affines[i].A2M[0] << " " << affines[i].A2M[1] << " "<< affines[i].A2M[2] << " "<< affines[i].A2M[3] << " "<< affines[i].A2M[4] << " "<< affines[i].A2M[5];
643  npt[0] = affines[i].A2M[0] * *imageX + affines[i].A2M[1] * *imageY + affines[i].A2M[4];
644  npt[1] = affines[i].A2M[2] * *imageX + affines[i].A2M[3] * *imageY + affines[i].A2M[5];
645 
646  *imageX = npt[0]; //overwrite the input
647  *imageY = npt[1]; //overwrite the input
648 
649  return 1; //point successfully converted
650  }
651 
674  int ApolloPanIO::image2Machine(double imageX,
675  double imageY,
676  double *machineX,
677  double *machineY) {
678  /* This class method converts a machine coordinate to an image coordinate
679  (overwriting the input arguments)
680 
681  imageX -> machine x input
682  imageY -> machine y input
683  machineX <- to be overwritten as the converted coordinate
684  machineY <- to be overwritten as the converted coordinate
685  */
686 
687  *machineX = imageX;
688  *machineY = imageY;
689 
690  return image2Machine(machineX,machineY);
691  }
692 
699  void ApolloPanIO::calc_residual_stats() {
700  maxR = 0.0;
701 
702  meanR = 0.0;
703 
704  stdevR = 0.0;
705 
706  int i,n=0;
707 
708  double l;
709 
710  for (i=0; i<90; i++) {
711  if (obs[i].flag) {
712  l = sqrt(obs[i].residuals[0]*obs[i].residuals[0] +
713  obs[i].residuals[1]*obs[i].residuals[1]);
714 
715  if( l > maxR)
716  maxR = l;
717 
718  meanR += l;
719 
720  n++;
721  }
722  }
723 
724  meanR /=n;
725 
726  for (i=0; i<90; i++) {
727  if(obs[i].flag) {
728  l = sqrt(obs[i].residuals[0]*obs[i].residuals[0] +
729  obs[i].residuals[1]*obs[i].residuals[1]);
730 
731  stdevR += (l-meanR)*(l-meanR);
732  }
733  }
734 
735  stdevR = sqrt(stdevR/(n-1));
736 
737  return;
738  }
739 
746  double ApolloPanIO::stdevResiduals() {
747  //returns the standard deviation of the length of the 2D residual vectors
748  //if it hasn't been calucated a nonsense number (-1.0) will be returned
749  return stdevR;
750  }
751 
758  double ApolloPanIO::meanResiduals()
759  {
760  return meanR;
761  }
762 
769  double ApolloPanIO::maxResiduals()
770  {
771  return maxR;
772  }
773 } //end namespace Isis
Namespace for the standard library.
Namespace for ISIS/Bullet specific routines.
Definition: Apollo.h:31