From 28952e15d99322fa69aedce460fd98fb3b6b193e Mon Sep 17 00:00:00 2001 From: Oleg Alexandrov Date: Wed, 31 Jul 2024 11:25:08 -0700 Subject: [PATCH] A couple of bugfixes for sat_sim in special cases. --- docs/tools/sat_sim.rst | 12 ++-- src/asp/Camera/SatSim.cc | 36 ++++++++--- src/asp/Rig/rig_config.cc | 124 +++++++++++++++++++++++--------------- src/asp/Tools/sat_sim.cc | 27 ++++++--- 4 files changed, 124 insertions(+), 75 deletions(-) diff --git a/docs/tools/sat_sim.rst b/docs/tools/sat_sim.rst index 8f585118d..b2a060752 100644 --- a/docs/tools/sat_sim.rst +++ b/docs/tools/sat_sim.rst @@ -440,7 +440,7 @@ is defined in :numref:`rig_config`. As an example, consider the setup from :numref:`sat_sim_roll_pitch_yaw`. Add the rig option, and do not set the image size, focal length, and optical center on -the command line, as those are set by the rig. +the command line, as those are set by the rig configuration. The produced image and camera file names will include the sensor name, before the image/camera extension. Example:: @@ -483,7 +483,7 @@ using the option ``--save-as-csm``. :name: sat_sim_rig_illustration :alt: sat_sim_rig_illustration - Illustration of ``sat_sim`` creating a rig of 3 cameras. The resulting + Illustration of ``sat_sim`` creating a rig of 3 frame cameras. The resulting images have been mapprojected onto the ground. .. _sat_sim_time: @@ -497,7 +497,7 @@ starting ground position (``--first-ground-pos``), and satellite velocity recorded for each acquisition. The time is measured in seconds in double precision. Time is important for -pointing control with a rig. +modeling a rig, when this option is set to true even when not explicitly set. The time will be saved with the linescan camera metadata. It will be part of the name of the pinhole cameras and images (but not part of the name for linescan @@ -517,9 +517,9 @@ looks straight down at the starting point of the ground path. The default value is 10,000 seconds. For different orbits it is suggested to use a different value for -``--reference-time``. It is suggested to keep the reference time in the 10,000 - -100,000 range to ensure the produced times are positive but not too large, which -can result in loss of precision. +``--reference-time``, and also a different output prefix. It is suggested to +keep the reference time in the 1000 - 100,000 second range to ensure the produced +and nearby times are positive but not too large, which can result in loss of precision. Here we also assumed a rig was present (:numref:`sat_sim_rig`), with the sensor name being ``haz_cam``. diff --git a/src/asp/Camera/SatSim.cc b/src/asp/Camera/SatSim.cc index a5b63f45e..5e16cfa53 100644 --- a/src/asp/Camera/SatSim.cc +++ b/src/asp/Camera/SatSim.cc @@ -513,19 +513,30 @@ void adjustForFrameRate(SatSimOptions const& opt, // Number of cameras. Add 1 because we need to include the last camera. num_cameras = int(orbit_len / period) + 1; + // Cannot have one camera sample for linescan regardless of what the user wants + bool is_linescan = (opt.sensor_type.find("linescan") != std::string::npos); + // Sanity checks. It is fine to have one single camera, but that is not usual. if (num_cameras < 1) vw::vw_throw(vw::ArgumentErr() << "The number of cameras must be at least 1.\n"); - if (num_cameras == 1) - vw::vw_out(vw::WarningMessage) << "Warning: Creating only one camera sample.\n"; - - // Update the orbit length - orbit_len = period * (num_cameras - 1.0); + if (num_cameras == 1) { + if (is_linescan) { + vw::vw_out(vw::WarningMessage) << "For linescan cameras, must have at least " + "two camera samples. Adding a second one.\n"; + num_cameras = 2; + } else { + vw::vw_out(vw::WarningMessage) << "Creating only one frame camera sample.\n"; + } + } + // Update the orbit length. Handle gracefully the case of one camera. + orbit_len = period * std::max(num_cameras - 1.0, 1.0); + // Sanity check, important for the work below. It is fine for first and last // proj to be in the same location, but that is not usual. if (norm_2(last_proj - first_proj) < 1e-6) - vw::vw_out(vw::WarningMessage) << "Warning: The first and last camera positions are too close. Check your inputs.\n"; + vw::vw_out(vw::WarningMessage) << "Warning: The first and last camera positions are " + <<"too close. Check your inputs.\n"; // Travel along the orbit in very small increments. Return the last point // before exceeding the orbit length. @@ -562,8 +573,10 @@ void adjustForFrameRate(SatSimOptions const& opt, } - // Update the last orbit point, in projected coords - last_proj = out_proj; + // Update the last orbit point, in projected coords. Handle gracefully + // the case of one camera. + if (norm_2(last_proj - first_proj) > 1e-6) + last_proj = out_proj; } // Given the direction going from first_proj to last_proj, and knowing @@ -871,6 +884,9 @@ void genCamPoses(SatSimOptions & opt, int last_pos = opt.num_cameras; // stop before last if (opt.sensor_type == "linescan") { // Double the number of cameras, half of extra ones going beyond image lines + if (opt.num_cameras < 2) + vw::vw_throw(vw::ArgumentErr() << "For linescan cameras, must have at least " + "two camera samples.\n"); first_pos = -opt.num_cameras/2; last_pos = 2 * opt.num_cameras + first_pos; } @@ -1574,11 +1590,11 @@ void genRigCamerasImages(SatSimOptions & opt, local_opt.optical_center[1] = params.GetOpticalOffset()[1]; local_opt.image_size[0] = params.GetDistortedSize()[0]; local_opt.image_size[1] = params.GetDistortedSize()[1]; - local_opt.sensor_type = sensor_types[sensor_it]; + local_opt.sensor_type = sensor_types[sensor_index]; // The transform from the reference sensor to the current sensor Eigen::Affine3d ref2sensor = rig.ref_to_cam_trans[sensor_index]; - std::string suffix = "-" + sensor_names[sensor_it]; + std::string suffix = "-" + rig.cam_names[sensor_index]; bool have_rig = true; genCamerasImages(ortho_nodata_val, have_rig, sensor_index, dem, height_guess, ortho_georef, ortho, local_opt, rig, dem_georef, suffix); diff --git a/src/asp/Rig/rig_config.cc b/src/asp/Rig/rig_config.cc index a0a3086c6..718f78ee2 100644 --- a/src/asp/Rig/rig_config.cc +++ b/src/asp/Rig/rig_config.cc @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -37,13 +38,13 @@ namespace rig { void RigSet::validate() const { if (cam_set.empty()) - LOG(FATAL) << "Found an empty set of rigs.\n"; + vw::vw_throw(vw::IOErr() << "Found an empty set of rigs.\n"); size_t num_cams = 0; std::set all_cams; // checks for duplicates for (size_t rig_it = 0; rig_it < cam_set.size(); rig_it++) { if (cam_set[rig_it].empty()) - LOG(FATAL) << "Found a rig with no cams.\n"; + vw::vw_throw(vw::IOErr() << "Found a rig with no cams.\n"); num_cams += cam_set[rig_it].size(); for (size_t cam_it = 0; cam_it < cam_set[rig_it].size(); cam_it++) @@ -51,38 +52,45 @@ void RigSet::validate() const { } if (num_cams != all_cams.size() || num_cams != cam_names.size()) - LOG(FATAL) << "Found a duplicate sensor name in the rig set.\n"; + vw::vw_throw(vw::IOErr() + << "Found a duplicate sensor name in the rig set.\n"); if (num_cams != ref_to_cam_trans.size()) - LOG(FATAL) << "Number of sensors is not equal to number of ref-to-cam transforms.\n"; + vw::vw_throw(vw::IOErr() + << "Number of sensors is not equal to number of ref-to-cam transforms.\n"); if (num_cams != depth_to_image.size()) - LOG(FATAL) << "Number of sensors is not equal to number of depth-to-image transforms.\n"; + vw::vw_throw(vw::IOErr() + << "Number of sensors is not equal to number of depth-to-image " + << "transforms.\n"); if (num_cams != ref_to_cam_timestamp_offsets.size()) - LOG(FATAL) << "Number of sensors is not equal to number of ref-to-cam timestamp offsets.\n"; + vw::vw_throw(vw::IOErr() + << "Number of sensors is not equal to number of ref-to-cam " + << "timestamp offsets.\n"); if (num_cams != cam_params.size()) - LOG(FATAL) << "Number of sensors is not equal to number of camera models.\n"; + vw::vw_throw(vw::IOErr() + << "Number of sensors is not equal to number of camera models.\n"); for (size_t cam_it = 0; cam_it < cam_names.size(); cam_it++) { if (isRefSensor(cam_names[cam_it]) && ref_to_cam_timestamp_offsets[cam_it] != 0) - LOG(FATAL) << "The timestamp offsets for the reference sensors must be always 0.\n"; + vw::vw_throw(vw::IOErr() << "The timestamp offsets for the reference sensors must be always 0.\n"); } for (size_t i = 0; i < this->cam_params.size(); i++) { auto const& params = this->cam_params[i]; if (params.GetDistortedSize()[0] <= 1 || params.GetDistortedSize()[1] <= 1) - LOG(FATAL) << "The image size must be at least 2 x 2.\n"; + vw::vw_throw(vw::IOErr() << "The image size must be at least 2 x 2.\n"); if (params.GetFocalLength() == 0) - LOG(FATAL) << "The focal length must be non-zero.\n"; + vw::vw_throw(vw::IOErr() << "The focal length must be non-zero.\n"); } for (size_t cam_it = 0; cam_it < cam_names.size(); cam_it++) { if (isRefSensor(cam_names[cam_it]) && ref_to_cam_trans[cam_it].matrix() != Eigen::Affine3d::Identity().matrix()) - LOG(FATAL) - << "The transform from the reference sensor to itself must be the identity.\n"; + vw::vw_throw(vw::IOErr() + << "The transform from the reference sensor to itself must be the identity.\n"); } } @@ -105,7 +113,7 @@ bool RigSet::isRefSensor(int cam_id) const { // in cam_names. int RigSet::rigId(int cam_id) const { if (cam_id < 0 || cam_id >= cam_names.size()) - LOG(FATAL) << "Out of bounds sensor id.\n"; + vw::vw_throw(vw::IOErr() << "Out of bounds sensor id.\n"); std::string cam_name = cam_names[cam_id]; @@ -118,7 +126,8 @@ int RigSet::rigId(int cam_id) const { } // Should not arrive here - LOG(FATAL) << "Could not look up in the rig the sensor: " << cam_name << "\n"; + vw::vw_throw(vw::IOErr() + << "Could not look up in the rig the sensor: " << cam_name << "\n"); return -1; } @@ -133,8 +142,10 @@ std::string RigSet::refSensor(int cam_id) const { int RigSet::sensorIndex(std::string const& sensor_name) const { auto it = std::find(cam_names.begin(), cam_names.end(), sensor_name); if (it == cam_names.end()) - LOG(FATAL) << "Could not find sensor in rig. That is unexpected. Offending sensor: " - << sensor_name << ".\n"; + vw::vw_throw(vw::IOErr() + << "Could not find sensor in rig. That is unexpected. Offending sensor: " + << sensor_name << ".\n"); + return it - cam_names.begin(); } @@ -148,7 +159,7 @@ int RigSet::refSensorId(int cam_id) const { RigSet RigSet::subRig(int rig_id) const { if (rig_id < 0 || rig_id >= cam_set.size()) - LOG(FATAL) << "Out of range in rig set.\n"; + vw::vw_throw(vw::IOErr() << "Out of range in rig set.\n"); RigSet sub_rig; sub_rig.cam_set.push_back(cam_set[rig_id]); @@ -182,7 +193,8 @@ void writeRigConfig(std::string const& rig_config, bool model_rig, RigSet const& vw::vw_out() << "Writing: " << rig_config << "\n"; std::ofstream f; f.open(rig_config.c_str(), std::ios::binary | std::ios::out); - if (!f.is_open()) LOG(FATAL) << "Cannot open file for writing: " << rig_config << "\n"; + if (!f.is_open()) + vw::vw_throw(vw::IOErr() << "Cannot open file for writing: " << rig_config << "\n"); f.precision(17); for (size_t cam_type = 0; cam_type < R.cam_params.size(); cam_type++) { @@ -223,8 +235,9 @@ void writeRigConfig(std::string const& rig_config, bool model_rig, RigSet const& else if (D.size() > 5 && dist_type == camera::RPC_DISTORTION) f << "distortion_type: " << camera::RPC_DISTORTION_STR << "\n"; else - LOG(FATAL) << "Expecting 0, 1, 4, 5, or more distortion coefficients. Got: " - << D.size() << ".\n"; + vw::vw_throw(vw::IOErr() + << "Expecting 0, 1, 4, 5, or more distortion coefficients. Got: " + << D.size() << ".\n"); Eigen::Vector2i image_size = R.cam_params[cam_type].GetDistortedSize(); f << "image_size: " << image_size[0] << ' ' << image_size[1] << "\n"; @@ -298,19 +311,20 @@ void readConfigVals(std::ifstream & f, std::string const& tag, int desired_num_v if (token == "") continue; // likely just whitespace is present on the line - if (token != tag) throw std::runtime_error("Could not read value for: " + tag); + if (token != tag) + vw::vw_throw(vw::IOErr() << "Could not read value for: " << tag << "\n"); // Copy to Eigen::VectorXd vals.resize(local_vals.size()); for (int it = 0; it < vals.size(); it++) vals[it] = local_vals[it]; if (desired_num_vals >= 0 && vals.size() != desired_num_vals) - throw std::runtime_error("Read an incorrect number of values for: " + tag); + vw::vw_throw(vw::IOErr() << "Read an incorrect number of values for: " << tag << "\n"); return; } - throw std::runtime_error("Could not read value for: " + tag); + vw::vw_throw(vw::IOErr() << "Could not read value for: " << tag << "\n"); } // Read strings separated by spaces after given tag. Ignore comments, so any line starting @@ -335,15 +349,15 @@ void readConfigVals(std::ifstream & f, std::string const& tag, int desired_num_v } if (token != tag) - throw std::runtime_error("Could not read value for: " + tag); + vw::vw_throw(vw::IOErr() << "Could not read value for: " << tag << "\n"); if (desired_num_vals >= 0 && vals.size() != desired_num_vals) - throw std::runtime_error("Read an incorrect number of values for: " + tag); + vw::vw_throw(vw::IOErr() << "Read an incorrect number of values for: " << tag << "\n"); return; } - throw std::runtime_error("Could not read value for: " + tag); + vw::vw_throw(vw::IOErr() << "Could not read value for: " << tag << "\n"); } // Read a rig configuration. Check if the transforms among the sensors @@ -363,7 +377,7 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS std::ifstream f; f.open(rig_config.c_str(), std::ios::in); if (!f.is_open()) - LOG(FATAL) << "Cannot open file for reading: " << rig_config << "\n"; + vw::vw_throw(vw::IOErr() << "Cannot open file: " << rig_config << "\n"); int ref_sensor_count = 0; Eigen::VectorXd vals; @@ -387,7 +401,7 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS // Check for duplicate ref sensor if (ref_sensors.find(ref_sensor_name) != ref_sensors.end()) - LOG(FATAL) << "Found a duplicate reference sensor: " << ref_sensor_name << "\n"; + vw::vw_throw(vw::IOErr() << "Found a duplicate reference sensor: " << ref_sensor_name << "\n"); ref_sensors.insert(ref_sensor_name); } catch (...) { // No luck, go back to the line we tried to read, and continue reading other fields @@ -405,17 +419,19 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS // The first sensor must be the ref sensor if (is_ref_sensor && sensor_name != ref_sensor_name) - LOG(FATAL) << "The first sensor in each rig must be the reference sensor.\n"; + vw::vw_throw(vw::IOErr() + << "The first sensor in each rig must be the reference sensor.\n"); is_ref_sensor = false; // reset if (ref_sensor_name == "") - LOG(FATAL) << "The reference sensor name must be the first entry in " - << "the rig config file.\n"; + vw::vw_throw(vw::IOErr() + << "The reference sensor name must be the first entry in " + << "the rig config file.\n"); // Check for duplicate sensor if (sensors.find(sensor_name) != sensors.end()) - LOG(FATAL) << "Found a duplicate sensor: " << sensor_name << "\n"; + vw::vw_throw(vw::IOErr() << "Found a duplicate sensor: " << sensor_name << "\n"); sensors.insert(sensor_name); // It is convenient to store each sensor in cam_set, which has the rig set structure, @@ -435,13 +451,15 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS readConfigVals(f, "distortion_coeffs:", -1, vals); if (vals.size() != 0 && vals.size() != 1 && vals.size() != 4 && vals.size() < 5) - LOG(FATAL) << "Expecting 0, 1, 4, 5, or more distortion coefficients.\n"; + vw::vw_throw(vw::IOErr() + << "Expecting 0, 1, 4, 5, or more distortion coefficients.\n"); Eigen::VectorXd distortion = vals; readConfigVals(f, "distortion_type:", 1, str_vals); if (distortion.size() == 0 && str_vals[0] != camera::NO_DISTORTION_STR) - LOG(FATAL) << "When there are no distortion coefficients, distortion type must be: " - << camera::NO_DISTORTION_STR << "\n"; + vw::vw_throw(vw::IOErr() + << "When there are no distortion coefficients, distortion type must be: " + << camera::NO_DISTORTION_STR << "\n"); // For backward compatibility, accept camera::FISHEYE_DISTORTION_STR with 1 distortion coefficient, but use the FOV model if (distortion.size() == 1 && str_vals[0] == camera::FISHEYE_DISTORTION_STR) @@ -449,22 +467,26 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS // Validation if (distortion.size() == 1 && str_vals[0] != camera::FOV_DISTORTION_STR) - LOG(FATAL) << "When there is 1 distortion coefficient, distortion type must be: " - << camera::FOV_DISTORTION_STR << "\n"; + vw::vw_throw(vw::IOErr() + << "When there is 1 distortion coefficient, distortion type must be: " + << camera::FOV_DISTORTION_STR << "\n"); if (distortion.size() == 4 && str_vals[0] != camera::FISHEYE_DISTORTION_STR && str_vals[0] != camera::RADTAN_DISTORTION_STR) - LOG(FATAL) << "When there are 4 distortion coefficients, distortion type " - << "must be: " << camera::FISHEYE_DISTORTION_STR << " or " - << camera::RADTAN_DISTORTION_STR << "\n"; + vw::vw_throw(vw::IOErr() + << "When there are 4 distortion coefficients, distortion type " + << "must be: " << camera::FISHEYE_DISTORTION_STR << " or " + << camera::RADTAN_DISTORTION_STR << "\n"); if (distortion.size() == 5 && str_vals[0] != camera::RADTAN_DISTORTION_STR) - LOG(FATAL) << "When there are 5 distortion coefficient, distortion type must be: " - << camera::RADTAN_DISTORTION_STR << "\n"; + vw::vw_throw(vw::IOErr() + << "When there are 5 distortion coefficient, distortion type must be: " + << camera::RADTAN_DISTORTION_STR << "\n"); if ((distortion.size() > 5) && str_vals[0] != camera::RPC_DISTORTION_STR) - LOG(FATAL) << "When there are more than 5 distortion coefficients, distortion " - << "type must be: " << camera::RPC_DISTORTION_STR << "\n"; + vw::vw_throw(vw::IOErr() + << "When there are more than 5 distortion coefficients, distortion " + << "type must be: " << camera::RPC_DISTORTION_STR << "\n"); // Set distortion type based on str_vals[0] if (str_vals[0] == camera::NO_DISTORTION_STR) @@ -478,7 +500,7 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS else if (str_vals[0] == camera::RPC_DISTORTION_STR) dist_type = camera::RPC_DISTORTION; else - LOG(FATAL) << "Unknown distortion type: " << str_vals[0] << "\n"; + vw::vw_throw(vw::IOErr() << "Unknown distortion type: " << str_vals[0] << "\n"); readConfigVals(f, "image_size:", 2, vals); Eigen::Vector2i image_size(vals[0], vals[1]); @@ -502,19 +524,21 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS // Sanity check if (have_rig_transforms) { if (R.ref_to_cam_trans.back().matrix() == 0 * R.ref_to_cam_trans.back().matrix()) - LOG(FATAL) << "Failed to read valid transforms between the sensors on the rig\n"; + vw::vw_throw(vw::IOErr() + << "Failed to read valid transforms between the sensors on the rig\n"); // Put a warning if the transform is the identity matrix if the sensor // is not the ref one if (sensor_name != ref_sensor_name && R.ref_to_cam_trans.back().matrix() == Eigen::Affine3d::Identity().matrix()) - LOG(WARNING) << "A non-reference sensor on the rig as the identity as the " - << "transform from the reference sensor. Sensor name: " << sensor_name << "\n"; + LOG(WARNING) << "A non-reference sensor on the rig has the identity as the " + << "sensor transform. Sensor name: " << sensor_name << "\n"; // The determinant of the transform must be 1. double scale = pow(R.ref_to_cam_trans.back().linear().determinant(), 1.0 / 3.0); if (std::abs(scale - 1.0) > 1e-6) - LOG(FATAL) << "The determinant of the ref-to-sensor transform must be 1.\n"; + vw::vw_throw(vw::IOErr() + << "The determinant of the ref-to-sensor transform must be 1.\n"); } readConfigVals(f, "depth_to_image_transform:", 12, vals); @@ -526,7 +550,7 @@ void readRigConfig(std::string const& rig_config, bool have_rig_transforms, RigS } } catch(std::exception const& e) { - LOG(FATAL) << e.what() << "\n"; + vw::vw_throw(vw::IOErr() << e.what() << "\n"); } R.validate(); diff --git a/src/asp/Tools/sat_sim.cc b/src/asp/Tools/sat_sim.cc index a3ac8fe86..a9c59207c 100644 --- a/src/asp/Tools/sat_sim.cc +++ b/src/asp/Tools/sat_sim.cc @@ -179,11 +179,21 @@ void handle_arguments(int argc, char *argv[], asp::SatSimOptions& opt, positional, positional_desc, usage, allow_unregistered, unregistered); - if (opt.dem_file == "" || opt.ortho_file == "") - vw::vw_throw(vw::ArgumentErr() << "Missing input DEM and/or ortho image.\n"); if (opt.out_prefix == "") vw::vw_throw(vw::ArgumentErr() << "Missing output prefix.\n"); + // Create the output directory based on the output prefix + vw::create_out_dir(opt.out_prefix); + + // Turn on logging to file + asp::log_to_file(argc, argv, "", opt.out_prefix); + + // Some functions use google logging + google::InitGoogleLogging(argv[0]); + + if (opt.dem_file == "" || opt.ortho_file == "") + vw::vw_throw(vw::ArgumentErr() << "Missing input DEM and/or ortho image.\n"); + bool have_rig = !opt.rig_config.empty(); if (have_rig && opt.camera_list != "") vw::vw_throw(vw::ArgumentErr() @@ -194,7 +204,12 @@ void handle_arguments(int argc, char *argv[], asp::SatSimOptions& opt, !vm["optical-center"].defaulted())) vw::vw_throw(vw::ArgumentErr() << "Cannot specify image size, focal length, or " "optical center when using a rig. Those are set in the rig configuration file.\n"); - + + if (have_rig && !opt.model_time) { + opt.model_time = true; + vw::vw_out() << "Will model time as a rig was specified.\n"; + } + if (!have_rig) { if (std::isnan(opt.image_size[0]) || std::isnan(opt.image_size[1])) vw::vw_throw(vw::ArgumentErr() << "The image size must be specified.\n"); @@ -399,12 +414,6 @@ void handle_arguments(int argc, char *argv[], asp::SatSimOptions& opt, vw::vw_throw(vw::ArgumentErr() << "The reference time is not positive or is too large. " << "This can cause numerical issues.\n"); - // Create the output directory based on the output prefix - vw::create_out_dir(opt.out_prefix); - - // Turn on logging to file - asp::log_to_file(argc, argv, "", opt.out_prefix); - if (have_rig) { // Read the rig configuration bool have_rig_transforms = true; // dictated by the api