/** Cache J2000 rotation over existing cached time range using polynomials * * This method will reload an internal cache with matrices * formed from rotation angles fit to polynomials over a time * range. * * @param function1 The first polynomial function used to * find the rotation angles * @param function2 The second polynomial function used to * find the rotation angles * @param function3 The third polynomial function used to * find the rotation angles */ void LineScanCameraRotation::ReloadCache() { NaifStatus::CheckErrors(); // Make sure caches are already loaded if(!p_cachesLoaded) { QString msg = "A LineScanCameraRotation cache has not been loaded yet"; throw IException(IException::Programmer, msg, _FILEINFO_); } // Clear existing matrices from cache p_cache.clear(); // Create polynomials fit to angles & use to reload cache Isis::PolynomialUnivariate function1(p_degree); Isis::PolynomialUnivariate function2(p_degree); Isis::PolynomialUnivariate function3(p_degree); // Get the coefficients of the polynomials already fit to the angles of rotation defining [CI] std::vector<double> coeffAng1; std::vector<double> coeffAng2; std::vector<double> coeffAng3; GetPolynomial(coeffAng1, coeffAng2, coeffAng3); // Reset linear term to center around zero -- what works best is either roll-avg & pitchavg+ or pitchavg+ & yawavg- // coeffAng1[1] -= 0.0000158661225; // coeffAng2[1] = 0.0000308433; // coeffAng3[0] = -0.001517547; if(p_pitchRate) coeffAng2[1] = p_pitchRate; if(p_yaw) coeffAng3[0] = p_yaw; // Load the functions with the coefficients function1.SetCoefficients(coeffAng1); function2.SetCoefficients(coeffAng2); function3.SetCoefficients(coeffAng3); double CI[3][3]; double IJ[3][3]; std::vector<double> rtime; SpiceRotation *prot = p_spi->bodyRotation(); std::vector<double> CJ; CJ.resize(9); for(std::vector<double>::size_type pos = 0; pos < p_cacheTime.size(); pos++) { double et = p_cacheTime.at(pos); rtime.push_back((et - GetBaseTime()) / GetTimeScale()); double angle1 = function1.Evaluate(rtime); double angle2 = function2.Evaluate(rtime); double angle3 = function3.Evaluate(rtime); rtime.clear(); // Get the first angle back into the range Naif expects [180.,180.] if(angle1 < -1 * pi_c()) { angle1 += twopi_c(); } else if(angle1 > pi_c()) { angle1 -= twopi_c(); } eul2m_c((SpiceDouble) angle3, (SpiceDouble) angle2, (SpiceDouble) angle1, p_axis3, p_axis2, p_axis1, CI); mxm_c((SpiceDouble( *)[3]) & (p_jitter->SetEphemerisTimeHPF(et))[0], CI, CI); prot->SetEphemerisTime(et); mxm_c((SpiceDouble( *)[3]) & (p_cacheIB.at(pos))[0], (SpiceDouble( *)[3]) & (prot->Matrix())[0], IJ); mxm_c(CI, IJ, (SpiceDouble( *)[3]) &CJ[0]); p_cache.push_back(CJ); // J2000 to constant frame } // Set source to cache to get updated values SetSource(SpiceRotation::Memcache); // Make sure SetEphemerisTime updates the matrix by resetting it twice (in case the first one // matches the current et. p_et is private and not available from the child class NaifStatus::CheckErrors(); SetEphemerisTime(p_cacheTime[0]); SetEphemerisTime(p_cacheTime[1]); NaifStatus::CheckErrors(); }
/** Cache J2000 rotation quaternion over a time range. * * This method will load an internal cache with frames over a time * range. This prevents the NAIF kernels from being read over-and-over * again and slowing an application down due to I/O performance. Once the * cache has been loaded then the kernels can be unloaded from the NAIF * system. * * @internal * @history 2010-12-23 Debbie A. Cook Added set of full cache time * parameters */ void LineScanCameraRotation::LoadCache() { NaifStatus::CheckErrors(); double startTime = p_cacheTime[0]; int size = p_cacheTime.size(); double endTime = p_cacheTime[size-1]; SetFullCacheParameters(startTime, endTime, size); // TODO Add a label value to indicate pointing is already decomposed to line scan angles // and set p_pointingDecomposition=none,framing angles, or line scan angles. // Also add a label value to indicate jitterOffsets=jitterFileName // Then we can decide whether to simply grab the crot angles or do new decomposition and whether // to apply jitter or throw an error because jitter has already been applied. // *** May need to do a frame trace and load the frames (at least the constant ones) *** // Loop and load the cache double state[6]; double lt; NaifStatus::CheckErrors(); double R[3]; // Direction of radial axis of line scan camera double C[3]; // Direction of cross-track axis double I[3]; // Direction of in-track axis double *velocity; std::vector<double> IB(9); std::vector<double> CI(9); SpiceRotation *prot = p_spi->bodyRotation(); SpiceRotation *crot = p_spi->instrumentRotation(); for(std::vector<double>::iterator i = p_cacheTime.begin(); i < p_cacheTime.end(); i++) { double et = *i; prot->SetEphemerisTime(et); crot->SetEphemerisTime(et); // The following code will be put into method LoadIBcache() spkezr_c("MRO", et, "IAU_MARS", "NONE", "MARS", state, <); NaifStatus::CheckErrors(); // Compute the direction of the radial axis (3) of the line scan camera vscl_c(1. / vnorm_c(state), state, R); // vscl and vnorm only operate on first 3 members of state // Compute the direction of the cross-track axis (2) of the line scan camera velocity = state + 3; vscl_c(1. / vnorm_c(velocity), velocity, C); vcrss_c(R, C, C); // Compute the direction of the in-track axis (1) of the line scan camera vcrss_c(C, R, I); // Load the matrix IB and enter it into the cache vequ_c(I, (SpiceDouble( *)) &IB[0]); vequ_c(C, (SpiceDouble( *)) &IB[3]); vequ_c(R, (SpiceDouble( *)) &IB[6]); p_cacheIB.push_back(IB); // end IB code // Compute the CIcr matrix - in-track, cross-track, radial frame to constant frame mxmt_c((SpiceDouble( *)[3]) & (crot->TimeBasedMatrix())[0], (SpiceDouble( *)[3]) & (prot->Matrix())[0], (SpiceDouble( *)[3]) &CI[0]); // Put CI into parent cache to use the parent class methods on it mxmt_c((SpiceDouble( *)[3]) &CI[0], (SpiceDouble( *)[3]) &IB[0], (SpiceDouble( *)[3]) &CI[0]); p_cache.push_back(CI); } p_cachesLoaded = true; SetSource(Memcache); NaifStatus::CheckErrors(); }
void IsisMain() { UserInterface &ui = Application::GetUserInterface(); double time0,//start time time1,//end time alti, //altitude of the spacecraftmore fmc, //forward motion compensation rad/sec horV, //horizontal velocity km/sec radV, //radial velocity km/sec rollV,//roll speed in rad/sec led; //line exposure duration in seconds Cube panCube; iTime isisTime; QString iStrTEMP; int i,j,k,scFrameCode,insCode; QString mission; SpicePosition *spPos; SpiceRotation *spRot; //int nlines,nsamples,nbands; double deg2rad = acos(-1.0)/180.0; ProcessImport jp; FileName transFile("$apollo15/translations/apollopantranstable.trn"); PvlTranslationTable transTable(transFile); PvlGroup kernels_pvlG; //scFrameCode and insCode from user input mission = ui.GetString("MISSION"); if (mission == "APOLLO12") scFrameCode = -912000; if (mission == "APOLLO14") scFrameCode = -914000; if (mission == "APOLLO15") scFrameCode = -915000; if (mission == "APOLLO16") scFrameCode = -916000; if (mission == "APOLLO17") scFrameCode = -917000; insCode = scFrameCode - 230; try { panCube.open(ui.GetFileName("FROM"),"rw"); } catch (IException &e) { throw IException(IException::User, "Unable to open the file [" + ui.GetFileName("FROM") + "] as a cube.", _FILEINFO_); } ////////////////////////////////////////////build the cube header instrament group PvlGroup inst_pvlG("Instrument"); PvlKeyword keyword; //four that are the same for every panaramic mission keyword.setName("SpacecraftName"); keyword.setValue(mission); inst_pvlG.addKeyword(keyword); keyword.setName("InstrumentName"); keyword.setValue(transTable.Translate("InstrumentName","whatever")); inst_pvlG.addKeyword(keyword); keyword.setName("InstrumentId"); keyword.setValue(transTable.Translate("InstrumentId","whatever")); inst_pvlG.addKeyword(keyword); keyword.setName("TargetName"); keyword.setValue(transTable.Translate("TargetName","whatever")); inst_pvlG.addKeyword(keyword); //three that need to be calculated from input values horV = ui.GetDouble("VEL_HORIZ"); radV = ui.GetDouble("VEL_RADIAL"); alti = ui.GetDouble("CRAFT_ALTITUDE"); //caculate the LineExposureDuration (led) if( ui.WasEntered("V/H_OVERRIDE") ) fmc = ui.GetDouble("V/H_OVERRIDE")/1000.0; else //forward motion compensation is directly equivalent to V/H fmc = sqrt(horV*horV + radV*radV)/alti; rollV = fmc*ROLLC; //roll angular velcoity is equal to V/H * constant (units rad/sec) //led = rad/mm * sec/rad = radians(2.5)/FIDL / rollV (final units: sec/mm) led = (2.5*acos(-1.0)/180.0)/rollV/FIDL; //use led and the number of mm to determine the start and stop times isisTime = ui.GetString("GMT"); //calculate starting and stoping times time0 = isisTime.Et() - led*FIDL*21.5; time1 = time0 + led*FIDL*43; isisTime = time0; keyword.setName("StartTime"); keyword.setValue(iStrTEMP=isisTime.UTC()); inst_pvlG.addKeyword(keyword); isisTime = time1; keyword.setName("StopTime"); keyword.setValue(iStrTEMP=isisTime.UTC()); inst_pvlG.addKeyword(keyword); keyword.setName("LineExposureDuration"); //converted led to msec/mm--negative sign to account for the anti-parallel time and line axes keyword.setValue(iStrTEMP=toString(-led),"sec/mm"); inst_pvlG.addKeyword(keyword); panCube.putGroup(inst_pvlG); ///////////////////////////////////The kernals group kernels_pvlG.setName("Kernels"); kernels_pvlG.clear(); keyword.setName("NaifFrameCode"); keyword.setValue(toString(insCode)); kernels_pvlG.addKeyword(keyword); keyword.setName("LeapSecond"); keyword.setValue( transTable.Translate("LeapSecond","File1") ); kernels_pvlG.addKeyword(keyword); keyword.setName("TargetAttitudeShape"); keyword.setValue( transTable.Translate("TargetAttitudeShape", "File1") ); keyword.addValue( transTable.Translate("TargetAttitudeShape", "File2") ); keyword.addValue( transTable.Translate("TargetAttitudeShape", "File3") ); kernels_pvlG.addKeyword(keyword); keyword.setName("TargetPosition"); keyword.setValue("Table"); keyword.addValue( transTable.Translate("TargetPosition", "File1") ); keyword.addValue( transTable.Translate("TargetPosition", "File2") ); kernels_pvlG.addKeyword(keyword); keyword.setName("ShapeModel"); keyword.setValue( transTable.Translate("ShapeModel", "File1") ); kernels_pvlG.addKeyword(keyword); keyword.setName("InstrumentPointing"); keyword.setValue("Table"); kernels_pvlG.addKeyword(keyword); keyword.setName("InstrumentPosition"); keyword.setValue("Table"); kernels_pvlG.addKeyword(keyword); keyword.setName("InstrumentAddendum"); keyword.setValue( transTable.Translate("InstrumentAddendum",mission)); kernels_pvlG.addKeyword(keyword); panCube.putGroup(kernels_pvlG); //Load all the kernals Load_Kernel(kernels_pvlG["TargetPosition"]); Load_Kernel(kernels_pvlG["TargetAttitudeShape"]); Load_Kernel(kernels_pvlG["LeapSecond"]); //////////////////////////////////////////attach a target rotation table char frameName[32]; SpiceInt frameCode; SpiceBoolean found; //get the framecode from the body code (301=MOON) cidfrm_c(301, sizeof(frameName), &frameCode, frameName, &found); if(!found) { QString naifTarget = QString("IAU_MOOM"); namfrm_c(naifTarget.toAscii().data(), &frameCode); if(frameCode == 0) { QString msg = "Can not find NAIF code for [" + naifTarget + "]"; throw IException(IException::Io, msg, _FILEINFO_); } } spRot = new SpiceRotation(frameCode); //create a table from starttime to endtime (streched by 3%) with NODES entries spRot->LoadCache(time0-0.015*(time1-time0), time1+0.015*(time1-time0), NODES); Table tableTargetRot = spRot->Cache("BodyRotation"); tableTargetRot.Label() += PvlKeyword("Description", "Created by apollopaninit"); panCube.write(tableTargetRot); //////////////////////////////////////////////////attach a sun position table spPos = new SpicePosition(10,301); //Position of the sun (10) WRT to the MOON (301) //create a table from starttime to endtime (stretched by 3%) with NODES entries spPos->LoadCache(time0-0.015*(time1-time0), time1+0.015*(time1-time0), NODES); Table tableSunPos = spPos->Cache("SunPosition"); tableSunPos.Label() += PvlKeyword("SpkTableStartTime", toString(time0-0.015*(time1-time0))); tableSunPos.Label() += PvlKeyword("SpkTablleEndTime", toString(time1+0.015*(time1-time0))); tableSunPos.Label() += PvlKeyword("Description", "Created by apollopaninit"); panCube.write(tableSunPos); //attach the table to the cube /////////////Finding the principal scan line position and orientation //get the radii of the MOON SpiceInt tempRadii = 0; bodvcd_c(301,"RADII",3,&tempRadii,R_MOON); //units are km double omega,phi,kappa; std::vector<double> posSel; //Seleno centric position std::vector<double> sunPos; //sunPosition used to transform to J2000 std::vector<double> posJ20; //camera position in J2000 posSel.resize(3); sunPos.resize(3); posJ20.resize(3); double temp, vel[3] = { 0.0, 0.0, 0.0 }, //the total velocity vector (combined Horizonatal and normal components) // in km/sec M[3][3] = { { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }, //rotation matrix zDir[] = { 0.0, 0.0, 1.0 }, //selenographic Z axis northPN[3] = { 0.0, 0.0, 0.0 }, //normal to the plane containing all the north/south directions, // that is plane containing // the origin, the z axis, and the primary point of intersection northL[3] = { 0.0, 0.0, 0.0 }, //north direction vector in local horizontal plane azm[3] = { 0.0, 0.0, 0.0 }, //azm direction of the veclocity vector in selenographic coordinates azmP[3] = { 0.0, 0.0, 0.0 }, //azm rotated (partially) and projected into the image plane norm[3] = { 0.0, 0.0, 0.0 }, //normal to the local horizontal plane look[3] = { 0.0, 0.0, 0.0 }; //unit direction vector in the pincipal cameral look direction, // parallel to the vector from the center of the moon through the spacecraft double pos0[3] = { 0.0, 0.0, 0.0 }, //coordinate of the camera position pInt[3] = { 0.0, 0.0, 0.0 }; //coordinate of the principle intersection point /////////////////calculating the camera position for the center (principal scan line) pos0[1] = ui.GetDouble("LON_NADIR")*deg2rad; pos0[0] = ui.GetDouble("LAT_NADIR")*deg2rad; pos0[2] = ui.GetDouble("CRAFT_ALTITUDE"); //units are km Geographic2GeocentricLunar(pos0,pos0); //function is written so the input can also be the // output /////////////////////calculating the camera orientation for the center (principal) scan line pInt[1] = ui.GetDouble("LON_INT")*deg2rad; pInt[0] = ui.GetDouble("LAT_INT")*deg2rad; pInt[2] = 0.0; Geographic2GeocentricLunar(pInt,pInt); //function is written so the input can also be the output //calculate the unit look direction vector in object space look[0] = -pos0[0] + pInt[0]; look[1] = -pos0[1] + pInt[1]; look[2] = -pos0[2] + pInt[2]; temp = sqrt(look[0]*look[0] + look[1]*look[1] + look[2]*look[2]); look[0] /= temp; look[1] /= temp; look[2] /= temp; //the local normal vector is equal to pInt0/|pInt0| temp = sqrt(pInt[0]*pInt[0] + pInt[1]*pInt[1] + pInt[2]*pInt[2]); norm[0] = pInt[0]/temp; norm[1] = pInt[1]/temp; norm[2] = pInt[2]/temp; //omega and phi are defined so that M(phi)M(omega)look = [0 0 -1] leaving only the roation // around z axis to be found omega = -atan2(look[1], look[2]); //omega rotation to zero look[1] phi = atan2(-look[0], sin(omega)*look[1] - cos(omega)*look[2]); //phi rotation to zero look[0] //use the horizontal velocity vector direction to solve for the last rotation; we will make the // image x axis parallel to the in-image-plane projection of the horizontal direction of flight. // The local normal cross the selenogrpahic z gives northPN (normal to the plane containing all // the north/south directions), that is, the plane containing the origin, the z axis, and the // primary point of intersection. crossp(northPN,norm,northL); //The normal to the plane containing all the north/south directions cross the local normal // direction gives the local north/south direction in the local normal plane crossp(norm, zDir, northPN); if (northL[2] < 0) { //if by chance we got the south direction change the signs northL[0] = -northL[0]; northL[1] = -northL[1]; northL[2] = -northL[2]; } //define the rotation matrix to convert northL to the azimuth of flight. // A left handed rotation of "VEL_AZM" around the positive normal direction will convert northL // to azm MfromVecLeftAngle(M,norm,ui.GetDouble("VEL_AZM")*deg2rad); azm[0] = M[0][0]*northL[0] + M[0][1]*northL[1] + M[0][2]*northL[2]; azm[1] = M[1][0]*northL[0] + M[1][1]*northL[1] + M[1][2]*northL[2]; azm[2] = M[2][0]*northL[0] + M[2][1]*northL[1] + M[2][2]*northL[2]; //apply the two rotations we already know MfromLeftEulers(M,omega,phi,0.0); azmP[0] = M[0][0]*azm[0] + M[0][1]*azm[1] + M[0][2]*azm[2]; azmP[1] = M[1][0]*azm[1] + M[1][1]*azm[1] + M[1][2]*azm[2]; azmP[2] = M[2][0]*azm[2] + M[2][1]*azm[1] + M[2][2]*azm[2]; //subtract that portion of the azm that is perpindicular to the image plane (also the portion // which is parallel to look) making azm a vector parrallel to the image plane // Further, since we're now rotated into some coordinate system that differs from // the image coordinate system by only a kappa rotation making the vector parrallel to the // image plan is as simple as zeroing the z component (and as pointless to further calculations // as a nat's fart in hurricane) nevertheless it completes the logical transition azmP[2] = 0.0; //finally the kappa rotation that will make azmP parallel (including sign) to the camera x axis kappa = -atan2(-azmP[1], azmP[0]); ////////////////////Add an instrument position table //Define the table records TableRecord recordPos; // reacord to be added to table // add x,y,z position labels and ephemeris time et to record TableField x("J2000X", TableField::Double); TableField y("J2000Y", TableField::Double); TableField z("J2000Z", TableField::Double); TableField t("ET", TableField::Double); recordPos += x; recordPos += y; recordPos += z; recordPos += t; Table tablePos("InstrumentPosition", recordPos); //now that the azm and norm vectors are defined // the total velocity vector can be calcualted (km/sec) vel[0] = horV*azm[0] + radV * norm[0]; vel[1] = horV*azm[1] + radV * norm[1]; vel[2] = horV*azm[2] + radV * norm[2]; //we'll provide a two ellement table (more is redundant because the motion is modeled as linear // at this point) we'll extend the nodes 3% beyond the edges of the images to be sure // rounding errors don't cause problems temp = 0.515*(time1-time0); //3% extension posSel[0] = pos0[0] - temp*vel[0]; //selenocentric coordinate calculation posSel[1] = pos0[1] - temp*vel[1]; posSel[2] = pos0[2] - temp*vel[2]; //converting to J2000 temp = time0 - 0.005*(time1-time0); //et just before the first scan line spPos->SetEphemerisTime(temp); spRot->SetEphemerisTime(temp); //Despite being labeled as J2000, the coordinates for the instrument position are in fact in // target centric coordinated rotated to a system centered at the target with aces parallel // to J2000, whatever that means posJ20 = spRot->J2000Vector(posSel); //J2000Vector calls rotates the position vector into J2000, // completing the transformation recordPos[0] = posJ20[0]; recordPos[1] = posJ20[1]; recordPos[2] = posJ20[2]; recordPos[3] = temp; //temp = et (right now anyway) tablePos += recordPos; tablePos.Label() += PvlKeyword("SpkTableStartTime",toString(temp)); //now the other node temp = 0.515*(time1-time0); //3% extension posSel[0] = pos0[0] + temp*vel[0]; //selenocentric coordinate calculation posSel[1] = pos0[1] + temp*vel[1]; posSel[2] = pos0[2] + temp*vel[2]; //converting to J2000 temp = time1 + 0.015*(time1-time0); //et just after the last scan line spPos->SetEphemerisTime(temp); spRot->SetEphemerisTime(temp); //Despite being labeled as J2000, the coordinates for the instrument position are in fact // in target centric coordinated rotated to a system centered at the target with aces // parallel to J2000, whatever that means posJ20 = spRot->J2000Vector(posSel); //J2000Vector calls rotates the position vector into J2000, // completing the transformation recordPos[0] = posJ20[0]; recordPos[1] = posJ20[1]; recordPos[2] = posJ20[2]; recordPos[3] = temp; //temp = et (right now anyway) tablePos += recordPos; tablePos.Label() += PvlKeyword("SpkTableEndTime",toString(temp)); tablePos.Label() += PvlKeyword("CacheType","Linear"); tablePos.Label() += PvlKeyword("Description","Created by apollopaninit"); panCube.write(tablePos); //now attach it to the table /////////////////////////////attach a camera pointing table double cacheSlope, //time between epoches in the table rollComb, //magnitude of roll relative to the center in the middle of the epoch relT, //relative time at the center of each epoch Q[NODES][5], //NODES four ellement unit quarternions and et (to be calculated). gimVec[3], //the direction of the gimbal rotation vector (to the cameras persepective // this is always changing because the camera is mounted to the roll frame // assembly which is mounted to the gimbal) M0[3][3], //rotation matrix of the previous epoch Mtemp1[3][3], //intermediate step in the multiplication of rotation matricies Mtemp2[3][3], //intermediate step in the multiplication of rotation matricies Mdg[3][3], //incremental rotation due the the gimbal motion in the camera frame Mdr[3][3]; //the contribution of the roll motion in the camera frame during time // cacheSlope std::vector <double> M_J2toT; //rotation matrix from J2000 to the target frame M_J2toT.resize(9); //Table Definition TableField q0("J2000Q0", TableField::Double); TableField q1("J2000Q1", TableField::Double); TableField q2("J2000Q2", TableField::Double); TableField q3("J2000Q3", TableField::Double); TableField et("ET", TableField::Double); TableRecord recordRot; recordRot += q0; recordRot += q1; recordRot += q2; recordRot += q3; recordRot += et; Table tableRot("InstrumentPointing",recordRot); //From the cameras perspective the gimbal motion is around a constantly changing axis, // this is handled by combining a series of incremental rotations MfromLeftEulers(M0, omega, phi, kappa); //rotation matrix in the center Q[(NOPDES-1)/2] spRot->SetEphemerisTime(isisTime.Et()); M_J2toT = spRot->Matrix(); //this actually gives the rotation from J2000 to target centric for(j=0; j<3; j++) //reformating M_J2toT to a 3x3 for(k=0; k<3; k++) Mtemp1[j][k] = M_J2toT[3*j+k]; mxm_c(M0, Mtemp1, Mtemp2); M2Q(Mtemp2, Q[(NODES-1)/2]); //save the middle scan line quarternion Q[(NODES-1)/2][4] = (time1 + time0)/2.0; //time in the center of the image //the total time is scaled up slightly so that nodes will extend just beyond the edge of the image cacheSlope = 1.03*(time1 - time0)/(NODES-1); //Mdr is constant for all the forward time computations MfromLeftEulers(Mdr,cacheSlope*rollV,0.0,0.0); for (i=(NODES-1)/2+1; i<NODES; i++) { //moving foward in time first Q[i][4] = Q[i-1][4] + cacheSlope; //new time epoch //epoch center time relative to the center line relT = double(i - (NODES-1)/2 - 0.5)*cacheSlope; rollComb = relT*rollV; gimVec[0] = 0.0; //gimbal rotation vector direction in the middle of the epoch gimVec[1] = cos(rollComb); gimVec[2] = -sin(rollComb); //incremental rotation due to the gimbal (forward motion compensation) MfromVecLeftAngle(Mdg, gimVec, fmc*cacheSlope); //the new rotation matrix is Transpose(Mdr)*Transpose(Mdg)*M0--NOTE the order swap and // transposes are needed because both Mdr and Mdg were caculated in image space and need to be // transposed to apply to object space mtxm_c(Mdg, M0, Mtemp1); //M0 is now what would typically be considered the rotation matrix of an image. It rotates a // vector from the target centric space into camera space. However, what is standard to // include in the cube labels is a rotation from camera space to J2000. M0 is therefore the // transpose of the first part of this rotation. Transpose(M0) is the rotation from camera // space to target centric space mtxm_c(Mdr, Mtemp1, M0); //now adding the rotation from the target frame to J2000 spRot->SetEphemerisTime(Q[i][4]); //this actually gives the rotation from J2000 to target centric--hence the mxmt_c function being // used later M_J2toT = spRot->Matrix(); for(j=0; j<3; j++) //reformating M_J2toT to a 3x3 for(k=0; k<3; k++) Mtemp1[j][k] = M_J2toT[3*j+k]; mxm_c(M0, Mtemp1, Mtemp2); M2Q(Mtemp2, Q[i]); //convert to a quarterion } MfromLeftEulers(M0, omega, phi, kappa); //rotation matrix in the center Q[(NOPDES-1)/2] //Mdr is constant for all the backward time computations MfromLeftEulers(Mdr, -cacheSlope*rollV, 0.0, 0.0); for (i=(NODES-1)/2-1; i>=0; i--) { //moving backward in time Q[i][4] = Q[i+1][4] - cacheSlope; //new time epoch //epoch center time relative to the center line relT = double(i - (NODES-1)/2 + 0.5)*cacheSlope; rollComb = relT*rollV; gimVec[0] = 0.0; //gimbal rotation vector direction in the middle of the epoch gimVec[1] = cos(rollComb); gimVec[2] = -sin(rollComb); //incremental rotation due to the gimbal (forward motion compensation) MfromVecLeftAngle(Mdg, gimVec, -fmc*cacheSlope); //the new rotation matrix is Transpose(Mdr)*Transpose(Mdg)*M0 NOTE the order swap and // transposes are needed because both Mdr and Mdg were caculated in image space and need to be // transposed to apply to object space mtxm_c(Mdg, M0, Mtemp1); //M0 is now what would typically be considered the rotation matrix of an image. It rotates a // vector from the target centric space into camera space. However, what is standard to // include in the cube labels is a rotation from camera space to J2000. M0 is therefore the // transpose of the first part of this rotation. Transpose(M0) is the rotation from camera // space to target centric space mtxm_c(Mdr, Mtemp1, M0); //now adding the rotation from the target frame to J2000 spRot->SetEphemerisTime(Q[i][4]); M_J2toT = spRot->Matrix(); for(j=0; j<3; j++) //reformating M_J2toT to a 3x3 for(k=0; k<3; k++) Mtemp1[j][k] = M_J2toT[3*j+k]; mxm_c(M0, Mtemp1, Mtemp2); M2Q(Mtemp2, Q[i]); //convert to a quarterion } //fill in the table for (i=0; i<NODES; i++) { recordRot[0] = Q[i][0]; recordRot[1] = Q[i][1]; recordRot[2] = Q[i][2]; recordRot[3] = Q[i][3]; recordRot[4] = Q[i][4]; tableRot += recordRot; } tableRot.Label() += PvlKeyword("CkTableStartTime", toString(Q[0][4])); tableRot.Label() += PvlKeyword("CkTableEndTime", toString(Q[NODES-1][4])); tableRot.Label() += PvlKeyword("Description", "Created by appollopan2isis"); keyword.setName("TimeDependentFrames"); keyword.setValue(toString(scFrameCode)); keyword.addValue("1"); tableRot.Label() += keyword; keyword.setName("ConstantFrames"); keyword.setValue(toString(insCode)); keyword.addValue(toString(scFrameCode)); tableRot.Label() += keyword; keyword.setName("ConstantRotation"); keyword.setValue("1"); for (i=1;i<9;i++) if (i%4 == 0) keyword.addValue("1"); else keyword.addValue("0"); tableRot.Label() += keyword; panCube.write(tableRot); /////////////////////////Attach a table with all the measurements of the fiducial mark locations. Chip patternS,searchS; //scaled pattern and search chips Cube fidC; //Fiducial image //line and sample coordinates for looping through the panCube double l=1,s=1,sample,line,sampleInitial=1,lineInitial=1,play; int regStatus, fidn, panS, refL, //number of lines in the patternS refS; //number of samples in the patternS Pvl pvl; bool foundFirst=false; QString fileName; panS = panCube.sampleCount(); //Table definition TableRecord recordFid; TableField indexFid("FID_INEX",TableField::Integer); TableField xFid("X_COORD",TableField::Double); TableField yFid("Y_COORD",TableField::Double); recordFid += indexFid; recordFid += xFid; recordFid += yFid; Table tableFid("Fiducial Measurement",recordFid); //read the image resolutions and scale the constants acordingly double resolution = ui.GetDouble("MICRONS"), //pixel size in microns scale = SCALE *5.0/resolution, //reduction scale for fast autoregistrations searchHeight = SEARCHh*5.0/resolution, //number of lines (in 5-micron-pixels) in // search space for the first fiducial searchCellSize = SEARCHc*5.0/resolution, //height/width of search chips block averageSamples = AVERs *5.0/resolution, //scaled smaples between fiducials averageLines = AVERl *5.0/resolution; //scaled average distance between the top and //bottom fiducials if( 15.0/resolution < 1.5) play=1.5; else play = 15.0/resolution; //copy the patternS chip (the entire ApolloPanFiducialMark.cub) FileName fiducialFileName("$apollo15/calibration/ApolloPanFiducialMark.cub"); fidC.open(fiducialFileName.expanded(),"r"); if( !fidC.isOpen() ) { QString msg = "Unable to open the fiducial patternS cube: ApolloPanFiducialMark.cub\n"; throw IException(IException::User, msg, _FILEINFO_); } refL = fidC.lineCount(); refS = fidC.sampleCount(); //scaled pattern chip for fast matching patternS.SetSize(int((refS-2)/SCALE), int((refL-2)/SCALE)); patternS.TackCube((refS-1)/2, (refL-1)/2); patternS.Load(fidC, 0, SCALE); //parameters for maximum correlation autoregestration // see: file:///usgs/pkgs/isis3nightly2011-09-21/isis/doc/documents/patternSMatch/patternSMatch.html#DistanceTolerance FileName fiducialPvl("$apollo15/templates/apolloPanFiducialFinder.pvl"); pvl.read(fiducialPvl.expanded()); //read in the autoreg parameters AutoReg *arS = AutoRegFactory::Create(pvl); *arS->PatternChip() = patternS; //patternS chip is constant //set up a centroid measurer CentroidApolloPan centroid(resolution); Chip inputChip,selectionChip; inputChip.SetSize(int(ceil(200*5.0/resolution)), int(ceil(200*5.0/resolution))); fileName = ui.GetFileName("FROM"); if( panCube.pixelType() == 1) //UnsignedByte centroid.setDNRange(12, 1e99); //8 bit bright target else centroid.setDNRange(3500, 1e99); //16 bit bright target Progress progress; progress.SetText("Locating Fiducials"); progress.SetMaximumSteps(91); //Search for the first fiducial, search sizes are constanst searchS.SetSize(int(searchCellSize/scale),int(searchCellSize/scale)); //now start searching along a horizontal line for the first fiducial mark for(l = searchCellSize/2; l<searchHeight+searchCellSize/2.0 && !foundFirst; l+=searchCellSize-125*5.0/resolution) { for (s = searchCellSize/2; s < averageSamples + searchCellSize/2.0 && !foundFirst; s += searchCellSize-125*5.0/resolution) { searchS.TackCube(s, l); searchS.Load(panCube, 0, scale); *arS->SearchChip() = searchS; regStatus = arS->Register(); if (regStatus == AutoReg::SuccessPixel) { inputChip.TackCube(arS->CubeSample(), arS->CubeLine()); inputChip.Load(panCube, 0, 1); inputChip.SetCubePosition(arS->CubeSample(), arS->CubeLine()); //continuous dynamic range selection centroid.selectAdaptive(&inputChip, &selectionChip); //elliptical trimming/smoothing if (centroid.elipticalReduction(&selectionChip, 95, play, 2000)) { //center of mass to reduce selection to a single measure centroid.centerOfMass(&selectionChip, &sample, &line); inputChip.SetChipPosition(sample, line); sampleInitial = inputChip.CubeSample(); lineInitial = inputChip.CubeLine(); foundFirst = true; //once the first fiducial is found stop } } } } if(s>=averageLines+searchCellSize/2.0) { QString msg = "Unable to locate a fiducial mark in the input cube [" + fileName + "]. Check FROM and MICRONS parameters."; throw IException(IException::Io, msg, _FILEINFO_); return; } progress.CheckStatus(); //record first fiducial measurement in the table recordFid[0] = 0; recordFid[1] = sampleInitial; recordFid[2] = lineInitial; tableFid += recordFid; for (s= sampleInitial, l=lineInitial, fidn=0; s<panS; s+=averageSamples, fidn++) { //corrections for half spacing of center fiducials if (fidn == 22) s -= averageSamples/2.0; if (fidn == 23) s -= averageSamples/2.0; //look for the bottom fiducial searchS.TackCube(s,l+averageLines); searchS.Load(panCube, 0, scale); *arS->SearchChip() = searchS; regStatus = arS->Register(); if (regStatus == AutoReg::SuccessPixel) { inputChip.TackCube(arS->CubeSample(), arS->CubeLine()); inputChip.Load(panCube,0,1); inputChip.SetCubePosition(arS->CubeSample(), arS->CubeLine()); } else { //if autoreg is unsuccessful, a larger window will be used inputChip.TackCube(s, l+averageLines); inputChip.Load(panCube, 0, 1); inputChip.SetCubePosition(s, l+averageLines); } centroid.selectAdaptive(&inputChip, &selectionChip); //continuous dynamic range selection //elliptical trimming/smoothing... if this fails move on if (centroid.elipticalReduction(&selectionChip, 95, play, 2000) != 0 ) { //center of mass to reduce selection to a single measure centroid.centerOfMass(&selectionChip, &sample, &line); inputChip.SetChipPosition(sample, line); sample = inputChip.CubeSample(); line = inputChip.CubeLine(); recordFid[0] = fidn*2+1; recordFid[1] = sample; recordFid[2] = line; tableFid += recordFid; } progress.CheckStatus(); //look for the top fiducial if (s == sampleInitial) //first time through the loop? continue; //then the top fiducial was already found searchS.TackCube(s, l); searchS.Load(panCube, 0, scale); *arS->SearchChip() = searchS; regStatus = arS->Register(); if (regStatus == AutoReg::SuccessPixel) { inputChip.TackCube(arS->CubeSample(), arS->CubeLine()); inputChip.Load(panCube, 0, 1); inputChip.SetCubePosition(arS->CubeSample(), arS->CubeLine()); } else { //if autoreg is unsuccessful, a larger window will be used inputChip.TackCube(s, l); inputChip.Load(panCube, 0, 1); inputChip.SetCubePosition(s, l); } centroid.selectAdaptive(&inputChip, &selectionChip);//continuous dynamic range selection //inputChip.Write("inputTemp.cub");//debug //selectionChip.Write("selectionTemp.cub");//debug //elliptical trimming/smoothing... if this fails move on if (centroid.elipticalReduction(&selectionChip, 95, play, 2000) !=0) { //center of mass to reduce selection to a single measure centroid.centerOfMass(&selectionChip, &sample, &line); inputChip.SetChipPosition(sample, line); //when finding the top fiducial both s and l are refined for a successful measurement, // this will help follow trends in the scaned image s = inputChip.CubeSample(); l = inputChip.CubeLine(); recordFid[0] = fidn*2; recordFid[1] = s; recordFid[2] = l; tableFid += recordFid; } progress.CheckStatus(); } panCube.write(tableFid); //close the new cube panCube.close(false); panCube.open(ui.GetFileName("FROM"),"rw"); delete spPos; delete spRot; //now instantiate a camera to make sure all of this is working ApolloPanoramicCamera* cam = (ApolloPanoramicCamera*)(panCube.camera()); //log the residual report from interior orientation PvlGroup residualStats("InteriorOrientationStats"); residualStats += PvlKeyword("FiducialsFound", toString(tableFid.Records())); residualStats += PvlKeyword("ResidualMax", toString(cam->intOriResidualMax()),"pixels"); residualStats += PvlKeyword("ResidualMean", toString(cam->intOriResidualMean()),"pixels"); residualStats += PvlKeyword("ResidualStdev", toString(cam->intOriResidualStdev()),"pixels"); Application::Log( residualStats ); return; }