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

U.S. Department of the Interior | U.S. Geological Survey
ISIS | Privacy & Disclaimers | Astrogeology Research Program
To contact us, please post comments and questions on the ISIS Support Center
File Modified: 07/12/2023 23:14:20