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