void IsisMain() { ProcessRubberSheet p; // Open the input cube Cube *icube = p.SetInputCube ("FROM"); // Set up the transform object UserInterface &ui = Application::GetUserInterface(); Transform *transform = new Rotate(icube->Samples(), icube->Lines(), ui.GetDouble("DEGREES")); // Determine the output size int samples = transform->OutputSamples(); int lines = transform->OutputLines(); // Allocate the output file p.SetOutputCube ("TO", samples, lines, icube->Bands()); // Set up the interpolator Interpolator *interp; if (ui.GetString("INTERP") == "NEARESTNEIGHBOR") { interp = new Interpolator(Interpolator::NearestNeighborType); } else if (ui.GetString("INTERP") == "BILINEAR") { interp = new Interpolator(Interpolator::BiLinearType); } else if (ui.GetString("INTERP") == "CUBICCONVOLUTION") { interp = new Interpolator(Interpolator::CubicConvolutionType); } else { string msg = "Unknow value for INTERP [" + ui.GetString("INTERP") + "]"; throw iException::Message(iException::Programmer,msg,_FILEINFO_); } p.StartProcess(*transform, *interp); p.EndProcess(); delete transform; delete interp; }
void IsisMain() { ProcessRubberSheet p; // Open the input cube Cube *icube = p.SetInputCube("FROM"); // Set up the transform object UserInterface &ui = Application::GetUserInterface(); Transform *transform = new Translate(icube->sampleCount(), icube->lineCount(), ui.GetDouble("STRANS"), ui.GetDouble("LTRANS")); // Allocate the output file, same size as input p.SetOutputCube("TO", icube->sampleCount(), icube->lineCount(), icube->bandCount()); // Set up the interpolator Interpolator *interp; if(ui.GetString("INTERP") == "NEARESTNEIGHBOR") { interp = new Interpolator(Interpolator::NearestNeighborType); } else if(ui.GetString("INTERP") == "BILINEAR") { interp = new Interpolator(Interpolator::BiLinearType); } else if(ui.GetString("INTERP") == "CUBICCONVOLUTION") { interp = new Interpolator(Interpolator::CubicConvolutionType); } else { QString msg = "Unknow value for INTERP [" + ui.GetString("INTERP") + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); } p.StartProcess(*transform, *interp); p.EndProcess(); delete transform; delete interp; }
void IsisMain() { // Open the match cube and get the camera model on it ProcessRubberSheet m; Cube *mcube = m.SetInputCube("MATCH"); Cube *ocube = m.SetOutputCube("TO"); // Set up the default reference band to the middle of the cube // If we have even bands it will be close to the middle int referenceBand = ocube->bandCount(); referenceBand += (referenceBand % 2); referenceBand /= 2; // See if the user wants to override the reference band UserInterface &ui = Application::GetUserInterface(); if(ui.WasEntered("REFBAND")) { referenceBand = ui.GetInteger("REFBAND"); } // Using the Camera method out of the object opack will not work, because the // filename required by the Camera is not passed by the process class in this // case. Use the CameraFactory to create the Camera instead to get around this // problem. Camera *outcam = CameraFactory::Create(*(mcube->label())); // Set the reference band we want to match PvlGroup instgrp = mcube->group("Instrument"); if(!outcam->IsBandIndependent()) { PvlKeyword rBand("ReferenceBand", toString(referenceBand)); rBand.addComment("# All bands are aligned to reference band"); instgrp += rBand; mcube->putGroup(instgrp); delete outcam; outcam = NULL; } // Only recreate the output camera if it was band dependent if(outcam == NULL) outcam = CameraFactory::Create(*(mcube->label())); // We might need the instrument group later, so get a copy before clearing the input // cubes. m.ClearInputCubes(); Cube *icube = m.SetInputCube("FROM"); incam = icube->camera(); // Set up the transform object which will simply map // output line/samps -> output lat/lons -> input line/samps Transform *transform = new cam2cam(icube->sampleCount(), icube->lineCount(), incam, ocube->sampleCount(), ocube->lineCount(), outcam); // Add the reference band to the output if necessary ocube->putGroup(instgrp); // Set up the interpolator Interpolator *interp = NULL; if(ui.GetString("INTERP") == "NEARESTNEIGHBOR") { interp = new Interpolator(Interpolator::NearestNeighborType); } else if(ui.GetString("INTERP") == "BILINEAR") { interp = new Interpolator(Interpolator::BiLinearType); } else if(ui.GetString("INTERP") == "CUBICCONVOLUTION") { interp = new Interpolator(Interpolator::CubicConvolutionType); } // See if we need to deal with band dependent camera models if(!incam->IsBandIndependent()) { m.BandChange(BandChange); } // Warp the cube m.StartProcess(*transform, *interp); m.EndProcess(); // Cleanup delete transform; delete interp; }
void IsisMain() { // We will be warping a cube ProcessRubberSheet p; // Get the map projection file provided by the user UserInterface &ui = Application::GetUserInterface(); Pvl userPvl(ui.GetFileName("MAP")); PvlGroup &userMappingGrp = userPvl.findGroup("Mapping", Pvl::Traverse); // Open the input cube and get the projection Cube *icube = p.SetInputCube("FROM"); // Get the mapping group PvlGroup fromMappingGrp = icube->group("Mapping"); TProjection *inproj = (TProjection *) icube->projection(); PvlGroup outMappingGrp = fromMappingGrp; // If the default range is FROM, then wipe out any range data in user mapping file if(ui.GetString("DEFAULTRANGE").compare("FROM") == 0 && !ui.GetBoolean("MATCHMAP")) { if(userMappingGrp.hasKeyword("MinimumLatitude")) { userMappingGrp.deleteKeyword("MinimumLatitude"); } if(userMappingGrp.hasKeyword("MaximumLatitude")) { userMappingGrp.deleteKeyword("MaximumLatitude"); } if(userMappingGrp.hasKeyword("MinimumLongitude")) { userMappingGrp.deleteKeyword("MinimumLongitude"); } if(userMappingGrp.hasKeyword("MaximumLongitude")) { userMappingGrp.deleteKeyword("MaximumLongitude"); } } // Deal with user overrides entered in the GUI. Do this by changing the user's mapping group, which // will then overlay anything in the output mapping group. if(ui.WasEntered("MINLAT") && !ui.GetBoolean("MATCHMAP")) { userMappingGrp.addKeyword(PvlKeyword("MinimumLatitude", toString(ui.GetDouble("MINLAT"))), Pvl::Replace); } if(ui.WasEntered("MAXLAT") && !ui.GetBoolean("MATCHMAP")) { userMappingGrp.addKeyword(PvlKeyword("MaximumLatitude", toString(ui.GetDouble("MAXLAT"))), Pvl::Replace); } if(ui.WasEntered("MINLON") && !ui.GetBoolean("MATCHMAP")) { userMappingGrp.addKeyword(PvlKeyword("MinimumLongitude", toString(ui.GetDouble("MINLON"))), Pvl::Replace); } if(ui.WasEntered("MAXLON") && !ui.GetBoolean("MATCHMAP")) { userMappingGrp.addKeyword(PvlKeyword("MaximumLongitude", toString(ui.GetDouble("MAXLON"))), Pvl::Replace); } /** * If the user is changing from positive east to positive west, or vice-versa, the output minimum is really * the input maximum. However, the user mapping group must be left unaffected (an input minimum must be the * output minimum). To accomplish this, we swap the minimums/maximums in the output group ahead of time. This * causes the minimums and maximums to correlate to the output minimums and maximums. That way when we copy * the user mapping group into the output group a mimimum overrides a minimum and a maximum overrides a maximum. */ bool sameDirection = true; if(userMappingGrp.hasKeyword("LongitudeDirection")) { if(((QString)userMappingGrp["LongitudeDirection"]).compare(fromMappingGrp["LongitudeDirection"]) != 0) { sameDirection = false; } } // Since the out mapping group came from the from mapping group, which came from a valid cube, // we can assume both min/max lon exists if min longitude exists. if(!sameDirection && outMappingGrp.hasKeyword("MinimumLongitude")) { double minLon = outMappingGrp["MinimumLongitude"]; double maxLon = outMappingGrp["MaximumLongitude"]; outMappingGrp["MaximumLongitude"] = toString(minLon); outMappingGrp["MinimumLongitude"] = toString(maxLon); } if(ui.GetString("PIXRES").compare("FROM") == 0 && !ui.GetBoolean("MATCHMAP")) { // Resolution will be in fromMappingGrp and outMappingGrp at this time // delete from user mapping grp if(userMappingGrp.hasKeyword("Scale")) { userMappingGrp.deleteKeyword("Scale"); } if(userMappingGrp.hasKeyword("PixelResolution")) { userMappingGrp.deleteKeyword("PixelResolution"); } } else if(ui.GetString("PIXRES").compare("MAP") == 0 || ui.GetBoolean("MATCHMAP")) { // Resolution will be in userMappingGrp - delete all others if(outMappingGrp.hasKeyword("Scale")) { outMappingGrp.deleteKeyword("Scale"); } if(outMappingGrp.hasKeyword("PixelResolution")) { outMappingGrp.deleteKeyword("PixelResolution"); } if(fromMappingGrp.hasKeyword("Scale")); { fromMappingGrp.deleteKeyword("Scale"); } if(fromMappingGrp.hasKeyword("PixelResolution")) { fromMappingGrp.deleteKeyword("PixelResolution"); } } else if(ui.GetString("PIXRES").compare("MPP") == 0) { // Resolution specified - delete all and add to outMappingGrp if(outMappingGrp.hasKeyword("Scale")) { outMappingGrp.deleteKeyword("Scale"); } if(outMappingGrp.hasKeyword("PixelResolution")) { outMappingGrp.deleteKeyword("PixelResolution"); } if(fromMappingGrp.hasKeyword("Scale")) { fromMappingGrp.deleteKeyword("Scale"); } if(fromMappingGrp.hasKeyword("PixelResolution")) { fromMappingGrp.deleteKeyword("PixelResolution"); } if(userMappingGrp.hasKeyword("Scale")) { userMappingGrp.deleteKeyword("Scale"); } if(userMappingGrp.hasKeyword("PixelResolution")) { userMappingGrp.deleteKeyword("PixelResolution"); } outMappingGrp.addKeyword(PvlKeyword("PixelResolution", toString(ui.GetDouble("RESOLUTION")), "meters/pixel"), Pvl::Replace); } else if(ui.GetString("PIXRES").compare("PPD") == 0) { // Resolution specified - delete all and add to outMappingGrp if(outMappingGrp.hasKeyword("Scale")) { outMappingGrp.deleteKeyword("Scale"); } if(outMappingGrp.hasKeyword("PixelResolution")) { outMappingGrp.deleteKeyword("PixelResolution"); } if(fromMappingGrp.hasKeyword("Scale")) { fromMappingGrp.deleteKeyword("Scale"); } if(fromMappingGrp.hasKeyword("PixelResolution")) { fromMappingGrp.deleteKeyword("PixelResolution"); } if(userMappingGrp.hasKeyword("Scale")) { userMappingGrp.deleteKeyword("Scale"); } if(userMappingGrp.hasKeyword("PixelResolution")) { userMappingGrp.deleteKeyword("PixelResolution"); } outMappingGrp.addKeyword(PvlKeyword("Scale", toString(ui.GetDouble("RESOLUTION")), "pixels/degree"), Pvl::Replace); } // Rotation will NOT Propagate if(outMappingGrp.hasKeyword("Rotation")) { outMappingGrp.deleteKeyword("Rotation"); } /** * The user specified map template file overrides what ever is in the * cube's mapping group. */ for(int keyword = 0; keyword < userMappingGrp.keywords(); keyword ++) { outMappingGrp.addKeyword(userMappingGrp[keyword], Pvl::Replace); } /** * Now, we have to deal with unit conversions. We convert only if the following are true: * 1) We used values from the input cube * 2) The values are longitudes or latitudes * 3) The map file or user-specified information uses a different measurement system than * the input cube for said values. * * The data is corrected for: * 1) Positive east/positive west * 2) Longitude domain * 3) planetographic/planetocentric. */ // First, the longitude direction if(!sameDirection) { PvlGroup longitudes = inproj->MappingLongitudes(); for(int index = 0; index < longitudes.keywords(); index ++) { if(!userMappingGrp.hasKeyword(longitudes[index].name())) { // use the from domain because that's where our values are coming from if(((QString)userMappingGrp["LongitudeDirection"]).compare("PositiveEast") == 0) { outMappingGrp[longitudes[index].name()] = toString( TProjection::ToPositiveEast(outMappingGrp[longitudes[index].name()], outMappingGrp["LongitudeDomain"])); } else { outMappingGrp[longitudes[index].name()] = toString( TProjection::ToPositiveWest(outMappingGrp[longitudes[index].name()], outMappingGrp["LongitudeDomain"])); } } } } // Second, longitude domain if(userMappingGrp.hasKeyword("LongitudeDomain")) { // user set a new domain? if((int)userMappingGrp["LongitudeDomain"] != (int)fromMappingGrp["LongitudeDomain"]) { // new domain different? PvlGroup longitudes = inproj->MappingLongitudes(); for(int index = 0; index < longitudes.keywords(); index ++) { if(!userMappingGrp.hasKeyword(longitudes[index].name())) { if((int)userMappingGrp["LongitudeDomain"] == 180) { outMappingGrp[longitudes[index].name()] = toString( TProjection::To180Domain(outMappingGrp[longitudes[index].name()])); } else { outMappingGrp[longitudes[index].name()] = toString( TProjection::To360Domain(outMappingGrp[longitudes[index].name()])); } } } } } // Third, planetographic/planetocentric if(userMappingGrp.hasKeyword("LatitudeType")) { // user set a new domain? if(((QString)userMappingGrp["LatitudeType"]).compare(fromMappingGrp["LatitudeType"]) != 0) { // new lat type different? PvlGroup latitudes = inproj->MappingLatitudes(); for(int index = 0; index < latitudes.keywords(); index ++) { if(!userMappingGrp.hasKeyword(latitudes[index].name())) { if(((QString)userMappingGrp["LatitudeType"]).compare("Planetographic") == 0) { outMappingGrp[latitudes[index].name()] = toString(TProjection::ToPlanetographic( (double)fromMappingGrp[latitudes[index].name()], (double)fromMappingGrp["EquatorialRadius"], (double)fromMappingGrp["PolarRadius"])); } else { outMappingGrp[latitudes[index].name()] = toString(TProjection::ToPlanetocentric( (double)fromMappingGrp[latitudes[index].name()], (double)fromMappingGrp["EquatorialRadius"], (double)fromMappingGrp["PolarRadius"])); } } } } } // Try a couple equivalent longitudes to fix the ordering of min,max for border cases if ((double)outMappingGrp["MinimumLongitude"] >= (double)outMappingGrp["MaximumLongitude"]) { if ((QString)outMappingGrp["MinimumLongitude"] == "180.0" && (int)userMappingGrp["LongitudeDomain"] == 180) outMappingGrp["MinimumLongitude"] = "-180"; if ((QString)outMappingGrp["MaximumLongitude"] == "-180.0" && (int)userMappingGrp["LongitudeDomain"] == 180) outMappingGrp["MaximumLongitude"] = "180"; if ((QString)outMappingGrp["MinimumLongitude"] == "360.0" && (int)userMappingGrp["LongitudeDomain"] == 360) outMappingGrp["MinimumLongitude"] = "0"; if ((QString)outMappingGrp["MaximumLongitude"] == "0.0" && (int)userMappingGrp["LongitudeDomain"] == 360) outMappingGrp["MaximumLongitude"] = "360"; } // If MinLon/MaxLon out of order, we weren't able to calculate the correct values if((double)outMappingGrp["MinimumLongitude"] >= (double)outMappingGrp["MaximumLongitude"]) { if(!ui.WasEntered("MINLON") || !ui.WasEntered("MAXLON")) { QString msg = "Unable to determine the correct [MinimumLongitude,MaximumLongitude]."; msg += " Please specify these values in the [MINLON,MAXLON] parameters"; throw IException(IException::Unknown, msg, _FILEINFO_); } } int samples, lines; Pvl mapData; // Copy to preserve cube labels so we can match cube size if(userPvl.hasObject("IsisCube")) { mapData = userPvl; mapData.findObject("IsisCube").deleteGroup("Mapping"); mapData.findObject("IsisCube").addGroup(outMappingGrp); } else { mapData.addGroup(outMappingGrp); } // *NOTE: The UpperLeftX,UpperLeftY keywords will not be used in the CreateForCube // method, and they will instead be recalculated. This is correct. TProjection *outproj = (TProjection *) ProjectionFactory::CreateForCube(mapData, samples, lines, ui.GetBoolean("MATCHMAP")); // Set up the transform object which will simply map // output line/samps -> output lat/lons -> input line/samps Transform *transform = new map2map(icube->sampleCount(), icube->lineCount(), (TProjection *) icube->projection(), samples, lines, outproj, ui.GetBoolean("TRIM")); // Allocate the output cube and add the mapping labels Cube *ocube = p.SetOutputCube("TO", transform->OutputSamples(), transform->OutputLines(), icube->bandCount()); PvlGroup cleanOutGrp = outproj->Mapping(); // ProjectionFactory::CreateForCube updated mapData to have the correct // upperleftcornerx, upperleftcornery, scale and resolution. Use these // updated numbers. cleanOutGrp.addKeyword(mapData.findGroup("Mapping", Pvl::Traverse)["UpperLeftCornerX"], Pvl::Replace); cleanOutGrp.addKeyword(mapData.findGroup("Mapping", Pvl::Traverse)["UpperLeftCornerY"], Pvl::Replace); cleanOutGrp.addKeyword(mapData.findGroup("Mapping", Pvl::Traverse)["Scale"], Pvl::Replace); cleanOutGrp.addKeyword(mapData.findGroup("Mapping", Pvl::Traverse)["PixelResolution"], Pvl::Replace); ocube->putGroup(cleanOutGrp); // Set up the interpolator Interpolator *interp; if(ui.GetString("INTERP") == "NEARESTNEIGHBOR") { interp = new Interpolator(Interpolator::NearestNeighborType); } else if(ui.GetString("INTERP") == "BILINEAR") { interp = new Interpolator(Interpolator::BiLinearType); } else if(ui.GetString("INTERP") == "CUBICCONVOLUTION") { interp = new Interpolator(Interpolator::CubicConvolutionType); } else { QString msg = "Unknow value for INTERP [" + ui.GetString("INTERP") + "]"; throw IException(IException::Programmer, msg, _FILEINFO_); } // Warp the cube p.StartProcess(*transform, *interp); p.EndProcess(); Application::Log(cleanOutGrp); // Cleanup delete transform; delete interp; }
void IsisMain() { // We will be warping a cube ProcessRubberSheet p; // Get the map projection file provided by the user UserInterface &ui = Application::GetUserInterface(); Pvl userMap; userMap.read(ui.GetFileName("MAP")); PvlGroup &userGrp = userMap.findGroup("Mapping", Pvl::Traverse); // Open the input cube and get the camera icube = p.SetInputCube("FROM"); incam = icube->camera(); // Make sure it is not the sky if(incam->target()->isSky()) { QString msg = "The image [" + ui.GetFileName("FROM") + "] is targeting the sky, use skymap instead."; throw IException(IException::User, msg, _FILEINFO_); } // Get the mapping group from the camera Pvl camMap; incam->basicRingMapping(camMap); PvlGroup &camGrp = camMap.findGroup("Mapping"); // Make the target info match the user mapfile double minrad, maxrad, minaz, maxaz; incam->ringRange(minrad, maxrad, minaz, maxaz, userMap); camGrp.addKeyword(PvlKeyword("MinimumRingRadius", toString(minrad)), Pvl::Replace); camGrp.addKeyword(PvlKeyword("MaximumRingRadius", toString(maxrad)), Pvl::Replace); camGrp.addKeyword(PvlKeyword("MinimumRingLongitude", toString(minaz)), Pvl::Replace); camGrp.addKeyword(PvlKeyword("MaximumRingLongitude", toString(maxaz)), Pvl::Replace); // We want to delete the keywords we just added if the user wants the range // out of the mapfile, otherwise they will replace any keywords not in the // mapfile if (ui.GetString("DEFAULTRANGE") == "MAP" || ui.GetBoolean("MATCHMAP")) { camGrp.deleteKeyword("MinimumRingRadius"); camGrp.deleteKeyword("MaximumRingRadius"); camGrp.deleteKeyword("MinimumRingLongitude"); camGrp.deleteKeyword("MaximumRingLongitude"); } // Otherwise, remove the keywords from the map file so the camera keywords // will be propogated correctly else { while(userGrp.hasKeyword("MinimumRingRadius")) { userGrp.deleteKeyword("MinimumRingRadius"); } while(userGrp.hasKeyword("MinimumRingLongitude")) { userGrp.deleteKeyword("MinimumRingLongitude"); } while(userGrp.hasKeyword("MaximumRingRadius")) { userGrp.deleteKeyword("MaximumRingRadius"); } while(userGrp.hasKeyword("MaximumRingLongitude")) { userGrp.deleteKeyword("MaximumRingLongitude"); } } // If the user decided to enter a ground range then override if(ui.WasEntered("MINRINGLON")) { userGrp.addKeyword(PvlKeyword("MinimumRingLongitude", toString(ui.GetDouble("MINRINGLON"))), Pvl::Replace); } if(ui.WasEntered("MAXRINGLON")) { userGrp.addKeyword(PvlKeyword("MaximumRingLongitude", toString(ui.GetDouble("MAXRINGLON"))), Pvl::Replace); } if(ui.WasEntered("MINRINGRAD")) { userGrp.addKeyword(PvlKeyword("MinimumRingRadius", toString(ui.GetDouble("MINRINGRAD"))), Pvl::Replace); } if(ui.WasEntered("MAXRINGRAD")) { userGrp.addKeyword(PvlKeyword("MaximumRingRadius", toString(ui.GetDouble("MAXRINGRAD"))), Pvl::Replace); } // If they want the res. from the mapfile, delete it from the camera so // nothing gets overridden if(ui.GetString("PIXRES") == "MAP" || ui.GetBoolean("MATCHMAP")) { camGrp.deleteKeyword("PixelResolution"); } // Otherwise, delete any resolution keywords from the mapfile so the camera // info is propagated over else if(ui.GetString("PIXRES") == "CAMERA") { if(userGrp.hasKeyword("Scale")) { userGrp.deleteKeyword("Scale"); } if(userGrp.hasKeyword("PixelResolution")) { userGrp.deleteKeyword("PixelResolution"); } } // Copy any defaults that are not in the user map from the camera map file for(int k = 0; k < camGrp.keywords(); k++) { if(!userGrp.hasKeyword(camGrp[k].name())) { userGrp += camGrp[k]; } } // If the user is not matching the map file and entered a resolution, then reset this value if (!ui.GetBoolean("MATCHMAP")) { if(ui.GetString("PIXRES") == "MPP") { userGrp.addKeyword(PvlKeyword("PixelResolution", toString(ui.GetDouble("RESOLUTION"))), Pvl::Replace); if(userGrp.hasKeyword("Scale")) { userGrp.deleteKeyword("Scale"); } } else if(ui.GetString("PIXRES") == "PPD") { userGrp.addKeyword(PvlKeyword("Scale", toString(ui.GetDouble("RESOLUTION"))), Pvl::Replace); if(userGrp.hasKeyword("PixelResolution")) { userGrp.deleteKeyword("PixelResolution"); } } } // See if the user want us to handle the azimuth (ring longitude) seam // NOTE: For ringscam2map, if the user chooses to MATCHMAP, then we treat // the parameter ringlonseam as if it were set to "continue" (i.e. do nothing) if (!ui.GetBoolean("MATCHMAP")) { if((ui.GetString("DEFAULTRANGE") == "CAMERA" || ui.GetString("DEFAULTRANGE") == "MINIMIZE")) { //TODO This camera method will need attention for rings***** Solution: just call ringRange directly // if(incam->IntersectsLongitudeDomain(userMap)) { if (incam->ringRange(minrad, maxrad, minaz, maxaz, userMap)) { if(ui.GetString("RINGLONSEAM") == "AUTO") { if((int) userGrp["RingLongitudeDomain"] == 360) { userGrp.addKeyword(PvlKeyword("RingLongitudeDomain", "180"), Pvl::Replace); if(incam->ringRange(minrad, maxrad, minaz, maxaz, userMap)) { // Its looks like a global image so switch back to the // users preference userGrp.addKeyword(PvlKeyword("RingLongitudeDomain", "360"), Pvl::Replace); } } else { userGrp.addKeyword(PvlKeyword("RingLongitudeDomain", "360"), Pvl::Replace); if(incam->ringRange(minrad, maxrad, minaz, maxaz, userMap)) { // Its looks like a global image so switch back to the // users preference userGrp.addKeyword(PvlKeyword("RingLongitudeDomain", "180"), Pvl::Replace); } } // Make the target info match the new azimuth (ring longitude) domain Use radius for where // camera expects latitude & azimuth (ring longitude) where the camera expects longitude double minrad, maxrad, minaz, maxaz; incam->ringRange(minrad, maxrad, minaz, maxaz, userMap); if(!ui.WasEntered("MINRINGRAD")) { userGrp.addKeyword(PvlKeyword("MinimumRingRadius", toString(minrad)), Pvl::Replace); } if(!ui.WasEntered("MAXRINGRAD")) { userGrp.addKeyword(PvlKeyword("MaximumRingRadius", toString(maxrad)), Pvl::Replace); } if(!ui.WasEntered("MINRINGLON")) { userGrp.addKeyword(PvlKeyword("MinimumRingLongitude", toString(minaz)), Pvl::Replace); } if(!ui.WasEntered("MAXRINGLON")) { userGrp.addKeyword(PvlKeyword("MaximumRingLongitude", toString(maxaz)), Pvl::Replace); } } else if(ui.GetString("RINGLONSEAM") == "ERROR") { QString msg = "The image [" + ui.GetFileName("FROM") + "] crosses the " + "ring longitude seam"; throw IException(IException::User, msg, _FILEINFO_); } } } } // Use the updated label to create the output projection int samples, lines; RingPlaneProjection *outmap; bool trim; // Determine the image size if (ui.GetBoolean("MATCHMAP") || ui.GetString("DEFAULTRANGE") == "MAP") { // DEFAULTRANGE = MAP or MATCHMAP=true outmap = (RingPlaneProjection *) ProjectionFactory::RingsCreateForCube(userMap, samples, lines, ui.GetBoolean("MATCHMAP"));//??? if DR=map, this is always false??? trim = ui.GetBoolean("TRIM"); // trim allowed for defaultrange=map } else if(ui.GetString("DEFAULTRANGE") == "MINIMIZE") { outmap = (RingPlaneProjection *) ProjectionFactory::RingsCreateForCube(userMap, samples, lines, *incam); trim = false; } else { // if(ui.GetString("DEFAULTRANGE") == "CAMERA") { outmap = (RingPlaneProjection *) ProjectionFactory::RingsCreateForCube(userMap, samples, lines, false); trim = ui.GetBoolean("TRIM"); } // Output the mapping group used to the Gui session log PvlGroup cleanMapping = outmap->Mapping(); Application::GuiLog(cleanMapping); // Allocate the output cube and add the mapping labels Cube *ocube = p.SetOutputCube("TO", samples, lines, icube->bandCount()); ocube->putGroup(cleanMapping); // Set up the interpolator Interpolator *interp = NULL; if(ui.GetString("INTERP") == "NEARESTNEIGHBOR") { interp = new Interpolator(Interpolator::NearestNeighborType); } else if(ui.GetString("INTERP") == "BILINEAR") { interp = new Interpolator(Interpolator::BiLinearType); } else {//if(ui.GetString("INTERP") == "CUBICCONVOLUTION") { interp = new Interpolator(Interpolator::CubicConvolutionType); } // See if we need to deal with band dependent camera models if(!incam->IsBandIndependent()) { p.BandChange(BandChange); } // See if center of input image projects. If it does, force tile // containing this center to be processed in ProcessRubberSheet. // TODO: WEIRD ... why is this needed ... Talk to Tracie or JAA??? double centerSamp = icube->sampleCount() / 2.; double centerLine = icube->lineCount() / 2.; if(incam->SetImage(centerSamp, centerLine)) { // Force rings data into Isis by returning ring radius for latitude // and azimuth (ring longitude) for longitude if(outmap->SetUniversalGround(incam->LocalRadius().meters(), incam->UniversalLongitude())) { p.ForceTile(outmap->WorldX(), outmap->WorldY()); } } // Create an alpha cube group for the output cube if(!ocube->hasGroup("AlphaCube")) { PvlGroup alpha("AlphaCube"); alpha += PvlKeyword("AlphaSamples", toString(icube->sampleCount())); alpha += PvlKeyword("AlphaLines", toString(icube->lineCount())); alpha += PvlKeyword("AlphaStartingSample", toString(0.5)); alpha += PvlKeyword("AlphaStartingLine", toString(0.5)); alpha += PvlKeyword("AlphaEndingSample", toString(icube->sampleCount() + 0.5)); alpha += PvlKeyword("AlphaEndingLine", toString(icube->lineCount() + 0.5)); alpha += PvlKeyword("BetaSamples", toString(icube->sampleCount())); alpha += PvlKeyword("BetaLines", toString(icube->lineCount())); ocube->putGroup(alpha); } // We will need a transform class Transform *transform = 0; // Okay we need to decide how to apply the rubbersheeting for the transform // Does the user want to define how it is done? if (ui.GetString("WARPALGORITHM") == "FORWARDPATCH") { transform = new ringscam2mapForward(icube->sampleCount(), icube->lineCount(), incam, samples,lines, outmap, trim); // (Planar*)outmap, trim); int patchSize = ui.GetInteger("PATCHSIZE"); if (patchSize <= 1) patchSize = 3; // Make the patchsize reasonable p.setPatchParameters(1, 1, patchSize, patchSize, patchSize-1, patchSize-1); p.processPatchTransform(*transform, *interp); } else if (ui.GetString("WARPALGORITHM") == "REVERSEPATCH") { transform = new ringscam2mapReverse(icube->sampleCount(), icube->lineCount(), incam, samples,lines, outmap, trim); // (Planar*)outmap, trim); int patchSize = ui.GetInteger("PATCHSIZE"); int minPatchSize = 4; if (patchSize < minPatchSize) minPatchSize = patchSize; p.SetTiling(patchSize, minPatchSize); p.StartProcess(*transform, *interp); } // The user didn't want to override the program smarts. // Handle framing cameras. Always process using the backward // driven system (tfile). else if (incam->GetCameraType() == Camera::Framing) { transform = new ringscam2mapReverse(icube->sampleCount(), icube->lineCount(), incam, samples,lines, outmap, trim); // (Planar*)outmap, trim); p.SetTiling(4, 4); p.StartProcess(*transform, *interp); } // The user didn't want to override the program smarts. // Handle linescan cameras. Always process using the forward // driven patch option. Faster and we get better orthorectification // // TODO: For now use the default patch size. Need to modify // to determine patch size based on 1) if the limb is in the file // or 2) if the DTM is much coarser than the image else if (incam->GetCameraType() == Camera::LineScan) { transform = new ringscam2mapForward(icube->sampleCount(), icube->lineCount(), incam, samples,lines, outmap, trim); // (Planar*)outmap, trim); p.processPatchTransform(*transform, *interp); } // The user didn't want to override the program smarts. // Handle pushframe cameras. Always process using the forward driven patch // option. It is much faster than the tfile method. We will need to // determine patch sizes based on the size of the push frame. // // TODO: What if the user has run crop, enlarge, or shrink on the push // frame cube. Things probably won't work unless they do it just right // TODO: What about the THEMIS VIS Camera. Will tall narrow (128x4) patches // work okay? else if (incam->GetCameraType() == Camera::PushFrame) { transform = new ringscam2mapForward(icube->sampleCount(), icube->lineCount(), incam, samples,lines, outmap, trim); // (Planar*)outmap, trim); // Get the frame height PushFrameCameraDetectorMap *dmap = (PushFrameCameraDetectorMap *) incam->DetectorMap(); int frameSize = dmap->frameletHeight() / dmap->LineScaleFactor(); // Check for even/odd cube to determine starting line PvlGroup &instGrp = icube->label()->findGroup("Instrument", Pvl::Traverse); int startLine = 1; // Get the alpha cube group in case they cropped the image AlphaCube acube(*icube); double betaLine = acube.AlphaLine(1.0); if (fabs(betaLine - 1.0) > 0.0000000001) { if (fabs(betaLine - (int) betaLine) > 0.00001) { string msg = "Input file is a pushframe camera cropped at a "; msg += "fractional pixel. Can not project"; throw IException(IException::User, msg, _FILEINFO_); } int offset = (((int) (betaLine + 0.5)) - 1) % frameSize; startLine -= offset; } if (((QString)instGrp["Framelets"]).toUpper() == "EVEN") { startLine += frameSize; } p.setPatchParameters(1, startLine, 5, frameSize, 4, frameSize * 2); p.processPatchTransform(*transform, *interp); } // The user didn't want to override the program smarts. The other camera // types have not be analyized. This includes Radar and Point. Continue to // use the reverse geom option with the default tiling hints else { transform = new ringscam2mapReverse(icube->sampleCount(), icube->lineCount(), incam, samples,lines, outmap, trim); // (Planar*)outmap, trim); int tileStart, tileEnd; incam->GetGeometricTilingHint(tileStart, tileEnd); p.SetTiling(tileStart, tileEnd); p.StartProcess(*transform, *interp); } // Wrap up the warping process p.EndProcess(); // add mapping to print.prt Application::Log(cleanMapping); // Cleanup delete outmap; delete transform; delete interp; }
void IsisMain() { //Create a process to create the input cubes Process p; //Create the input cubes, matching sample/lines Cube *inCube = p.SetInputCube ("FROM"); Cube *latCube = p.SetInputCube("LATCUB", SpatialMatch); Cube *lonCube = p.SetInputCube("LONCUB", SpatialMatch); //A 1x1 brick to read in the latitude and longitude DN values from //the specified cubes Brick latBrick(1,1,1, latCube->PixelType()); Brick lonBrick(1,1,1, lonCube->PixelType()); UserInterface &ui = Application::GetUserInterface(); //Set the sample and line increments int sinc = (int)(inCube->Samples() * 0.10); if(ui.WasEntered("SINC")) { sinc = ui.GetInteger("SINC"); } int linc = (int)(inCube->Lines() * 0.10); if(ui.WasEntered("LINC")) { linc = ui.GetInteger("LINC"); } //Set the degree of the polynomial to use in our functions int degree = ui.GetInteger("DEGREE"); //We are using a polynomial with two variables PolynomialBivariate sampFunct(degree); PolynomialBivariate lineFunct(degree); //We will be solving the function using the least squares method LeastSquares sampSol(sampFunct); LeastSquares lineSol(lineFunct); //Setup the variables for solving the stereographic projection //x = cos(latitude) * sin(longitude - lon_center) //y = cos(lat_center) * sin(latitude) - sin(lat_center) * cos(latitude) * cos(longitude - lon_center) //Get the center lat and long from the input cubes double lat_center = latCube->Statistics()->Average() * PI/180.0; double lon_center = lonCube->Statistics()->Average() * PI/180.0; /** * Loop through lines and samples projecting the latitude and longitude at those * points to stereographic x and y and adding these points to the LeastSquares * matrix. */ for(int i = 1; i <= inCube->Lines(); i+= linc) { for(int j = 1; j <= inCube->Samples(); j+= sinc) { latBrick.SetBasePosition(j, i, 1); latCube->Read(latBrick); if(IsSpecial(latBrick.at(0))) continue; double lat = latBrick.at(0) * PI/180.0; lonBrick.SetBasePosition(j, i, 1); lonCube->Read(lonBrick); if(IsSpecial(lonBrick.at(0))) continue; double lon = lonBrick.at(0) * PI/180.0; //Project lat and lon to x and y using a stereographic projection double k = 2/(1 + sin(lat_center) * sin(lat) + cos(lat_center)*cos(lat)*cos(lon - lon_center)); double x = k * cos(lat) * sin(lon - lon_center); double y = k * (cos(lat_center) * sin(lat)) - (sin(lat_center) * cos(lat) * cos(lon - lon_center)); //Add x and y to the least squares matrix vector<double> data; data.push_back(x); data.push_back(y); sampSol.AddKnown(data, j); lineSol.AddKnown(data, i); //If the sample increment goes past the last sample in the line, we want to //always read the last sample.. if(j != inCube->Samples() && j + sinc > inCube->Samples()) { j = inCube->Samples() - sinc; } } //If the line increment goes past the last line in the cube, we want to //always read the last line.. if(i != inCube->Lines() && i + linc > inCube->Lines()) { i = inCube->Lines() - linc; } } //Solve the least squares functions using QR Decomposition sampSol.Solve(LeastSquares::QRD); lineSol.Solve(LeastSquares::QRD); //If the user wants to save the residuals to a file, create a file and write //the column titles to it. TextFile oFile; if(ui.WasEntered("RESIDUALS")) { oFile.Open(ui.GetFilename("RESIDUALS"), "overwrite"); oFile.PutLine("Sample,\tLine,\tX,\tY,\tSample Error,\tLine Error\n"); } //Gather the statistics for the residuals from the least squares solutions Statistics sampErr; Statistics lineErr; vector<double> sampResiduals = sampSol.Residuals(); vector<double> lineResiduals = lineSol.Residuals(); for(int i = 0; i < (int)sampResiduals.size(); i++) { sampErr.AddData(sampResiduals[i]); lineErr.AddData(lineResiduals[i]); } //If a residuals file was specified, write the previous data, and the errors to the file. if(ui.WasEntered("RESIDUALS")) { for(int i = 0; i < sampSol.Rows(); i++) { vector<double> data = sampSol.GetInput(i); iString tmp = ""; tmp += iString(sampSol.GetExpected(i)); tmp += ",\t"; tmp += iString(lineSol.GetExpected(i)); tmp += ",\t"; tmp += iString(data[0]); tmp += ",\t"; tmp += iString(data[1]); tmp += ",\t"; tmp += iString(sampResiduals[i]); tmp += ",\t"; tmp += iString(lineResiduals[i]); oFile.PutLine(tmp + "\n"); } } oFile.Close(); //Records the error to the log PvlGroup error( "Error" ); error += PvlKeyword( "Degree", degree ); error += PvlKeyword( "NumberOfPoints", (int)sampResiduals.size() ); error += PvlKeyword( "SampleMinimumError", sampErr.Minimum() ); error += PvlKeyword( "SampleAverageError", sampErr.Average() ); error += PvlKeyword( "SampleMaximumError", sampErr.Maximum() ); error += PvlKeyword( "SampleStdDeviationError", sampErr.StandardDeviation() ); error += PvlKeyword( "LineMinimumError", lineErr.Minimum() ); error += PvlKeyword( "LineAverageError", lineErr.Average() ); error += PvlKeyword( "LineMaximumError", lineErr.Maximum() ); error += PvlKeyword( "LineStdDeviationError", lineErr.StandardDeviation() ); Application::Log( error ); //Close the input cubes for cleanup p.EndProcess(); //If we want to warp the image, then continue, otherwise return if(!ui.GetBoolean("NOWARP")) { //Creates the mapping group Pvl mapFile; mapFile.Read(ui.GetFilename("MAP")); PvlGroup &mapGrp = mapFile.FindGroup("Mapping",Pvl::Traverse); //Reopen the lat and long cubes latCube = new Cube(); latCube->SetVirtualBands(ui.GetInputAttribute("LATCUB").Bands()); latCube->Open(ui.GetFilename("LATCUB")); lonCube = new Cube(); lonCube->SetVirtualBands(ui.GetInputAttribute("LONCUB").Bands()); lonCube->Open(ui.GetFilename("LONCUB")); PvlKeyword targetName; //If the user entered the target name if(ui.WasEntered("TARGET")) { targetName = PvlKeyword("TargetName", ui.GetString("TARGET")); } //Else read the target name from the input cube else { Pvl fromFile; fromFile.Read(ui.GetFilename("FROM")); targetName = fromFile.FindKeyword("TargetName", Pvl::Traverse); } mapGrp.AddKeyword(targetName, Pvl::Replace); PvlKeyword equRadius; PvlKeyword polRadius; //If the user entered the equatorial and polar radii if(ui.WasEntered("EQURADIUS") && ui.WasEntered("POLRADIUS")) { equRadius = PvlKeyword("EquatorialRadius", ui.GetDouble("EQURADIUS")); polRadius = PvlKeyword("PolarRadius", ui.GetDouble("POLRADIUS")); } //Else read them from the pck else { Filename pckFile("$base/kernels/pck/pck?????.tpc"); pckFile.HighestVersion(); string pckFilename = pckFile.Expanded(); furnsh_c(pckFilename.c_str()); string target = targetName[0]; SpiceInt code; SpiceBoolean found; bodn2c_c (target.c_str(), &code, &found); if (!found) { string msg = "Could not convert Target [" + target + "] to NAIF code"; throw Isis::iException::Message(Isis::iException::Io,msg,_FILEINFO_); } SpiceInt n; SpiceDouble radii[3]; bodvar_c(code,"RADII",&n,radii); equRadius = PvlKeyword("EquatorialRadius", radii[0] * 1000); polRadius = PvlKeyword("PolarRadius", radii[2] * 1000); } mapGrp.AddKeyword(equRadius, Pvl::Replace); mapGrp.AddKeyword(polRadius, Pvl::Replace); //If the latitude type is not in the mapping group, copy it from the input if(!mapGrp.HasKeyword("LatitudeType")) { if(ui.GetString("LATTYPE") == "PLANETOCENTRIC") { mapGrp.AddKeyword(PvlKeyword("LatitudeType","Planetocentric"), Pvl::Replace); } else { mapGrp.AddKeyword(PvlKeyword("LatitudeType","Planetographic"), Pvl::Replace); } } //If the longitude direction is not in the mapping group, copy it from the input if(!mapGrp.HasKeyword("LongitudeDirection")) { if(ui.GetString("LONDIR") == "POSITIVEEAST") { mapGrp.AddKeyword(PvlKeyword("LongitudeDirection","PositiveEast"), Pvl::Replace); } else { mapGrp.AddKeyword(PvlKeyword("LongitudeDirection","PositiveWest"), Pvl::Replace); } } //If the longitude domain is not in the mapping group, assume it is 360 if(!mapGrp.HasKeyword("LongitudeDomain")) { mapGrp.AddKeyword(PvlKeyword("LongitudeDomain","360"), Pvl::Replace); } //If the default range is to be computed, use the input lat/long cubes to determine the range if(ui.GetString("DEFAULTRANGE") == "COMPUTE") { //NOTE - When computing the min/max longitude this application does not account for the //longitude seam if it exists. Since the min/max are calculated from the statistics of //the input longitude cube and then converted to the mapping group's domain they may be //invalid for cubes containing the longitude seam. Statistics *latStats = latCube->Statistics(); Statistics *lonStats = lonCube->Statistics(); double minLat = latStats->Minimum(); double maxLat = latStats->Maximum(); bool isOcentric = ((std::string)mapGrp.FindKeyword("LatitudeType")) == "Planetocentric"; if(isOcentric) { if(ui.GetString("LATTYPE") != "PLANETOCENTRIC") { minLat = Projection::ToPlanetocentric(minLat, (double)equRadius, (double)polRadius); maxLat = Projection::ToPlanetocentric(maxLat, (double)equRadius, (double)polRadius); } } else { if(ui.GetString("LATTYPE") == "PLANETOCENTRIC") { minLat = Projection::ToPlanetographic(minLat, (double)equRadius, (double)polRadius); maxLat = Projection::ToPlanetographic(maxLat, (double)equRadius, (double)polRadius); } } int lonDomain = (int)mapGrp.FindKeyword("LongitudeDomain"); double minLon = lonDomain == 360 ? Projection::To360Domain(lonStats->Minimum()) : Projection::To180Domain(lonStats->Minimum()); double maxLon = lonDomain == 360 ? Projection::To360Domain(lonStats->Maximum()) : Projection::To180Domain(lonStats->Maximum()); bool isPosEast = ((std::string)mapGrp.FindKeyword("LongitudeDirection")) == "PositiveEast"; if(isPosEast) { if(ui.GetString("LONDIR") != "POSITIVEEAST") { minLon = Projection::ToPositiveEast(minLon, lonDomain); maxLon = Projection::ToPositiveEast(maxLon, lonDomain); } } else { if(ui.GetString("LONDIR") == "POSITIVEEAST") { minLon = Projection::ToPositiveWest(minLon, lonDomain); maxLon = Projection::ToPositiveWest(maxLon, lonDomain); } } if(minLon > maxLon) { double temp = minLon; minLon = maxLon; maxLon = temp; } mapGrp.AddKeyword(PvlKeyword("MinimumLatitude", minLat),Pvl::Replace); mapGrp.AddKeyword(PvlKeyword("MaximumLatitude", maxLat),Pvl::Replace); mapGrp.AddKeyword(PvlKeyword("MinimumLongitude", minLon),Pvl::Replace); mapGrp.AddKeyword(PvlKeyword("MaximumLongitude", maxLon),Pvl::Replace); } //If the user decided to enter a ground range then override if (ui.WasEntered("MINLAT")) { mapGrp.AddKeyword(PvlKeyword("MinimumLatitude", ui.GetDouble("MINLAT")),Pvl::Replace); } if (ui.WasEntered("MAXLAT")) { mapGrp.AddKeyword(PvlKeyword("MaximumLatitude", ui.GetDouble("MAXLAT")),Pvl::Replace); } if (ui.WasEntered("MINLON")) { mapGrp.AddKeyword(PvlKeyword("MinimumLongitude", ui.GetDouble("MINLON")),Pvl::Replace); } if (ui.WasEntered("MAXLON")) { mapGrp.AddKeyword(PvlKeyword("MaximumLongitude", ui.GetDouble("MAXLON")),Pvl::Replace); } //If the pixel resolution is to be computed, compute the pixels/degree from the input if (ui.GetString("PIXRES") == "COMPUTE") { latBrick.SetBasePosition(1,1,1); latCube->Read(latBrick); lonBrick.SetBasePosition(1,1,1); lonCube->Read(lonBrick); //Read the lat and long at the upper left corner double a = latBrick.at(0) * PI/180.0; double c = lonBrick.at(0) * PI/180.0; latBrick.SetBasePosition(latCube->Samples(),latCube->Lines(),1); latCube->Read(latBrick); lonBrick.SetBasePosition(lonCube->Samples(),lonCube->Lines(),1); lonCube->Read(lonBrick); //Read the lat and long at the lower right corner double b = latBrick.at(0) * PI/180.0; double d = lonBrick.at(0) * PI/180.0; //Determine the angle between the two points double angle = acos(cos(a) * cos(b) * cos(c - d) + sin(a) * sin(b)); //double angle = acos((cos(a1) * cos(b1) * cos(b2)) + (cos(a1) * sin(b1) * cos(a2) * sin(b2)) + (sin(a1) * sin(a2))); angle *= 180/PI; //Determine the number of pixels between the two points double pixels = sqrt(pow(latCube->Samples() -1.0, 2.0) + pow(latCube->Lines() -1.0, 2.0)); //Add the scale in pixels/degree to the mapping group mapGrp.AddKeyword(PvlKeyword("Scale", pixels/angle, "pixels/degree"), Pvl::Replace); if (mapGrp.HasKeyword("PixelResolution")) { mapGrp.DeleteKeyword("PixelResolution"); } } // If the user decided to enter a resolution then override if (ui.GetString("PIXRES") == "MPP") { mapGrp.AddKeyword(PvlKeyword("PixelResolution", ui.GetDouble("RESOLUTION"), "meters/pixel"), Pvl::Replace); if (mapGrp.HasKeyword("Scale")) { mapGrp.DeleteKeyword("Scale"); } } else if (ui.GetString("PIXRES") == "PPD") { mapGrp.AddKeyword(PvlKeyword("Scale", ui.GetDouble("RESOLUTION"), "pixels/degree"), Pvl::Replace); if (mapGrp.HasKeyword("PixelResolution")) { mapGrp.DeleteKeyword("PixelResolution"); } } //Create a projection using the map file we created int samples,lines; Projection *outmap = ProjectionFactory::CreateForCube(mapFile,samples,lines,false); //Write the map file to the log Application::GuiLog(mapGrp); //Create a process rubber sheet ProcessRubberSheet r; //Set the input cube inCube = r.SetInputCube("FROM"); double tolerance = ui.GetDouble("TOLERANCE") * outmap->Resolution(); //Create a new transform object Transform *transform = new nocam2map (sampSol, lineSol, outmap, latCube, lonCube, ui.GetString("LATTYPE") == "PLANETOCENTRIC", ui.GetString("LONDIR") == "POSITIVEEAST", tolerance, ui.GetInteger("ITERATIONS"), inCube->Samples(), inCube->Lines(), samples, lines); //Allocate the output cube and add the mapping labels Cube *oCube = r.SetOutputCube ("TO", transform->OutputSamples(), transform->OutputLines(), inCube->Bands()); oCube->PutGroup(mapGrp); //Determine which interpolation to use Interpolator *interp = NULL; if (ui.GetString("INTERP") == "NEARESTNEIGHBOR") { interp = new Interpolator(Interpolator::NearestNeighborType); } else if (ui.GetString("INTERP") == "BILINEAR") { interp = new Interpolator(Interpolator::BiLinearType); } else if (ui.GetString("INTERP") == "CUBICCONVOLUTION") { interp = new Interpolator(Interpolator::CubicConvolutionType); } //Warp the cube r.StartProcess(*transform, *interp); r.EndProcess(); // add mapping to print.prt PvlGroup mapping = outmap->Mapping(); Application::Log(mapping); //Clean up delete latCube; delete lonCube; delete outmap; delete transform; delete interp; } }