Commit bdbf918d authored by acpaquette's avatar acpaquette Committed by Jesse Mapel
Browse files

Frame Chain (#274)

* Adds voyager driver and associated tests

* Dictionary diff changes and isis_formatter updates

* Removed unnecessary imports from MRO driver

* Removed voyager playground notebook

* Removed comments on test data.

* Added comments to isis_formatter

* Updated tests based on changes to the isis_formatter

* Updated sun_position again for light time correction

* Updated frame chain, tests, and other drivers

* Cleaned up some logic

* Updated mro test with new frame chain output

* Test updates to mostly quaternions and moves frame_trace into transformation.py

* Added USGS channel to the environment file

* Adds Spiceypy outside of the environment.yml

* Updated python version

* Reverted travis and environment files

* Added back spiceypy to environment file

* Moved channels and updated version

* Install python into the base env

* More messing with travis

* Travis syntax fix

* Removed usgs channel

* Readded usgs channel and fixed networkx name

* Updated kaguya and mdis tests and the data_naif sensor_position target swapping
parent 6d3f2b73
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -40,7 +40,8 @@ install:
  - bash miniconda.sh -b -p $HOME/miniconda
  - export PATH="$HOME/miniconda/bin:$PATH"
  - conda config --set always_yes yes
  - conda env create -f environment.yml -n ale
  - conda env create -n ale python=3.7.3
  - conda env update -f environment.yml -n ale
  - source activate ale
  - conda install pytest

+11 −8
Original line number Diff line number Diff line
@@ -322,12 +322,12 @@ class NaifSpice():
            pos = []
            vel = []

            target = self.target_name
            observer = self.spacecraft_name
            # Check for ISIS flag to fix target and observer swapping
            if self.swap_observer_target:
            target = self.spacecraft_name
            observer = self.target_name
            # Check for ISIS flag to fix target and observer swapping
            if self.swap_observer_target:
                target = self.target_name
                observer = self.spacecraft_name

            for time in ephem:
                # spkezr returns a vector from the observer's location to the aberration-corrected
@@ -339,11 +339,11 @@ class NaifSpice():
                                        self.light_time_correction,
                                        observer)
                if self.swap_observer_target:
                    pos.append(state[:3])
                    vel.append(state[3:])
                else:
                    pos.append(-state[:3])
                    vel.append(-state[3:])
                else:
                    pos.append(state[:3])
                    vel.append(state[3:])

            # By default, SPICE works in km, so convert to m
            self._position = [p * 1000 for p in pos]
@@ -353,7 +353,10 @@ class NaifSpice():
    @property
    def frame_chain(self):
        if not hasattr(self, '_frame_chain'):
            self._frame_chain = FrameChain.from_spice(frame_changes = [(1, self.sensor_frame_id), (1, self.target_frame_id)], ephemeris_time=self.ephemeris_time)
            self._frame_chain = FrameChain.from_spice(sensor_frame=self.sensor_frame_id,
                                                      target_frame=self.target_frame_id,
                                                      center_ephemeris_time=self.center_ephemeris_time,
                                                      ephemeris_times=self.ephemeris_time)
        return self._frame_chain

    @property
+0 −11
Original line number Diff line number Diff line
@@ -212,17 +212,6 @@ class CassiniIssPds3LabelNaifSpiceDriver(Framer, Pds3Label, NaifSpice, RadialDis
        """
        return 1

    @property
    def frame_chain(self):
        if not hasattr(self, '_frame_chain'):
            if self.instrument_id == 'CASSINI_ISS_NAC':
                self._frame_chain = FrameChain.from_spice(frame_changes = [(1, self._original_naif_sensor_frame_id), (1, self.target_frame_id)], ephemeris_time=self.ephemeris_time)
                rot_180 = Rotation.from_euler('z', 180, degrees=True)
                self._frame_chain.add_edge(self._original_naif_sensor_frame_id, self.sensor_frame_id, ConstantRotation(rot_180.as_quat(), self._original_naif_sensor_frame_id, self.sensor_frame_id))
            elif self.instrument_id == "CASSINI_ISS_WAC":
                self._frame_chain =  super(CassiniIssPds3LabelNaifSpiceDriver, self).frame_chain
        return self._frame_chain

    @property
    def focal_length(self):
        """
+23 −25
Original line number Diff line number Diff line
@@ -29,29 +29,26 @@ def to_isis(driver):
    target_frame = driver.target_frame_id

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

    if source_frame != 1:
    # Reverse the frame order because ISIS orders frames as
    # (destination, intermediate, ..., intermediate, source)
        instrument_pointing['TimeDependentFrames'] = shortest_path(frame_chain, source_frame, 1)
        time_dependent_rotation = frame_chain.compute_rotation(1, source_frame)
    instrument_pointing['TimeDependentFrames'] = shortest_path(frame_chain, destination_frame, 1)
    time_dependent_rotation = frame_chain.compute_rotation(1, destination_frame)
    instrument_pointing['CkTableStartTime'] = time_dependent_rotation.times[0]
    instrument_pointing['CkTableEndTime'] = time_dependent_rotation.times[-1]
    instrument_pointing['CkTableOriginalSize'] = len(time_dependent_rotation.times)
    instrument_pointing['EphemerisTimes'] = time_dependent_rotation.times
    instrument_pointing['Quaternions'] = time_dependent_rotation.quats[:, [3, 0, 1, 2]]

    if source_frame != sensor_frame:
    # Reverse the frame order because ISIS orders frames as
    # (destination, intermediate, ..., intermediate, source)
        instrument_pointing['ConstantFrames'] = shortest_path(frame_chain, sensor_frame, source_frame)
        constant_rotation = frame_chain.compute_rotation(source_frame, sensor_frame)
        instrument_pointing['ConstantRotation'] = constant_rotation.rotation_matrix()
    instrument_pointing['ConstantFrames'] = shortest_path(frame_chain, sensor_frame, destination_frame)
    constant_rotation = frame_chain.compute_rotation(destination_frame, sensor_frame)
    instrument_pointing['ConstantRotation'] = constant_rotation.rotation_matrix().flatten()
    meta_data['InstrumentPointing'] = instrument_pointing

    body_rotation = {}
    target_frame = driver.target_frame_id
    source_frame, destination_frame, time_dependent_target_frame = frame_chain.last_time_dependent_frame_between(target_frame, 1)

    if source_frame != 1:
@@ -70,7 +67,8 @@ def to_isis(driver):
        # (destination, intermediate, ..., intermediate, source)
        body_rotation['ConstantFrames'] = shortest_path(frame_chain, target_frame, source_frame)
        constant_rotation = frame_chain.compute_rotation(source_frame, target_frame)
        body_rotation['ConstantRotation'] = constant_rotation.rotation_matrix()
        body_rotation['ConstantRotation'] = constant_rotation.rotation_matrix().flatten()

    meta_data['BodyRotation'] = body_rotation

    j2000_rotation = frame_chain.compute_rotation(target_frame, 1)
@@ -82,9 +80,9 @@ def to_isis(driver):
    instrument_position['SpkTableOriginalSize'] = len(times)
    instrument_position['EphemerisTimes'] = times
    # Rotate positions and velocities into J2000 then scale into kilometers
    positions = j2000_rotation._rots.apply(positions) * 1/1000
    velocities = j2000_rotation.rotate_velocity_at(positions, velocities, times)/1000
    positions = j2000_rotation.apply_at(positions, times)/1000
    instrument_position['Positions'] = positions
    velocities = j2000_rotation._rots.apply(velocities) * 1/1000
    instrument_position['Velocities'] = velocities
    meta_data['InstrumentPosition'] = instrument_position

@@ -95,9 +93,9 @@ def to_isis(driver):
    sun_position['SpkTableOriginalSize'] = len(times)
    sun_position['EphemerisTimes'] = times
    # Rotate positions and velocities into J2000 then scale into kilometers
    positions = j2000_rotation._rots.apply(positions) * 1/1000
    velocities = j2000_rotation.rotate_velocity_at(positions, velocities, times)/1000
    positions = j2000_rotation.apply_at(positions, times)/1000
    sun_position['Positions'] = positions
    velocities = j2000_rotation._rots.apply(velocities) * 1/1000
    sun_position['Velocities'] = velocities
    meta_data['SunPosition'] = sun_position

+95 −16
Original line number Diff line number Diff line
@@ -89,40 +89,119 @@ class FrameChain(nx.DiGraph):
                     of frame rotations in the frame chain
    """
    @classmethod
    def from_spice(cls, *args, frame_changes=[], ephemeris_time=[], **kwargs):
    def from_spice(cls, *args, sensor_frame, target_frame, center_ephemeris_time, ephemeris_times=[], **kwargs):
        frame_chain = cls()

        times = np.array(ephemeris_time)
        times = np.array(ephemeris_times)

        sensor_time_dependent_frames, sensor_constant_frames = cls.frame_trace(sensor_frame, center_ephemeris_time)
        target_time_dependent_frames, target_constant_frames = cls.frame_trace(target_frame, center_ephemeris_time)

        time_dependent_frames = list(zip(sensor_time_dependent_frames[:-1], sensor_time_dependent_frames[1:]))
        constant_frames = list(zip(sensor_constant_frames[:-1], sensor_constant_frames[1:]))
        target_time_dependent_frames = list(zip(target_time_dependent_frames[:-1], target_time_dependent_frames[1:]))
        target_constant_frames = list(zip(target_constant_frames[:-1], target_constant_frames[1:]))

        time_dependent_frames.extend(target_time_dependent_frames)
        constant_frames.extend(target_constant_frames)

        quats = np.zeros((len(times), 4))

        for s, d in frame_changes:
            for i, time in enumerate(times):
        for s, d in time_dependent_frames:
            for j, time in enumerate(times):
                rotation_matrix = spice.pxform(spice.frmnam(s), spice.frmnam(d), time)
                quat_from_rotation = spice.m2q(rotation_matrix)
                quats[i,:3] = quat_from_rotation[1:]
                quats[i,3] = quat_from_rotation[0]
                quats[j,:3] = quat_from_rotation[1:]
                quats[j,3] = quat_from_rotation[0]

            rotation = TimeDependentRotation(quats, times, s, d)
            frame_chain.add_edge(s, d, rotation=rotation)
            frame_chain.add_edge(rotation=rotation)

        quats = np.zeros(4)

        for s, d in constant_frames:
            rotation_matrix = spice.pxform(spice.frmnam(s), spice.frmnam(d), times[0])
            quat_from_rotation = spice.m2q(rotation_matrix)
            quats[:3] = quat_from_rotation[1:]
            quats[3] = quat_from_rotation[0]

            rotation = ConstantRotation(quats, s, d)

            frame_chain.add_edge(rotation=rotation)

        return frame_chain

    @staticmethod
    def frame_trace(reference_frame, ephemeris_time):
        frame_codes = [reference_frame]
        _, frame_type, _ = spice.frinfo(frame_codes[-1])
        frame_types = [frame_type]

        while(frame_codes[-1] != 1):
            try:
                center, frame_type, frame_type_id = spice.frinfo(frame_codes[-1])
            except Exception as e:
                print(e)
                break

            if frame_type is 1 or frame_type is 2:
                frame_code = 1

            elif frame_type is 3:
                try:
                    matrix, frame_code = spice.ckfrot(frame_type_id, ephemeris_time)
                except:
                    raise Exception(f"The ck rotation from frame {frame_codes[-1]} can not \
                                      be found due to no pointing available at requested time \
                                      or a problem with the frame")
            elif frame_type is 4:
                try:
                    matrix, frame_code = spice.tkfram(frame_type_id)
                except:
                    raise Exception(f"The tk rotation from frame {frame_codes[-1]} can not \
                                      be found")
            elif frame_type is 5:
                matrix, frame_code = spice.zzdynrot(frame_type_id, center, ephemeris_time)

            else:
                raise Exception(f"The frame {frame_codes[-1]} has a type {frame_type_id} \
                                  not supported by your version of Naif Spicelib. \
                                  You need to update.")

            frame_codes.append(frame_code)
            frame_types.append(frame_type)
        constant_frames = []
        while frame_codes:
            if frame_types[0] == 4:
                constant_frames.append(frame_codes.pop(0))
                frame_types.pop(0)
            else:
                break

        time_dependent_frames = []
        if len(constant_frames) != 0:
            time_dependent_frames.append(constant_frames[-1])

        while frame_codes:
            time_dependent_frames.append(frame_codes.pop(0))

        return time_dependent_frames, constant_frames

    @classmethod
    def from_isis_tables(cls, *args, inst_pointing={}, body_orientation={}, **kwargs):
        frame_chain = cls()

        for rotation in create_rotations(inst_pointing):
            frame_chain.add_edge(rotation.source,
                                 rotation.dest,
                                 rotation=rotation)
            frame_chain.add_edge(rotation=rotation)

        for rotation in create_rotations(body_orientation):
            frame_chain.add_edge(rotation.source,
                                 rotation.dest,
                                 rotation=rotation)
            frame_chain.add_edge(rotation=rotation)
        return frame_chain

    def add_edge(self, s, d, rotation, **kwargs):
        super(FrameChain, self).add_edge(s, d, rotation=rotation, **kwargs)
        super(FrameChain, self).add_edge(d, s, rotation=rotation.inverse(), **kwargs)
    def add_edge(self, rotation, **kwargs):
        super(FrameChain, self).add_edge(rotation.source, rotation.dest, rotation=rotation, **kwargs)
        rotation = rotation.inverse()
        super(FrameChain, self).add_edge(rotation.source, rotation.dest, rotation=rotation, **kwargs)

    def compute_rotation(self, source, destination):
        """
Loading