diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a9b4adc4..43e2e6cc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ release. ## [Unreleased] ### Added +- Re-enabled and fixed the TGO CaSSIS driver, which now emits the CaSSIS rational distortion. Validated against ISIS to within ~0.013 pixel across 130 framelets of two real stereo pairs. [#720](https://github.com/DOI-USGS/ale/pull/720) - `MroHiRisePds3LabelNaifSpiceDriver`, a PDS3 EDR label driver for HiRISE that generates an ISD directly from a raw EDR label without requiring an ISIS cube, paralleling the existing CTX PDS3 driver. [#702](https://github.com/DOI-USGS/ale/pull/702) - Added a catch to try correcting paths in metakernels (using spice_root) if they have been left as default. [#703](https://github.com/DOI-USGS/ale/pull/703) diff --git a/ale/base/type_distortion.py b/ale/base/type_distortion.py index e3f060955..eae92b02d 100644 --- a/ale/base/type_distortion.py +++ b/ale/base/type_distortion.py @@ -64,6 +64,34 @@ def usgscsm_distortion_model(self): """ return {"radial": {"coefficients": [0.0, 0.0, 0.0]}} + +class CassisDistortion(): + """ + Mix-in for the TGO CaSSIS rational (ratio-of-quadratics) distortion model + by Tulyakov/Ivanov (EPFL), the same model ISIS uses in TgoCassisDistortionMap. + With chi = [x^2, x*y, y^2, x, y, 1] the two directions are + corrected = (A1_corr.chi)/(A3_corr.chi), ... (distorted -> undistorted) + distorted = (A1_dist.chi)/(A3_dist.chi), ... (undistorted -> distorted) + each using three 6-vectors. The 36 coefficients live in the IK/IAK as + INS_OD_A{1,2,3}_{CORR,DIST}. + """ + + @property + def usgscsm_distortion_model(self): + """ + Returns + ------- + : dict + Dictionary with the 36 CaSSIS distortion coefficients, packed in the + order A1_corr, A2_corr, A3_corr, A1_dist, A2_dist, A3_dist (6 each), + matching the unpacking in USGSCSM Distortion.cpp. + """ + coefficients = [] + for vec in ["A1_CORR", "A2_CORR", "A3_CORR", "A1_DIST", "A2_DIST", "A3_DIST"]: + coefficients.extend(self.naif_keywords["INS{}_OD_{}".format(self.ikid, vec)]) + return {"cassis": {"coefficients": coefficients}} + + class KaguyaSeleneDistortion(): """ Mix-in for sensors on the Kaguya/Selene mission. diff --git a/ale/drivers/__init__.py b/ale/drivers/__init__.py index 9bab07bfb..1d18416cb 100644 --- a/ale/drivers/__init__.py +++ b/ale/drivers/__init__.py @@ -25,7 +25,7 @@ # Explicit list of disabled drivers -__disabled_drivers__ = ["tgo_drivers", "osirisrex_drivers"] +__disabled_drivers__ = ["osirisrex_drivers"] # dynamically load drivers __all__ = [os.path.splitext(os.path.basename(d))[0] for d in glob(os.path.join(os.path.dirname(__file__), '*_drivers.py'))] diff --git a/ale/drivers/tgo_drivers.py b/ale/drivers/tgo_drivers.py index 4ae25fa79..ebbc3a3fe 100644 --- a/ale/drivers/tgo_drivers.py +++ b/ale/drivers/tgo_drivers.py @@ -1,12 +1,15 @@ +import numpy as np +import scipy.constants +import spiceypy as spice from pyspiceql import pyspiceql from ale.base import Driver, WrongInstrumentException from ale.base.data_naif import NaifSpice from ale.base.label_isis import IsisLabel from ale.base.type_sensor import Framer -from ale.base.type_distortion import NoDistortion +from ale.base.type_distortion import CassisDistortion -class TGOCassisIsisLabelNaifSpiceDriver(Framer, IsisLabel, NaifSpice, NoDistortion, Driver): +class TGOCassisIsisLabelNaifSpiceDriver(Framer, IsisLabel, NaifSpice, CassisDistortion, Driver): """ Driver for reading TGO Cassis ISIS3 Labels. These are Labels that have been ingested into ISIS from PDS EDR images but have not been spiceinit'd yet. @@ -48,7 +51,7 @@ def ephemeris_start_time(self): ephemeris start time of the image. """ if not hasattr(self, "_ephemeris_start_time"): - self._ephemeris_start_time = pyspiceql.utcToEt(utc=self.utc_start_time.strftime("%Y-%m-%d %H:%M:%S.%f"), searchKernels=self.search_kernels, useWeb=self.use_web) + self._ephemeris_start_time = pyspiceql.utcToEt(utc=self.utc_start_time.strftime("%Y-%m-%d %H:%M:%S.%f"), searchKernels=self.search_kernels, useWeb=self.use_web)[0] return self._ephemeris_start_time @property @@ -68,3 +71,153 @@ def sensor_model_version(self): @property def sensor_name(self): return self.label['IsisCube']['Instrument']['SpacecraftName'] + + @property + def sample_summing(self): + """ + CaSSIS stores SummingMode as an enum (0 = 1x1, 1 = 2x2, 2 = 4x4), not as + the summing factor itself. ISIS converts it as summing = sumMode * 2, then + falls back to 1 when that is 0 (see TgoCassisCamera). Replicate that here, + otherwise the CSM detector summing becomes 0 and groundToImage diverges. + """ + sum_mode = self.label['IsisCube']['Instrument']['SummingMode'] + summing = sum_mode * 2 + if summing <= 0: + summing = 1 + return summing + + @property + def line_summing(self): + return self.sample_summing + + @property + def detector_center_sample(self): + """ + ISIS uses 0.5-based CCD coordinates (pixel centers at half integers), + so convert the IK boresight sample to the CSM 0-based convention by + subtracting 0.5, as the LRO, MRO, Dawn, MESSENGER, MEX, Kaguya and KPLO + drivers do. Without this the CSM look is offset from ISIS by half a pixel + in sample (and half in line), i.e. sqrt(0.5^2+0.5^2) ~ 0.707 px. + """ + return super().detector_center_sample - 0.5 + + @property + def detector_center_line(self): + """ + ISIS uses 0.5-based CCD coordinates; convert to the CSM 0-based + convention by subtracting 0.5 (see detector_center_sample). + """ + return super().detector_center_line - 0.5 + + @property + def sensor_position(self): + """ + CaSSIS sets LT_SURFACE_CORRECT with LIGHTTIME_CORRECTION=LT+S, so ISIS + applies the surface light-time correction. The shared + NaifSpice.sensor_position samples the target body at the raw ephemeris + time in that branch, but ISIS samples it at the surface-light-time + adjusted time (ephem - obs_tar_lt + radius_lt); the body moves along its + orbit during that interval, which otherwise leaves a constant + tens-of-meters camera-center bias versus ISIS. This override applies that + for CaSSIS only, so the shared path is unchanged for every other sensor. + It is the shared surface-light-time branch with the single change that the + body is sampled at adjusted_time. CaSSIS is a single-record framer, so + sampling the body at adjusted_time[0]..[-1] is exact. + """ + if not (self.correct_lt_to_surface + and self.light_time_correction.upper() == 'LT+S'): + return super().sensor_position + + if not hasattr(self, '_position'): + ephem = self.ephemeris_time + pos = [] + vel = [] + + target = self.spacecraft_name + observer = self.target_name + if self.swap_observer_target: + target = self.target_name + observer = self.spacecraft_name + + ephem_kwargs = {"startEt": ephem[0], + "stopEt": ephem[-1], + "numRecords": len(ephem), + "ckQualities": ["reconstructed"], + "spkQualities": ["reconstructed"], + "searchKernels": self.search_kernels, + "useWeb": self.use_web} + + obs_tars_kwargs = {**ephem_kwargs, + "target": target, + "observer": observer, + "frame": "J2000", + "abcorr": self.light_time_correction, + "mission": self.spiceql_mission} + ssb_obs_kwargs = {**ephem_kwargs, + "target": observer, + "observer": "SSB", + "frame": "J2000", + "abcorr": "NONE", + "mission": self.spiceql_mission} + + obs_tars = pyspiceql.getTargetStatesRanged(**obs_tars_kwargs)[0] + ssb_obs = pyspiceql.getTargetStatesRanged(**ssb_obs_kwargs)[0] + + obs_tar_lts = np.array(obs_tars)[:, -1] + ssb_obs_states = np.array(ssb_obs)[:, 0:6] + + radius_lt = (self.target_body_radii[2] + self.target_body_radii[0]) / 2 \ + / (scipy.constants.c / 1000.0) + adjusted_time = ephem - obs_tar_lts + radius_lt + + # The only change from the shared method: sample the target body at + # the surface-light-time adjusted time rather than the raw ephem time. + ssb_tars_kwargs = {**ephem_kwargs, + "target": target, + "observer": "SSB", + "frame": "J2000", + "abcorr": "NONE", + "mission": self.spiceql_mission} + ssb_tars_kwargs["startEt"] = adjusted_time[0] + ssb_tars_kwargs["stopEt"] = adjusted_time[-1] + ssb_tars = pyspiceql.getTargetStatesRanged(**ssb_tars_kwargs)[0] + ssb_tar_states = np.array(ssb_tars)[:, 0:6] + + _states = ssb_tar_states - ssb_obs_states + + reference_frame_id = pyspiceql.translateNameToCode(frame=self.reference_frame, + mission=self.spiceql_mission, + searchKernels=self.search_kernels, + useWeb=self.use_web)[0] + + function_args = {**ephem_kwargs, + "toFrame": reference_frame_id, + "refFrame": 1, + "mission": self.spiceql_mission} + function_args.pop("spkQualities") + rotations = pyspiceql.getTargetOrientationsRanged(**function_args)[0] + + states = [] + for i, rotation in enumerate(rotations): + quaternion = rotation[:4] + av = [0, 0, 0] + if len(rotation) > 4: + av = rotation[4:] + rotation_matrix = spice.q2m(quaternion) + matrix = spice.rav2xf(rotation_matrix, av) + rotated_state = spice.mxvg(matrix, _states[i]) + states.append(rotated_state) + + for state in states: + if self.swap_observer_target: + pos.append(-state[:3]) + vel.append(-state[3:]) + else: + pos.append(state[:3]) + vel.append(state[3:]) + + # SPICE works in km, so convert to m. + self._position = 1000 * np.asarray(pos) + self._velocity = 1000 * np.asarray(vel) + self._ephem = ephem + return self._position, self._velocity, self._ephem diff --git a/include/ale/Distortion.h b/include/ale/Distortion.h index 030a4e48d..706e66a4b 100644 --- a/include/ale/Distortion.h +++ b/include/ale/Distortion.h @@ -11,7 +11,8 @@ namespace ale { CAHVOR, LUNARORBITER, RADTAN, - KPLOSHADOWCAM + KPLOSHADOWCAM, + CASSIS }; } diff --git a/src/Util.cpp b/src/Util.cpp index 062148bfa..0ada13ce0 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -366,6 +366,8 @@ DistortionType getDistortionModel(json isd) { return DistortionType::RADTAN; } else if (distortion.compare("kplo_shadowcam") == 0) { return DistortionType::KPLOSHADOWCAM; + } else if (distortion.compare("cassis") == 0) { + return DistortionType::CASSIS; } } catch (...) { throw std::runtime_error("Could not parse the distortion model."); @@ -559,6 +561,18 @@ std::vector getDistortionCoeffs(json isd) { coefficients = std::vector(1, 0.0); } } break; + case DistortionType::CASSIS: { + try { + coefficients = isd.at("optical_distortion") + .at("cassis") + .at("coefficients") + .get>(); + return coefficients; + } catch (...) { + throw std::runtime_error( + "Could not parse the cassis distortion model coefficients."); + } + } break; } throw std::runtime_error( "Could not parse the distortion model coefficients."); diff --git a/tests/pytests/data/isds/cassis_isd.json b/tests/pytests/data/isds/cassis_isd.json index 82678f13f..bb075b2f3 100644 --- a/tests/pytests/data/isds/cassis_isd.json +++ b/tests/pytests/data/isds/cassis_isd.json @@ -1,127 +1,299 @@ { "isis_camera_version": 1, + "image_lines": 256, + "image_samples": 2048, + "name_platform": "TRACE GAS ORBITER", + "name_sensor": "TRACE GAS ORBITER", + "reference_height": { + "maxheight": 1000, + "minheight": -1000, + "unit": "m" + }, + "name_model": "USGS_ASTRO_FRAME_SENSOR_MODEL", + "center_ephemeris_time": 533471602.76595086, + "radii": { + "semimajor": 3396.19, + "semiminor": 3376.2, + "unit": "km" + }, + "body_rotation": { + "time_dependent_frames": [ + 10014, + 1 + ], + "ck_table_start_time": 533471602.76595086, + "ck_table_end_time": 533471602.76595086, + "ck_table_original_size": 1, + "ephemeris_times": [ + 533471602.76595086 + ], + "quaternions": [ + [ + 0.8436428688688923, + -0.08347233927208592, + -0.30718999789300266, + 0.43235793456071553 + ] + ], + "angular_velocities": [ + [ + 3.16231952619494e-05, + -2.881174578585201e-05, + 5.651672580233106e-05 + ] + ], + "reference_frame": 1 + }, + "instrument_pointing": { + "time_dependent_frames": [ + -143410, + -143400, + -143000, + 1 + ], + "ck_table_start_time": 533471602.76595086, + "ck_table_end_time": 533471602.76595086, + "ck_table_original_size": 1, + "ephemeris_times": [ + 533471602.76595086 + ], + "quaternions": [ + [ + 0.3885207820271846, + 0.5353466139887775, + 0.7498699301597845, + -0.012275694110543384 + ] + ], + "angular_velocities": [ + [ + -0.0005189739091085115, + -0.00027878212362186794, + 0.0002844628616547982 + ] + ], + "reference_frame": 1, + "constant_frames": [ + -143420, + -143410 + ], + "constant_rotation": [ + 0.0021039880161898283, + -0.0005089103275549567, + 0.9999976571196088, + 0.9848210365002222, + 0.17356149021484737, + -0.0019837290716918643, + -0.17356007404082358, + 0.9848229029245209, + 0.0008663568917524067 + ] + }, "naif_keywords": { - "BODY499_RADII": [3396.19, 3396.19, 3376.2], "BODY_FRAME_CODE": 10014, "BODY_CODE": 499, - "INS-143400_FOV_ANGLE_UNITS": "DEGREES", - "INS-143400_OD_A3_DIST": [ - 1.78250771483506e-05, - 4.24592743471094e-06, - 9.51220699036653e-06, - 0.00215158425420738, - -0.0066835595774833, - 0.573741540971609 - ], - "TKFRAME_-143400_ANGLES": [0.021, 0.12, -179.881], "FRAME_-143400_CENTER": -143.0, - "INS-143400_FOV_CLASS_SPEC": "ANGLES", - "INS-143400_OD_A3_CORR": [ - -3.13320167004204e-05, - -7.35655125749807e-06, - -1.57664245066771e-05, - 0.00373549465439151, - -0.0141671946930935, + "FRAME_-143400_CLASS": 4.0, + "FRAME_-143400_CLASS_ID": -143400.0, + "FRAME_-143400_NAME": "TGO_CASSIS_CRU", + "INS-143400_BORESIGHT": [ + 0.0, + 0.0, 1.0 ], - "INS-143400_FOV_REF_VECTOR": [1.0, 0.0, 0.0], - "TKFRAME_-143400_AXES": [1.0, 2.0, 3.0], - "TKFRAME_-143400_SPEC": "ANGLES", - "INS-143400_OD_A2_DIST": [ - -5.69725741015406e-05, - 0.00215155905679149, - -0.00716392991767185, - 0.000124152787728634, - 0.576459544392426, - 0.010576940564854 - ], - "FRAME_-143400_NAME": "TGO_CASSIS_CRU", - "INS-143400_BORESIGHT": [0.0, 0.0, 1.0], - "INS-143400_ITRANSL": [0.0, 0.0, 100.0], - "TKFRAME_-143400_RELATIVE": "TGO_SPACECRAFT", - "INS-143400_ITRANSS": [0.0, 100.0, 0.0], - "INS-143400_FOV_CROSS_ANGLE": 0.67057, "INS-143400_BORESIGHT_LINE": 1024.5, - "INS-143400_OD_A2_CORR": [ - 9.9842559363676e-05, - 0.00373543707958162, - -0.0133299918873929, - -0.000215311328389359, - 0.995296015537294, - -0.0183542717710778 - ], - "INS-143400_SWAP_OBSERVER_TARGET": "TRUE", - "FRAME_-143400_CLASS_ID": -143400.0, - "INS-143400_LIGHTTIME_CORRECTION": "LT+S", "INS-143400_BORESIGHT_SAMPLE": 1024.5, - "INS-143400_PIXEL_PITCH": 0.01, + "INS-143400_FILTER_LINES": 2048.0, + "INS-143400_FILTER_SAMPLES": 2048.0, + "INS-143400_FOCAL_LENGTH": 874.9, + "INS-143400_FOV_ANGLE_UNITS": "DEGREES", + "INS-143400_FOV_CLASS_SPEC": "ANGLES", + "INS-143400_FOV_CROSS_ANGLE": 0.67057, + "INS-143400_FOV_FRAME": "TGO_CASSIS_FSA", "INS-143400_FOV_REF_ANGLE": 0.67057, + "INS-143400_FOV_REF_VECTOR": [ + 1.0, + 0.0, + 0.0 + ], "INS-143400_FOV_SHAPE": "RECTANGLE", + "INS-143400_ITRANSL": [ + 0.0, + 0.0, + 100.0 + ], + "INS-143400_ITRANSS": [ + 0.0, + 100.0, + 0.0 + ], + "INS-143400_LIGHTTIME_CORRECTION": "LT+S", + "INS-143400_LT_SURFACE_CORRECT": true, + "INS-143400_NAME": "TGO_CASSIS", + "INS-143400_OD_A1_CORR": [ + 0.0037613053094826604, + -0.0134154156065812, + -1.8674952100723702e-05, + 1.00021352681836, + -0.00043236237170395304, + -0.000948065735350123 + ], "INS-143400_OD_A1_DIST": [ - 0.00213658795560622, + 0.0021365879556062197, -0.00711785765064197, 1.10355974742147e-05, 0.573607182625377, 0.000250884350194894, 0.000550623913037132 ], - "INS-143400_FILTER_SAMPLES": 2048.0, - "INS-143400_FILTER_LINES": 2048.0, - "INS-143400_OD_A1_CORR": [ - 0.00376130530948266, - -0.0134154156065812, - -1.86749521007237e-05, - 1.00021352681836, - -0.000432362371703953, - -0.000948065735350123 + "INS-143400_OD_A2_CORR": [ + 9.9842559363676e-05, + 0.00373543707958162, + -0.0133299918873929, + -0.000215311328389359, + 0.995296015537294, + -0.0183542717710778 ], - "INS-143400_FOV_FRAME": "TGO_CASSIS_FSA", - "INS-143400_LT_SURFACE_CORRECT": "TRUE", - "INS-143400_NAME": "TGO_CASSIS", - "INS-143400_FOCAL_LENGTH": 874.9, - "FRAME_-143400_CLASS": 4.0, - "INS-143400_TRANSX": [0.0, 0.01, 0.0], - "INS-143400_TRANSY": [0.0, 0.0, 0.01], + "INS-143400_OD_A2_DIST": [ + -5.6972574101540595e-05, + 0.00215155905679149, + -0.00716392991767185, + 0.000124152787728634, + 0.5764595443924261, + 0.010576940564854 + ], + "INS-143400_OD_A3_CORR": [ + -3.13320167004204e-05, + -7.3565512574980695e-06, + -1.57664245066771e-05, + 0.0037354946543915104, + -0.014167194693093499, + 1.0 + ], + "INS-143400_OD_A3_DIST": [ + 1.7825077148350597e-05, + 4.24592743471094e-06, + 9.51220699036653e-06, + 0.0021515842542073798, + -0.0066835595774833, + 0.573741540971609 + ], + "INS-143400_PIXEL_PITCH": 0.01, + "INS-143400_SWAP_OBSERVER_TARGET": true, + "INS-143400_TRANSX": [ + 0.0, + 0.01, + 0.0 + ], + "INS-143400_TRANSY": [ + 0.0, + 0.0, + 0.01 + ], + "TKFRAME_-143400_ANGLES": [ + 0.021, + 0.12, + -179.881 + ], + "TKFRAME_-143400_AXES": [ + 1.0, + 2.0, + 3.0 + ], + "TKFRAME_-143400_RELATIVE": "TGO_SPACECRAFT", + "TKFRAME_-143400_SPEC": "ANGLES", "TKFRAME_-143400_UNITS": "DEGREES", - "FRAME_1503499_CLASS": 4.0, - "FRAME_1500499_SEC_FRAME": "J2000", + "BODY499_GM": 42828.314, + "BODY499_PM": [ + 176.63, + 350.89198226, + 0.0 + ], + "BODY499_POLE_DEC": [ + 52.8865, + -0.0609, + 0.0 + ], + "BODY499_POLE_RA": [ + 317.68143, + -0.1061, + 0.0 + ], + "BODY499_RADII": [ + 3396.19, + 3396.19, + 3376.2 + ], + "FRAME_1500499_CENTER": 499.0, + "FRAME_1500499_CLASS": 5.0, + "FRAME_1500499_CLASS_ID": 1500499.0, "FRAME_1500499_DEF_STYLE": "PARAMETERIZED", - "TKFRAME_1503499_SPEC": "MATRIX", + "FRAME_1500499_FAMILY": "TWO-VECTOR", + "FRAME_1500499_NAME": "MME", "FRAME_1500499_PRI_AXIS": "Z", - "BODY499_POLE_DEC": [52.8865, -0.0609, 0.0], - "FRAME_1502499_PRI_TARGET": "SUN", - "FRAME_1502499_CENTER": 499.0, - "FRAME_1501499_ANGLE_1_COEFFS": [-47.68143, 3.362106117068471e-11], - "FRAME_1502499_FAMILY": "TWO-VECTOR", - "FRAME_1503499_NAME": "MME2000", - "FRAME_1502499_SEC_TARGET": "SUN", - "FRAME_1501499_EPOCH": 0.0, + "FRAME_1500499_PRI_FRAME": "IAU_MARS", + "FRAME_1500499_PRI_SPEC": "RECTANGULAR", + "FRAME_1500499_PRI_VECTOR": [ + 0.0, + 0.0, + 1.0 + ], + "FRAME_1500499_PRI_VECTOR_DEF": "CONSTANT", + "FRAME_1500499_RELATIVE": "J2000", + "FRAME_1500499_SEC_AXIS": "Y", + "FRAME_1500499_SEC_FRAME": "J2000", "FRAME_1500499_SEC_SPEC": "RECTANGULAR", + "FRAME_1500499_SEC_VECTOR": [ + 0.0, + 0.0, + 1.0 + ], + "FRAME_1500499_SEC_VECTOR_DEF": "CONSTANT", + "FRAME_1501499_ANGLE_1_COEFFS": [ + -47.68143, + 3.362106117068471e-11 + ], + "FRAME_1501499_ANGLE_2_COEFFS": [ + -37.1135, + -1.929804547874363e-11 + ], + "FRAME_1501499_ANGLE_3_COEFFS": 0.0, + "FRAME_1501499_AXES": [ + 3.0, + 1.0, + 3.0 + ], + "FRAME_1501499_CENTER": 499.0, + "FRAME_1501499_CLASS": 5.0, + "FRAME_1501499_CLASS_ID": 1501499.0, + "FRAME_1501499_DEF_STYLE": "PARAMETERIZED", + "FRAME_1501499_EPOCH": 0.0, + "FRAME_1501499_FAMILY": "EULER", + "FRAME_1501499_NAME": "MME_IAU2000", + "FRAME_1501499_RELATIVE": "J2000", "FRAME_1501499_UNITS": "DEGREES", - "FRAME_1500499_CENTER": 499.0, + "FRAME_1502499_CENTER": 499.0, + "FRAME_1502499_CLASS": 5.0, + "FRAME_1502499_CLASS_ID": 1502499.0, + "FRAME_1502499_DEF_STYLE": "PARAMETERIZED", + "FRAME_1502499_FAMILY": "TWO-VECTOR", + "FRAME_1502499_NAME": "MSO", + "FRAME_1502499_PRI_ABCORR": "NONE", + "FRAME_1502499_PRI_AXIS": "X", "FRAME_1502499_PRI_OBSERVER": "MARS", - "FRAME_1500499_FAMILY": "TWO-VECTOR", - "FRAME_1502499_SEC_VECTOR_DEF": "OBSERVER_TARGET_VELOCITY", - "FRAME_1500499_CLASS": 5.0, - "FRAME_1500499_SEC_VECTOR_DEF": "CONSTANT", - "FRAME_1501499_ANGLE_2_COEFFS": [-37.1135, -1.929804547874363e-11], - "FRAME_1500499_PRI_SPEC": "RECTANGULAR", - "BODY499_POLE_RA": [317.68143, -0.1061, 0.0], - "FRAME_1500499_PRI_VECTOR": [0.0, 0.0, 1.0], + "FRAME_1502499_PRI_TARGET": "SUN", "FRAME_1502499_PRI_VECTOR_DEF": "OBSERVER_TARGET_POSITION", - "TKFRAME_1503499_RELATIVE": "J2000", - "FRAME_1500499_NAME": "MME", - "FRAME_1500499_PRI_VECTOR_DEF": "CONSTANT", - "FRAME_1500499_SEC_VECTOR": [0.0, 0.0, 1.0], - "BODY499_PM": [176.63, 350.89198226, 0.0], + "FRAME_1502499_RELATIVE": "J2000", + "FRAME_1502499_SEC_ABCORR": "NONE", + "FRAME_1502499_SEC_AXIS": "Y", "FRAME_1502499_SEC_FRAME": "J2000", - "FRAME_1502499_PRI_ABCORR": "NONE", + "FRAME_1502499_SEC_OBSERVER": "MARS", + "FRAME_1502499_SEC_TARGET": "SUN", + "FRAME_1502499_SEC_VECTOR_DEF": "OBSERVER_TARGET_VELOCITY", "FRAME_1503499_CENTER": 499.0, - "FRAME_1502499_DEF_STYLE": "PARAMETERIZED", + "FRAME_1503499_CLASS": 4.0, "FRAME_1503499_CLASS_ID": 1503499.0, - "FRAME_1502499_RELATIVE": "J2000", - "FRAME_1502499_SEC_ABCORR": "NONE", - "FRAME_1501499_CLASS": 5.0, + "FRAME_1503499_NAME": "MME2000", "TKFRAME_1503499_MATRIX": [ 0.6732521982472339, 0.7394129276360181, @@ -133,130 +305,114 @@ -0.4062376142607541, 0.7974417791532832 ], - "FRAME_1502499_CLASS_ID": 1502499.0, - "FRAME_1501499_ANGLE_3_COEFFS": 0.0, - "FRAME_1501499_RELATIVE": "J2000", - "FRAME_1501499_NAME": "MME_IAU2000", - "FRAME_1501499_CENTER": 499.0, - "FRAME_1501499_CLASS_ID": 1501499.0, - "FRAME_1500499_RELATIVE": "J2000", - "FRAME_1501499_FAMILY": "EULER", - "FRAME_1502499_SEC_AXIS": "Y", - "FRAME_1502499_CLASS": 5.0, - "FRAME_1501499_DEF_STYLE": "PARAMETERIZED", - "FRAME_1500499_CLASS_ID": 1500499.0, - "FRAME_1500499_PRI_FRAME": "IAU_MARS", - "BODY499_GM": 42828.314, - "FRAME_1502499_PRI_AXIS": "X", - "FRAME_1502499_SEC_OBSERVER": "MARS", - "FRAME_1502499_NAME": "MSO", - "FRAME_1501499_AXES": [3.0, 1.0, 3.0], - "FRAME_1500499_SEC_AXIS": "Y" + "TKFRAME_1503499_RELATIVE": "J2000", + "TKFRAME_1503499_SPEC": "MATRIX" }, - "detector_sample_summing": 0, - "detector_line_summing": 0, - "focal_length_model": {"focal_length": 874.9}, - "detector_center": {"line": 1024.5, "sample": 1024.5}, - "starting_detector_line": 0, - "starting_detector_sample": 0, - "focal2pixel_lines": [0.0, 0.0, 100.0], - "focal2pixel_samples": [0.0, 100.0, 0.0], - "optical_distortion": {"radial": {"coefficients": [0.0, 0.0, 0.0]}}, - "image_lines": 256, - "image_samples": 2048, - "name_platform": "TRACE GAS ORBITER", - "name_sensor": "TRACE GAS ORBITER", - "reference_height": {"maxheight": 1000, "minheight": -1000, "unit": "m"}, - "name_model": "USGS_ASTRO_FRAME_SENSOR_MODEL", - "center_ephemeris_time": 533471602.76595086, - "radii": {"semimajor": 3396.19, "semiminor": 3376.2, "unit": "km"}, - "instrument_pointing": { - "time_dependent_frames": [-143410, -143400, -143000, 1], - "constant_frames" : [-143420, -143410], - "ck_table_start_time": 533471602.76595, - "ck_table_end_time": 533471602.76595, - "ck_table_original_size": 1, - "ephemeris_times": [533471602.76595], - "quaternions": [ - [-0.38852078202718, - -0.53534661398878, - -0.74986993015978, - 0.012275694110544 - ] - ], - "angular_velocities": [ - [-5.18973909108511e-04, - -2.78782123621868e-04, - 2.84462861654798e-04 - ] - ], - "reference_frame": 1, - "constant_rotation": [ - 0.0021039880161896, - -5.08910327554815e-04, - 0.99999765711961, - 0.98482103650022, - 0.17356149021485, - -0.0019837290716917, - -0.17356007404082, - 0.98482290292452, - 8.66356891752243e-04 - ] + "detector_sample_summing": 1, + "detector_line_summing": 1, + "focal_length_model": { + "focal_length": 874.9 }, - "body_rotation": { - "time_dependent_frames": [10014, 1], - "ck_table_start_time": 533471602.76595, - "ck_table_end_time": 533471602.76595, - "ck_table_original_size": 1, - "ephemeris_times": [533471602.76595], - "quaternions": [ - [-0.84364286886959, - 0.083472339272581, - 0.30718999789287, - -0.43235793455935 - ] - ], - "angular_velocities": [ - [3.16231952619494e-05, - -2.8811745785852e-05, - 5.6516725802331e-05 + "detector_center": { + "line": 1024.0, + "sample": 1024.0 + }, + "focal2pixel_lines": [ + 0.0, + 0.0, + 100.0 + ], + "focal2pixel_samples": [ + 0.0, + 100.0, + 0.0 + ], + "optical_distortion": { + "cassis": { + "coefficients": [ + 0.0037613053094826604, + -0.0134154156065812, + -1.8674952100723702e-05, + 1.00021352681836, + -0.00043236237170395304, + -0.000948065735350123, + 9.9842559363676e-05, + 0.00373543707958162, + -0.0133299918873929, + -0.000215311328389359, + 0.995296015537294, + -0.0183542717710778, + -3.13320167004204e-05, + -7.3565512574980695e-06, + -1.57664245066771e-05, + 0.0037354946543915104, + -0.014167194693093499, + 1.0, + 0.0021365879556062197, + -0.00711785765064197, + 1.10355974742147e-05, + 0.573607182625377, + 0.000250884350194894, + 0.000550623913037132, + -5.6972574101540595e-05, + 0.00215155905679149, + -0.00716392991767185, + 0.000124152787728634, + 0.5764595443924261, + 0.010576940564854, + 1.7825077148350597e-05, + 4.24592743471094e-06, + 9.51220699036653e-06, + 0.0021515842542073798, + -0.0066835595774833, + 0.573741540971609 ] - ], - "reference_frame": 1 + } }, + "starting_detector_line": 0, + "starting_detector_sample": 0, "instrument_position": { - "spk_table_start_time": 533471602.76595, - "spk_table_end_time": 533471602.76595, + "spk_table_start_time": 533471602.76595086, + "spk_table_end_time": 533471602.76595086, "spk_table_original_size": 1, - "ephemeris_times": [533471602.76595], + "ephemeris_times": [ + 533471602.76595086 + ], "positions": [ - [-2707.0266303122, - 2307.373459613, - 2074.8887762465] + [ + -2707.0266303122044, + 2307.373459609226, + 2074.8887762464587 + ] ], "velocities": [ - [-1.6101681507607, - -4.1440662687653, - -0.48379032129765 + [ + -1.6101681507606709, + -4.144066268765297, + -0.48379032129771266 ] ], "reference_frame": 1 }, "sun_position": { - "spk_table_start_time": 533471602.76595, - "spk_table_end_time": 533471602.76595, + "spk_table_start_time": 533471602.76595086, + "spk_table_end_time": 533471602.76595086, "spk_table_original_size": 1, - "ephemeris_times": [533471602.76595], + "ephemeris_times": [ + 533471602.76595086 + ], "positions": [ - [-206349081.4204, - 17157784.002827, - 13440194.20453 + [ + -206349081.42040092, + 17157784.00282702, + 13440194.204529513 ] ], "velocities": [ - [-3.3908615862221, - -23.832213042409, - -10.839726759899 + [ + -3.3908615862231235, + -23.832213042413816, + -10.839726759898475 ] ], "reference_frame": 1 diff --git a/tests/pytests/test_cassis_drivers.py b/tests/pytests/test_cassis_drivers.py index 1a13eff3b..7e23bd665 100644 --- a/tests/pytests/test_cassis_drivers.py +++ b/tests/pytests/test_cassis_drivers.py @@ -10,7 +10,9 @@ from ale.drivers.tgo_drivers import TGOCassisIsisLabelNaifSpiceDriver import unittest -from unittest.mock import patch, call +from unittest.mock import patch, call, PropertyMock + +from ale.base.data_naif import NaifSpice from conftest import get_image_label, get_image_kernels, convert_kernels, compare_dicts, get_isd @@ -23,10 +25,9 @@ def test_kernels(scope="module"): for kern in binary_kernels: os.remove(kern) -@pytest.mark.xfail def test_cassis_load(test_kernels): label_file = get_image_label("CAS-MCO-2016-11-26T22.32.14.582-RED-01000-B1", "isis") - isd_str = ale.loads(label_file, props={'kernels': test_kernels}) + isd_str = ale.loads(label_file, props={'kernels': test_kernels, 'attach_kernels': False}) isd_obj = json.loads(isd_str) compare_dict = get_isd('cassis') print(json.dumps(isd_obj, indent=2)) @@ -46,9 +47,28 @@ def test_instrument_id(self): assert self.driver.instrument_id == "TGO_CASSIS" def test_ephemeris_start_time(self): - with patch('ale.drivers.tgo_drivers.pyspiceql.utcToEt', side_effect=[12345]) as utcToEt: + with patch('ale.drivers.tgo_drivers.pyspiceql.utcToEt', side_effect=[(12345, {})]) as utcToEt: assert self.driver.ephemeris_start_time == 12345 calls = [call(utc='2016-11-26 22:32:14.582000', searchKernels=False, useWeb=False)] utcToEt.assert_has_calls(calls) assert utcToEt.call_count == 1 + def test_sample_summing(self): + # CaSSIS SummingMode is an enum (0 = 1x1); the label here is 0, so the + # summing factor must be 1, not the raw 0. + assert self.driver.sample_summing == 1 + + def test_line_summing(self): + assert self.driver.line_summing == 1 + + def test_detector_center_sample(self): + # ISIS 0.5-based -> CSM 0-based: subtract 0.5 from the IK boresight. + with patch.object(NaifSpice, 'detector_center_sample', + new_callable=PropertyMock, return_value=1024.5): + assert self.driver.detector_center_sample == 1024.0 + + def test_detector_center_line(self): + with patch.object(NaifSpice, 'detector_center_line', + new_callable=PropertyMock, return_value=1024.5): + assert self.driver.detector_center_line == 1024.0 +