Unverified Commit 193f92fa authored by acpaquette's avatar acpaquette Committed by GitHub
Browse files

MSL Fixes/Landed Addition (#558)



* Some potential fixes for MSL

* Adjustment to signs in rotation matrix for MSM Cahvor

* Another incremental fix, with a temporary rover transform to help things along

* Added template property function for the final_inst_frame

* Updated frame chain with missing MSL frames

* Made the logic for adding the cahvor frame less round about

* Added MSL final_inst_frame property

* Tweak the frame id

Tweak the frame id

* Allowed cahvor drivers to produce local ISDs

* Fixed tests

* Removed unnecessary properties

* Added final_inst_frame test

---------

Co-authored-by: default avatarOleg Alexandrov <oleg.alexandrov@gmail.com>
parent 3ea165b1
Loading
Loading
Loading
Loading
+32 −5
Original line number Diff line number Diff line
@@ -386,6 +386,22 @@ class Cahvor():
        """
        raise NotImplementedError

    @property
    def final_inst_frame(self):
      """
      Defines the final frame before cahvor frame in the frame chain
      """
      raise NotImplementedError

    @property
    def sensor_position(self):
      positions, velocities, times = super().sensor_position
      positions += self.cahvor_camera_dict["C"]
      if self._props.get("landed", False):
        positions = np.array([[0, 0, 0]] * len(times))
        velocities = np.array([[0, 0, 0]] * len(times))
      return positions, velocities, times

    def compute_h_c(self):
        """
        Computes the h_c element of a cahvor model for the conversion
@@ -451,7 +467,10 @@ class Cahvor():
            v_s = self.compute_v_s()
            H_prime = (self.cahvor_camera_dict['H'] - h_c * self.cahvor_camera_dict['A'])/h_s
            V_prime = (self.cahvor_camera_dict['V'] - v_c * self.cahvor_camera_dict['A'])/v_s
            if self._props.get("landed", False):
              self._cahvor_rotation_matrix = np.array([H_prime, -V_prime, -self.cahvor_camera_dict['A']])
            else:
              self._cahvor_rotation_matrix = np.array([H_prime, V_prime, self.cahvor_camera_dict['A']])
        return self._cahvor_rotation_matrix

    @property
@@ -466,13 +485,17 @@ class Cahvor():
          A networkx frame chain object
        """
        if not hasattr(self, '_frame_chain'):
            self._frame_chain = FrameChain.from_spice(sensor_frame=self.spacecraft_id * 1000,
            self._frame_chain = FrameChain.from_spice(sensor_frame=self.final_inst_frame,
                                                      target_frame=self.target_frame_id,
                                                      center_ephemeris_time=self.center_ephemeris_time,
                                                      ephemeris_times=self.ephemeris_time,
                                                      nadir=False, exact_ck_times=False)
            cahvor_quats = Rotation.from_matrix(self.cahvor_rotation_matrix).as_quat()
            cahvor_rotation = ConstantRotation(cahvor_quats, self.spacecraft_id * 1000, self.sensor_frame_id)
            # If we are landed we only care about the final cahvor frame relative to the target
            if self._props.get("landed", False):
              cahvor_rotation = ConstantRotation(cahvor_quats, self.target_frame_id, self.sensor_frame_id)
            else:
              cahvor_rotation = ConstantRotation(cahvor_quats, self.final_inst_frame, self.sensor_frame_id)
            self._frame_chain.add_edge(rotation = cahvor_rotation)
        return self._frame_chain

@@ -487,7 +510,9 @@ class Cahvor():
        : float
          The detector center line/boresight center line
        """
        return self.compute_v_c()
        # Add here 0.5 for consistency with the CSM convention that the
        # upper-left image pixel is at (0.5, 0.5).
        return self.compute_v_c() + 0.5

    @property
    def detector_center_sample(self):
@@ -500,7 +525,9 @@ class Cahvor():
        : float
          The detector center sample/boresight center sample
        """
        return self.compute_h_c()
        # Add here 0.5 for consistency with the CSM convention that the
        # upper-left image pixel is at (0.5, 0.5).
        return self.compute_h_c() + 0.5

    @property
    def pixel_size(self):
+14 −18
Original line number Diff line number Diff line
@@ -69,6 +69,18 @@ class MslMastcamPds3NaifSpiceDriver(Cahvor, Framer, Pds3Label, NaifSpice, Cahvor
                self._cahvor_camera_params['R'] = np.array(camera_model_group["MODEL_COMPONENT_6"])
        return self._cahvor_camera_params

    @property
    def final_inst_frame(self):
        """
        Defines MSLs last naif frame before the cahvor model frame

        Returns
        -------
        : int
          Naif frame code for MSL_RSM_HEAD
        """
        return spice.bods2c("MSL_RSM_HEAD")

    @property
    def sensor_frame_id(self):
        """
@@ -96,7 +108,7 @@ class MslMastcamPds3NaifSpiceDriver(Cahvor, Framer, Pds3Label, NaifSpice, Cahvor
        : list<double>
          focal plane to detector lines
        """
        return [0, 1/self.pixel_size, 0]
        return [0, 0, 1/self.pixel_size]
    
    @property
    def focal2pixel_samples(self):
@@ -108,23 +120,7 @@ class MslMastcamPds3NaifSpiceDriver(Cahvor, Framer, Pds3Label, NaifSpice, Cahvor
        : list<double>
          focal plane to detector samples
        """
        return [0, 0, 1/self.pixel_size]

    @property
    def detector_center_line(self):
        return self.label["INSTRUMENT_STATE_PARMS"]["DETECTOR_LINES"]/2

    @property
    def detector_center_sample(self):
        return self.label["INSTRUMENT_STATE_PARMS"]["MSL:DETECTOR_SAMPLES"]/2

    @property
    def detector_start_line(self):
        return self.label["IMAGE_REQUEST_PARMS"]["FIRST_LINE"]

    @property
    def detector_start_sample(self):
        return self.label["IMAGE_REQUEST_PARMS"]["FIRST_LINE_SAMPLE"]
        return [0, -1/self.pixel_size, 0]

    @property
    def sensor_model_version(self):
+1 −1
Original line number Diff line number Diff line
@@ -118,7 +118,7 @@ def to_isd(driver):
    sensor_frame = driver.sensor_frame_id

    instrument_pointing = {}
    source_frame, destination_frame, time_dependent_sensor_frame = frame_chain.last_time_dependent_frame_between(1, sensor_frame)
    source_frame, destination_frame, _ = frame_chain.last_time_dependent_frame_between(1, sensor_frame)

    # Reverse the frame order because ISIS orders frames as
    # (destination, intermediate, ..., intermediate, source)
+18 −3
Original line number Diff line number Diff line
@@ -67,6 +67,12 @@ def main():
        action="store_true",
        help="Only use drivers that generate fresh spice data"
    )
    parser.add_argument(
        "-l", "--local",
        action="store_true",
        help="Generate local spice data, or image that is unaware of itself relative to "
             "target body. This is largely used for landed/rover data."
    )
    parser.add_argument(
        '--version',
        action='version',
@@ -97,7 +103,7 @@ def main():

    if len(args.input) == 1:
        try:
            file_to_isd(args.input[0], args.out, kernels=k, log_level=log_level, only_isis_spice=args.only_isis_spice, only_naif_spice=args.only_naif_spice)
            file_to_isd(args.input[0], args.out, kernels=k, log_level=log_level, only_isis_spice=args.only_isis_spice, only_naif_spice=args.only_naif_spice, local=args.local)
        except Exception as err:
            # Seriously, this just throws a generic Exception?
            sys.exit(f"File {args.input[0]}: {err}")
@@ -107,7 +113,11 @@ def main():
        ) as executor:
            futures = {
                executor.submit(
                    file_to_isd, f, **{"kernels": k, "log_level": log_level, "only_isis_spice": args.only_isis_spice, "only_naif_spice": args.only_naif_spice}
                    file_to_isd, f, **{"kernels": k, 
                                       "log_level": log_level, 
                                       "only_isis_spice": args.only_isis_spice, 
                                       "only_naif_spice": args.only_naif_spice,
                                       "local": args.local}
                ): f for f in args.input
            }
            for f in concurrent.futures.as_completed(futures):
@@ -127,7 +137,8 @@ def file_to_isd(
    kernels: list = None,
    log_level=logging.WARNING,
    only_isis_spice=False,
    only_naif_spice=False
    only_naif_spice=False,
    local=False
):
    """
    Returns nothing, but acts as a thin wrapper to take the *file* and generate
@@ -152,6 +163,10 @@ def file_to_isd(

    logger.info(f"Reading: {file}")
    props = {}

    if local:
        props['landed'] = local

    if kernels is not None:
        kernels = [str(PurePath(p)) for p in kernels]
        props["kernels"] = kernels
+9 −6
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ class test_cahvor_sensor(unittest.TestCase):
        self.driver.target_frame_id = 10014
        self.driver.center_ephemeris_time = 0
        self.driver.ephemeris_time = [0]
        self.driver._props = {}

    @patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
    def test_compute_functions(self, cahvor_camera_dict):
@@ -39,26 +40,28 @@ class test_cahvor_sensor(unittest.TestCase):
    def test_cahvor_model_elements(self, cahvor_camera_dict):
        cahvor_matrix = self.driver.cahvor_rotation_matrix
        np.testing.assert_allclose(cahvor_matrix, [[-0.82067034, -0.57129702, 0.01095033],
                                                   [-0.43920248, 0.6184257, -0.65165238],
                                                   [ 0.3655151, -0.5396012, -0.7584387 ]])
                                                   [0.43920248, -0.6184257, 0.65165238],
                                                   [ -0.3655151, 0.5396012, 0.7584387 ]])

    @patch('ale.transformation.FrameChain.from_spice', return_value=ale.transformation.FrameChain())
    @patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
    def test_cahvor_frame_chain(self, cahvor_camera_dict, from_spice):
    @patch("ale.base.type_sensor.Cahvor.final_inst_frame", new_callable=PropertyMock, return_value=-76000)
    def test_cahvor_frame_chain(self, final_inst_frame, cahvor_camera_dict, from_spice):
      frame_chain = self.driver.frame_chain
      assert len(frame_chain.nodes()) == 2
      assert -76000 in frame_chain.nodes()
      assert -76562 in frame_chain.nodes()
      from_spice.assert_called_with(center_ephemeris_time=0, ephemeris_times=[0], sensor_frame=-76000, target_frame=10014, nadir=False, exact_ck_times=False)
      np.testing.assert_allclose(frame_chain[-76562][-76000]['rotation'].quat, [-0.28255205, 0.8940826, -0.33309383, 0.09914206])
      print(frame_chain[-76562][-76000]['rotation'].quat[3])
      np.testing.assert_allclose(frame_chain[-76562][-76000]['rotation'].quat, [-0.09914206260782343, 0.3330938313054434, 0.8940825953723198, -0.28255205470925904])

    @patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
    def test_cahvor_detector_center_line(self, cahvor_camera_dict):
        np.testing.assert_almost_equal(self.driver.detector_center_line, 590.1933422831007)
        np.testing.assert_almost_equal(self.driver.detector_center_line, 590.6933422831007)
    
    @patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
    def test_cahvor_detector_center_sample(self, cahvor_camera_dict):
        np.testing.assert_almost_equal(self.driver.detector_center_sample, 673.4306859859296)
        np.testing.assert_almost_equal(self.driver.detector_center_sample, 673.9306859859296)

    @patch("ale.base.type_sensor.Cahvor.cahvor_camera_dict", new_callable=PropertyMock, return_value=cahvor_camera_dict())
    def test_cahvor_pixel_size(self, cahvor_camera_dict):
Loading