/* flatfish.c last updated 6/20/1997 hpm - fixed rotation bug */ #include #include #include #include #include #include #include #include #include #define SPOTRADIUSMIN 4 /* minimum radius of spot to consider */ #define DISTORPOLYDEG 5 /* degree of radial distortion polynomial + 1 */ #define PhMax 1000 /* Maximum picture height */ #define PwMax 2000 /* Maximum picture width */ #define Pad 40 /* Picture padding border, pixels */ #define Sad 2 /* How far beyond boundaries to allow spot centers */ #define Paspect 1.020 /* Width/height ratio of pixels */ gray **picin; /* raw incoming picture */ pixel **picout; /* outgoing display picture */ gray **graphout; /* outgoing plot picture */ int Ph, /* Picture height, pixels (576 or 480 typical) */ Pw; /* Picture width, pixels (768 or 640 typical) */ unsigned char *Bch[PhMax+2*Pad]; /* row pointers to padded image */ unsigned char **picpad; /* padded image, for working beyond edges */ #define PI 3.14159265358979 /* Pies are round */ /* Radial distortion finder and rectifier for solid state cameras with wide angle lenses. Created 5/95 Hans Moravec Carnegie Mellon University Pittsburgh, PA 15213 In calibration phase, the program assumes the camera is perpendicularly aimed at a square, horizontally oriented, grid of round dark spots, with an additional spot at a half grid position near the center: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *_* * * * * * * * * * * * * * * * * * * * * * * *^* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * (the center "spot" made of an underbar paired with a hat is supposed to look like all the others, represented by stars, and all spots should be black circular disks: excuse the limitations of ascii graphics). The program was developed with a test pattern made of a magnetic white board 4 feet high and six feet wide, covered with a 16 x 24 + 1 grid of dark magnetic disks 1.5 inches in diameter at 3 inch spacing. The object of the calibration was a stereo bar of three Sony XC-75 black and white cameras with 90 degree wide angle lenses exhibiting strong fish eye distortion, with a radial image compression factor between the center and the edges of at least 2:1. The cameras were mounted parallel at 15 cm spacing, and connected to the R, G and B channels of a color digitizer. The camera bar was placed so the lens iris rings were 22.5 inches in front of the calibration pattern. This filled each camera's field of view with a majority of the pattern. The center spot was approximately centered in the "green" camera's image, to the left of center in the "blue" image and to the right in the "red" image. The program finds the spots, in pixel (distorted) and spot grid (ideal) coordinates, and finds a least-squares best fit distortion center, assumed to indicate lens axis, a digitizer aspect ratio, and a distortion correcting polynomial that maps distorted to ideal radius from that center. In our setup the result RMS deviation for all (approximately 300/image) spots is better than 0.5 pixel. In our setup, we note that the lens center between calibrations jitters only a few hundredths of a pixel. As of 11/95 the program could rectify images from the cameras using the distortion polynomial into ones with ideal projective geometry. The program is not complete, as of 3/96. Bugs and missing: When the distorion causes a corner spot to appear in the calibration image whose horizontal and vertical neighbors are off the edge of the image, the program often links it with a diagonal neighbor, giving it the wron ideal coordinates. Tests need to be added to handle this situation. (fixed 3/15/96 - hpm). The center spot is intended to relate the three cameras, in a not yet implemented later calibration step, which produces a full geometric camera calibration. (completed 4/24/96 - hpm) */ /* The Great Aspect Ratio Saga Concerning aspect ratio of Sony XC-75 camera module in conjunction with our K2T digitizer: the camera specs say the camera pixels are 8.4 um horizontal by 9.8 um vertical. Horizontal clock rate is 14.318 MHz. Vertical drive frequency is 15.734 kHz +- 1%. The camera has 768 horizontal by 494 vertical pixels. Sensing area is 1/2 inch wide. (Optical blank 43 elements on each horizontal line?) EIA system. Chip size is 7.95 (h) by 6.45 (v) mm. 525 lines. --- Turns out that the resolution of AQDOTFREQ in the k2t package is limited - I think it's 80ns and the 12.2 MHz is really 12.256 which is as close as we can get to perfectly square... Bill Ross 12.256 makes the horizontal pixel size 9.813, vs. 9.8 um vertical, an error of only 0.135 percent, about 0.86 pixels total in the horizontal direction, if you use the vertical as the reference. That's good, it means that there is no effect on things like template matches, where the template spans only a fraction of the image, and thus has a maximum aspect stretch much less than a pixel. In numeric computations involving pixel coordinates, I can now multiply in the aspect ratio of 1.001352107, and have complete peace of mind. A camera solver would never have been able to come up with an estimate that accurate. hpm 9/95 Hah!!!! Minimizing over aspect ratio with the distortion-finding code gives a repeatable aspect ratio of 1.020 +/- .0006. Using that found aspect ratio reduces the rms error of the best fit curve of spot index radius to pixel radius to about 0.5 pixel, compared to much worse 2.5 pixels using the calculated aspect ratio. Another good intention leads to hell! */ float **flP; /* raster spotness value */ unsigned char **chP; /* raster spotness radius */ int Nspots = 0; /* number of spots found */ double *Ispots, *Jspots; /* spot location list */ double *Vspots, *Rspots; /* spotness value, radius lists */ double *IIspots, *IJspots, *JIspots, *JJspots; /* spot local coordinate system */ int *Aspots; /* spot coordinate status: -1 untouched, 0 inited, 1 done */ float **Dspots; int **DPspots, **DPerror; int Cspots, Pspots; /* Pattern and image center spots */ double Poly[1000]; /* Snapshot of "fitpoly" polynomial */ float isc, jsc; int deg; /* Image axis center, and radial polynomial degree */ double ipc, jpc, res, aspect, rootaspect; double bestisc, bestjsc, bestaspect, bestfit; int dpass; double CalibPi, CalibPj, CalibAS; /* Calibrated pixel image center and aspect ratio */ double CalibSi, CalibSj; /* Calibrated spot image center */ int CalibPN = 0; /* Scatter polynomial size */ double CalibP[100]; /* Spot->Pixel radius scatter plot polynomial */ double CalibS[100]; /* Pixel->Spot radius scatter plot polynomial */ double CalibAN; /* Angle of the gid image from horizontal (vertical) */ #define x 255 #define y 128 unsigned char cmap[15][3] = {{0,0,0},{x,0,0},{0,x,0},{0,0,x},{0,x,x}, {x,0,x},{x,x,0},{x,x,x},{x,y,0},{x,0,y}, {y,x,0},{y,0,x},{0,x,y},{0,y,x},{y,y,y}}; #undef x #undef y /* spot pixels, from center out, maximally balanced */ #define SPX 5000 /* maximum length of spot list */ char spi[SPX], spj[SPX]; /* i and j coords of the pixels */ int sprpos[100]; /* starting index for each whole pixel ring */ int spn = 0; /* actual number of pixels in list */ int sprmax = 0; /* maximum spot radius in list */ int i, j, k, l, m, n; FILE *fp, *flog; char slog[500]; /* flog, slog: file and string for run log output */ void plog() /* write out message in log file and stdout */ { fprintf(flog,"%s",slog); printf("%s",slog); }; void llog(char *s) /* write fixed string in log file */ { fprintf(flog,s); printf(s); }; void **padmat(int row, int col, int padrow, int padcol, int typesize) /* make padded 2D array of typesize elements */ { int i, sizfac; void *ar, **arp; arp = (void **) calloc(row+2*padrow,sizeof(void *)); ar = (void *) calloc((row+2*padrow)*(col+2*padcol),typesize); sizfac = typesize/sizeof(void); for (i=0; isprmax) outr = sprmax; aq = avt = ant = 0; for (i = 0; idotbest) { dotbest = dotq; *rbest = k;}; } return(dotbest*(*rbest)); /* bias answer towards largest spots */ } void paintspots(int lor, int hir) /* calculate "spotness" across image */ { int i,j,l; flP = (float **) padmat(Ph,Pw,Sad,Sad,sizeof(float)); chP = (unsigned char **) padmat(Ph,Pw,Sad,Sad,sizeof(char)); for (i= -Sad; i0.0) { Ispots[Nspots] = i; Jspots[Nspots] = j; Vspots[Nspots] = flP[i][j]; Rspots[Nspots] = chP[i][j]; Nspots++; }; /* print histogram of found spot sizes */ { int sizes[100], szmax, szmin; szmin = 99; szmax = 0; for (i=0; i<100; i++) sizes[i]=0; for (i=0; iszmax) szmax=Rspots[i]; }; llog("Spot histogram (of maxima): "); for (i=szmin; i<=szmax; i++) { sprintf(slog," %d(%d)",i,sizes[i]); plog(); }; llog("\n"); } /* create arrays for spot neighborhoods */ Dspots = (float **) padmat(Nspots,Hood,0,0,sizeof(float)); DPspots = (int **) padmat(Nspots,Hood,0,0,sizeof(int)); DPerror = (int **) padmat(Nspots,5,0,0,sizeof(int)); /* and temporary arrays for spot distance sort */ Dsp = (float *) calloc(Nspots,sizeof(float)); DPsp = (int *) calloc(Nspots,sizeof(int)); llog("\n* Linking neighboring spots into grid *\n"); llog("Creating and propagating spot-local coordinate systems\n"); /* scan spot list to find neighbors of each spot */ if (Nspots < Hood) Hood = Nspots; for (i = 0; i 1.4 || ddj/ddi > 1.4) {sprintf(slog,"Point %d, axis ratio %.3g\n",i,ddi/ddj); plog();}; for (j=0; j<4; j++) /* initialize best axis spots */ { /* fits must be better than 3/4 way to prototype */ di = axbs[j][0] = axpr[j][0]*0.75; dj = axbs[j][1] = axpr[j][1]*0.75; axid[j] = -1; ddi = axpr[j][0] - di; ddj = axpr[j][1] - dj; axvl[j] = ddi*ddi + ddj*ddj; }; /* search for best fit for each axis direction among neighbors */ for (j=1; j=0) for (k=0; k<2; k++) axpr[j][k] = axbs[j][k]; for (j=0; j<4; j++) DPspots[i][j+1] = axid[j]; Aspots[i] = 1; /* this spot has finalized coordinates! */ IIspots[i] = (axpr[0][0]-axpr[1][0])/4.0 + IIspots[i]/2.0; IJspots[i] = (axpr[0][1]-axpr[1][1])/4.0 + IJspots[i]/2.0; JIspots[i] = (axbs[2][0]-axpr[3][0])/4.0 + JIspots[i]/2.0; JJspots[i] = (axbs[2][1]-axpr[3][1])/4.0 + JJspots[i]/2.0; for (j=0; j<4; j++) /* propagate to neighbors */ if ((k=axid[j])>=0) {if (Aspots[k]<0) /* uninited */ { Aspots[k] = 0; IIspots[k] = IIspots[i]; IJspots[k] = IJspots[i]; JIspots[k] = JIspots[i]; JJspots[k] = JJspots[i]; } else if (Aspots[k]==0) /* already has initial coordinates */ { /* average in this spot's coordinates */ IIspots[k] = (IIspots[i] + IIspots[k])/2; IJspots[k] = (IJspots[i] + IJspots[k])/2; JIspots[k] = (JIspots[i] + JIspots[k])/2; JJspots[k] = (JJspots[i] + JJspots[i])/2; }; }; for (j=0; j<4; j++) DPspots[i][j+1] = axid[j]; /* replace near neighbor with coordinate neighbor links */ }; sprintf (slog," %d",ndone); plog(); } while (ndone>0); llog("\n"); /* edit out link inconsistencies */ do { for (i=0; i= 0) if ((l=DPspots[k][jp=1+((j-1) ^ 1)]) >= 0) if (l != i) { DPerror[k][jp]++; DPerror[i][j]++; }; ii = jj = 0; /* find the most inconsistent link */ for (i=0; i DPerror[ii][jj]) { ii = i; jj = j; }; DPspots[ii][jj] = -1; /* wipe out max inconsistency */ if (jj>0) { sprintf(slog,"Link inconsistency %d[%d] erased\n",ii,jj); plog(); }; } while (jj>0); /* complete unidirectional links, flag overlooked link inconsistencies */ for (i=0; i=0) { if ((l=DPspots[k][1+((j-1) ^ 1)]) == -1) { DPspots[k][1+((j-1) ^ 1)] = i; sprintf(slog,"Missing link %d --> %d\n",k,i); plog(); } else if (l != i) { sprintf(slog, "GRID LINKING INCONSISTENCY %d -> %d <- %d !!!!\n",i,k,l); plog(); } } llog("Assigning ideal coordinates to spots\n"); /* label spots with ideal coordinates */ for (i=0; i=0) { di = IIspots[i]; dj = JJspots[i]; l = ((j-1)&1)?-1:1; if ((j-1)&2) dj += l; else di += l; if (Aspots[k]==2) { if (IIspots[k] != di || JJspots[k] != dj) { sprintf(slog, "GRID LABELING CLASH [%g,%g] : [%g,%g] !!!!\n", di,dj,IIspots[k],JJspots[k]); plog(); } } else { Aspots[k]=2; ndone++; IIspots[k] = di; JJspots[k] = dj; }; }; sprintf(slog," %d",ndone); plog(); } while (ndone>0); llog("\n"); /* calculate ideal coords for pattern center spot */ IIspots[Cspots]=JJspots[Cspots]=0; for (j=1; j<=4; j++) { IIspots[Cspots] += IIspots[DPspots[Cspots][j]]; JJspots[Cspots] += JJspots[DPspots[Cspots][j]]; }; IIspots[Cspots] /= 4.0; JJspots[Cspots] /= 4.0; sprintf(slog,"Pattern center spot, at Centroid spot + [%.3g,%.3g]\n", IIspots[Cspots],JJspots[Cspots]); plog(); /* shift spot coordinates so pattern center spot is at (0,0) */ di = IIspots[Cspots]; dj = JJspots[Cspots]; for (i=0; ip01?p00:p01) < 0) i = p10>p11?p10:p11; if (i<0) return; *ipos = Ispots[i]; *jpos = Jspots[i]; return; }; is -= is0; js -= js0; /* fractional position within bounding box */ /* Bilinear interpolation of spot pixel coordinates */ *ipos = (Ispots[p00]*(1.0-is) + Ispots[p10]*is)*(1.0-js) + (Ispots[p01]*(1.0-is) + Ispots[p11]*is)*js ; *jpos = (Jspots[p00]*(1.0-is) + Jspots[p10]*is)*(1.0-js) + (Jspots[p01]*(1.0-is) + Jspots[p11]*is)*js ; }; int SimulSolve(double a[], int n) /* solve n simultaneous linear equations */ /* a is an [n * (n+1)] array with rhs constants in rightmost column. Solved values are returned in a[0:n-1]. Rest of a turns into a mess. */ { int i, ii, j, jj, k, it; double t, tt; int perm[n]; /* initialize pivot permutation */ perm[0]=0; for (i=1; itt) {it = perm[ii]; perm[ii] = perm[i]; perm[i] = it; tt=t;}; } i = perm[ii]; if ((t=a[i+ii]) == 0.0) return(0); /* singular */ if (t != 1.0) { t = 1.0/t; /* eliminate variable perm[ii] */ for (k=ii+1; k<=n; k++) a[i+k] *= t; } for (jj=ii+1; jj0; ii--) /* backsolve */ { i = perm[ii]; if ((t=a[i+n]) != 0.0) for (jj=0; jj rp, and return its root mean square error */ { double S[2*deg-1], R[deg], A[deg*(deg+1)]; int i, j, k; double v, x, xp, sum; for (k=0; k<2*deg-1; k++) S[k]=0; for (k=0; k=0; j--) xp = xp*x + A[j]; xp -= rp[i]; sum += xp*xp; /* *w[i] if weighted sum */ }; return (sqrt(sum/n)); /* /Sum(w[i]), not n, if weighted sum */ }; void showspots(int lor, int hir) /* color spots found by paintspots */ { int i, j, k, l, szmax; double flmax; int sizes[100]; for (i=0; i<100; i++) sizes[i]=0; flmax = 0.0; szmax = 0; for (i=0; i0) { if (flP[i][j]>flmax) flmax=flP[i][j]; if (chP[i][j]>szmax) szmax=chP[i][j]; sizes[chP[i][j]]++; } if (hir>szmax) hir=szmax; llog("Spot operator spot size histogram: "); l=0; while (sizes[l]==0 && l<100) l++; for (i=l; i<=szmax; i++) { sprintf(slog," %d(%d)",i,sizes[i]); plog(); }; llog("\n"); for (i=0; i0) { k = chP[i][j]; if (khir) k = hir; l = sqrt(sqrt(flP[i][j]/flmax))*255.0; PPM_ASSIGN(picout[i][j],l,(l*(k-lor))/(hir-lor),l); }; } void stroke(int kl, int is, int js, int id, int jd) /* draw a line from is,js to id,jd */ { int i,j,k,l; l = sqrt((double) ((is-id)*(is-id)+(js-jd)*(js-jd))) + 1; for (k=0; k<=l; k++) { i = (k*id + (l-k)*is)/l; j = (k*jd + (l-k)*js)/l; if (j >= 0 && j < Pw) if (i >=0 && i= 0 && jp+i < Pw) { if (ip-i>=0 && ip-i=0 && ip+i=0 && i= 0 && j < Pw) { if (cmap[kl-1][0]) PPM_PUTR(picout[i][j],cmap[kl-1][0]); if (cmap[kl-1][1]) PPM_PUTG(picout[i][j],cmap[kl-1][1]); if (cmap[kl-1][2]) PPM_PUTB(picout[i][j],cmap[kl-1][2]); }; }; }; int main(argc, argv) int argc; char *argv[]; { int i, j, k, ii, jj; gray maxval; int rows, cols; int time1, time2 ; double di, dj, ddi, ddj; double v; int rmax; FILE *fp; char img[100]; int imgl; /* calibration image name */ float Yaim = 0.0, Xaim = -2.62, Distance = 8.5, FOV = 90.0; /* Daimler Benz old camera calibration setup Yaim = 0.0, Xaim = 0.0, Distance = 40.33, FOV = 30.0; */ /* CMU red camera Yaim = 0.0, Xaim = -2.62, Distance = 8.5, FOV = 90.0; */ if (argc < 2) { printf("Usage: flatfish spot.pgm \n"); printf("spot.pgm: an aligned image of a calibration spot grid\n"); printf("Y,Xaim: down,right spot coords of camera aim, center spot = (0,0)\n"); printf("Distance: spot spacings between lens center and the spot grid\n"); printf("FOV: Degrees horizontal field of view of rectified image\n"); exit(0); }; strcpy(img,argv[1]); imgl = strlen(img); if (imgl>5) if (img[imgl-4]=='.') imgl -= 4; img[imgl] = 0; pgm_init(&argc, argv); ppm_init(&argc, argv); /* Read in raw spot image */ fp = fopen(argv[1],"r"); if (fp == NULL) {printf("No %s file!!\n",argv[1]); exit(0); }; picin = pgm_readpgm(fp, &cols, &rows, &maxval ); fclose(fp); Pw = cols; Ph = rows; if (maxval > 255) { printf("Digitized pic pixels too big: %d instead of %d!!\n", maxval,255); exit(0); }; if (argc > 2) sscanf(argv[2],"%g",&Yaim); if (argc > 3) sscanf(argv[3],"%g",&Xaim); if (argc > 4) sscanf(argv[4],"%g",&Distance); if (argc > 5) sscanf(argv[5],"%g",&FOV); if (Yaim>10 || Yaim<-10 || Xaim>15 || Xaim<-15 || Distance<3 || Distance>50 || FOV < 10 || FOV > 180) { printf("(Yaim %.3g Xaim %.3g Distance %.3g FOV %.3g) seem unreasonable\n", Yaim, Xaim, Distance, FOV); exit(0); }; flog = fopen(strcat(img,".log"),"w"); /* open log file */ img[imgl] = 0; /* reset file name to base */ llog("\n"); j = strlen(argv[1]); /* pretty print file name */ for (i=0; i0.0) for (k=1; k= 0) if (ii= 0) if (jj0.0) Nspots++; sprintf(slog,"(%d of them) *\n",Nspots); plog(); spotmaxlink(); /* link up spot maxima */ /* display rings of appropriate size around the found spots */ for (i=0; i=0) stroke(j,Ispots[i],Jspots[i], (int) (Ispots[k]+Ispots[i]+1)/2, (int) (Jspots[k]+Jspots[i]+1)/2); fp = fopen(strcat(img,".links.ppm"),"w"); /* and write out display */ img[imgl] = 0; ppm_writeppm(fp,picout,Pw,Ph,255,0); fclose(fp); /***********************************************************\ fit distortion curve, and find center to minimize scatter \***********************************************************/ llog("\n* Determining optical center and radial distortion *\n"); /* choose initial center, in spot coords, and radial polynomial degree */ CalibSi = IIspots[Pspots]; CalibSj = JJspots[Pspots]; CalibPN = DISTORPOLYDEG ; CalibAS = 1.0; /* initial guess of aspect ratio */ sprintf(slog,"Fit polynomial degree %d\n",CalibPN-1); plog(); graphout = pgm_allocarray(Pw,Ph); /* make raster for scatter graph */ for (dpass = -1; dpass <= 0; dpass++) /* first guess, then fit */ { int scan; /* extent of search */ float reslo, reshi; /* search for a better image center */ bestfit = 1e10; /* Any answer is better than none! */ bestisc = CalibSi; bestjsc = CalibSj; bestaspect = CalibAS; scan = dpass?0:2; /* 1st pass: use initial guess */ reslo = 1.5; reshi = dpass?reslo:0.00001; /* 2nd pass: optimize */ for (res = reslo; res >= reshi; res /= 3.0) { for (l = -scan; l<=scan; l++) for (i = -scan; i<=scan; i++) for (j=-scan; j<=scan; j++) { /* convert to pixel coords */ aspect = bestaspect + l*res/10.0; ipc = isc = bestisc + i*res; jpc = jsc = bestjsc + j*res; rootaspect = sqrt(aspect); spotpixelcoord(&ipc,&jpc); for (k=0; kddi) ddi = IJspots[k]; di = IIspots[k] - CalibSi; dj = JJspots[k] - CalibSj; if ((JIspots[k] = sqrt(di*di+dj*dj))>ddj) ddj = JIspots[k]; }; sprintf(slog,"aspect %.4g, center S[%.4g %.4g]==P[%.4g %.4g]", CalibAS,CalibSi,CalibSj,CalibPi,CalibPj); plog(); sprintf(slog," --> %.4g pixel rms error\n",bestfit); plog(); llog("S->P Polynomial: "); for (i=0; iS Polynomial: "); for (i=0; iCmax) Cmax = t; }; llog(pass?"Cols":"Rows"); sprintf(slog,"[%g:%g] ",Cmin,Cmax); plog(); for (Cur = Cmin; Cur <= Cmax; Cur += 0.5) { n = 0; Sx = Sy = 0; for (k=0; k3) /* if row/column has enough members, add in its sums */ { sprintf(slog,"%d ",n); plog(); Sx /= n; Sy /= n; for (k=0; k=0 && i < Ph && j>=0 && jPI/4) CalibAN -= PI/2; while (CalibAN<-PI/4) CalibAN += PI/2; sprintf(slog,"Grid angle = %.4g degrees\n",CalibAN*180/PI); plog(); }; /* Write out all calibration parameters into .calib file */ llog("\n* Writing out calibration file *\n"); fp = fopen(strcat(img,".calib"),"w"); img[imgl] = 0; /* write out the calibration setup, Ph, Pw, Xaim, Yaim, Distance, FOV, The polynomial degree+1, the distortion center i and j pixel coordinates, the aspect ratio, the distortion center i and j spot coordinates, the image rotation angle, the Spot->Pixel polynomial coefficients, the Pixel->Spot polynomial coefficients */ fprintf(fp,"%d %d %.6g %.6g %.6g %.6g\n", Ph, Pw, Yaim, Xaim, Distance, FOV); fprintf(fp,"%d %.6g %.6g %.6g %.6g %.6g %.6g\n", CalibPN,CalibPi, CalibPj,CalibAS,CalibSi,CalibSj,CalibAN); for (i=0; iP Polynomial:"); for (i=0; iS Polynomial:"); for (i=0; i=0; k--) rp = rp*rs+CalibP[k]; /* project pixel radius back onto to spot radius direction, rotated by grid angle from horizontal */ ang = atan2(is,js); sang = sin(ang-CalibAN); cang = cos(ang-CalibAN); ip = rp*sang/rootaspect; jp = rp*cang*rootaspect; /* and add pixel optical axis to get source pixel */ ii = ip + ipc; jj = jp + jpc; if (ii<0 || ii>Ph-1 || jj<0 || jj>Pw-1) { errc++; PPM_ASSIGN(picout[i][j],128,128,128); } else PPM_ASSIGN(picout[i][j],picin[ii][jj],picin[ii][jj],picin[ii][jj]); }; sprintf(slog,"%d pixels from beyond the edge\n",errc); plog(); for (k=-1; k<=1; k+=2) { for (is=0; is