Unverified Commit 62f76886 authored by Amy Stamile's avatar Amy Stamile Committed by GitHub
Browse files

Ody Driver Fixes (#582)

* Updates to et times

* fixed tests

* ThemisVIR updates

* Adds band time offset

* ephemeris_stop_time updates

* fix stop time

* time fixes and docs

* added changelog entry
parent 58a8ee4a
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
# Docs
docs/doxyxml/
docs/build/*
docs/xml/*

# Byte-compiled / optimized / DLL files
__pycache__/
@@ -224,3 +225,5 @@ dmypy.json
*.img
*.IMG
print.prt
tests/ctests/default.profraw
default.profraw
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ release.
### Changed
- Enabled Hayabusa2 drivers [#596](https://github.com/DOI-USGS/ale/pull/596)
- Enabled Juno drivers [#597](https://github.com/DOI-USGS/ale/pull/597)
- Enabled Odyssey drivers [#582](https://github.com/DOI-USGS/ale/pull/582)

### Added
- Apollo Metric drivers, tests, and data [#533](https://github.com/DOI-USGS/ale/pull/533)
+12 −0
Original line number Diff line number Diff line
@@ -193,3 +193,15 @@ class LoDistortion():
                "center_point_y" : center_point_y
            }
        }
    
class ThemisIrDistortion():
    @property
    def usgscsm_distortion_model(self):

        return {
            "themisir":{
                "p_alpha1" : 0.00447623,
                "p_alpha2" : 0.00107556,
                "p_k" : 0.996005
            }
        }
 No newline at end of file
+2 −2
Original line number Diff line number Diff line
@@ -23,8 +23,8 @@ from ale.base.data_naif import NaifSpice
from abc import ABC

# Explicit list of disabled drivers
__disabled_drivers__ = ["ody_drivers",
                        "tgo_drivers"]

__disabled_drivers__ = ["tgo_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'))]
+370 −30
Original line number Diff line number Diff line
import spiceypy as spice

import ale
import math
from ale.base.base import Driver
from ale.base.type_distortion import NoDistortion
from ale.base.type_distortion import ThemisIrDistortion, NoDistortion
from ale.base.data_naif import NaifSpice
from ale.base.label_isis import IsisLabel
from ale.base.type_sensor import LineScanner
from ale.base.type_sensor import LineScanner, PushFrame

import pvl

class OdyThemisIrIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoDistortion, Driver):
class OdyThemisIrIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, ThemisIrDistortion, Driver):
    """
    Driver for Themis IR ISIS cube
    """
    @property
    def instrument_id(self):
        """
        Returns the short name of the instrument

        Returns
        -------
        : str
          instrument id
        """
        inst_id = super().instrument_id

        if inst_id not in ["THEMIS_IR"]:
            raise Exception(f"{inst_id} is not a valid THEMIS IR instrument name. Expecting THEMIS_IR")

        return inst_id

    @property
    def sensor_model_version(self):
        """
          Returns
        -------
        : int
          version of the sensor model
        """
        return 1

    @property
    def spacecraft_name(self):
        """
        Returns the name of the spacecraft
        Returns
        -------
        : str
        Full name of the spacecraft
        """
        name = super().spacecraft_name.replace('_', ' ')
        if name != "MARS ODYSSEY":
            raise Exception("{name} for label is not a valid Mars Odyssey spacecraft name")
@@ -35,8 +56,34 @@ class OdyThemisIrIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoD

    @property
    def ikid(self):
        """
        Returns the Naif ID code for the instrument

        Returns
        -------
        : int
          Naif ID used to for identifying the instrument in Spice kernels
        """
        return self.label['IsisCube']['Kernels']['NaifFrameCode']
    
    @property
    def sampling_factor(self):
        """
        Returns the summing factor from the ISIS label. For example a return value of 2
        indicates that 2 lines and 2 samples (4 pixels) were summed and divided by 4
        to produce the output pixel value.

        Returns
        -------
        : int
          Number of samples and lines combined from the original data to produce a single pixel in this image
        """
        try:
            summing = self.label['IsisCube']['Instrument']['SpatialSumming']
        except:
            summing = 1
        return summing

    @property
    def line_exposure_duration(self):
        """
@@ -47,7 +94,17 @@ class OdyThemisIrIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoD
        return (33.2871/1000 * self.line_summing)

    @property
    def ephemeris_start_time(self):
    def start_time(self):
        """
        The starting ephemeris time, in seconds

        Formula derived from ISIS3's ThemisIr Camera model

        Returns
        -------
        : double
          Starting ephemeris time in seconds
        """
        og_start_time = super().ephemeris_start_time
        offset = self.label["IsisCube"]["Instrument"]["SpacecraftClockOffset"]
        if isinstance(offset, pvl.collections.Quantity):
@@ -60,30 +117,145 @@ class OdyThemisIrIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoD

        return og_start_time + offset
    

    @property
    def ephemeris_start_time(self):
        """
        Returns the ephemeris start time of the image.
        Expects spacecraft_id to be defined. This should be the integer
        Naif ID code for the spacecraft.

        Returns
        -------
        : float
          ephemeris start time of the image
        """
        return self.band_times[0]
    

    @property
    def ephemeris_stop_time(self):
        """
        Returns the sum of the starting ephemeris time and the number of lines
        times the exposure duration. Expects ephemeris start time, exposure duration
        and image lines to be defined. These should be double precision numbers
        containing the ephemeris start, exposure duration and number of lines of
        the image.

        Returns
        -------
        : double
          Center ephemeris time for an image
        """
        return self.band_times[-1] + (self.image_lines * self.line_exposure_duration)

    @property
    def focal_length(self):
        return 202.059
        """
        The focal length of the instrument

        Returns
        -------
        float :
            The focal length in millimeters
        """
        return 203.9213

    @property
    def detector_center_sample(self):
        """
        Returns the center detector sample.

        Returns
        -------
        : float
          Detector sample of the principal point
        """
        return 160.0
    
    @property
    def detector_center_line(self):
        """
        Returns the center detector line.

        Returns
        -------
        : float
          Detector line of the principal point
        """
        return 0
    
    @property
    def detector_center_sample(self):
        return 0
    def tdi_mode(self):
        return self.label['IsisCube']['Instrument']["TimeDelayIntegration"]
    
    @property
    def sensor_name(self):
        """
        Returns the name of the sensor

        Returns
        -------
        : str
          Name of the sensor
        """
        return self.label['IsisCube']['Instrument']['SpacecraftName']

    @property
    def band_times(self):
        """
        Computes the band offset and adds to each band's start time

        Returns
        -------
        : list
          Adjusted start times for each band
        """
        self._num_bands = self.label["IsisCube"]["Core"]["Dimensions"]["Bands"]
        times = []

        org_bands = self.label["IsisCube"]["BandBin"]["FilterNumber"]

        for vband in range(self._num_bands):
            if 'ReferenceBand' in self.label['IsisCube']['Instrument']:
                band = self.label['IsisCube']['Instrument']['ReferenceBand']
            else:
                if isinstance(org_bands, (list, tuple)):
                    band = org_bands[vband]
                else:
                    band = org_bands

            if (self.tdi_mode == "ENABLED"):
                band_tdi = [8.5, 24.5, 50.5, 76.5, 102.5,
                                   128.5, 154.5, 180.5, 205.5, 231.5]
                detector_line = band_tdi[band-1]
            else:
                band_no_tdi = [9, 24, 52, 77, 102, 129, 155, 181, 206, 232]
                detector_line = band_no_tdi[band-1]

class OdyThemisVisIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoDistortion, Driver):
            band_offset = (detector_line - 0.5) * self.line_exposure_duration
            band_offset /= self.sampling_factor

            time = self.start_time + band_offset
            times.append(time)
        return times


class OdyThemisVisIsisLabelNaifSpiceDriver(PushFrame, IsisLabel, NaifSpice, NoDistortion, Driver):
    """"
    Driver for Themis VIS ISIS cube
    """

    @property
    def instrument_id(self):
        """
        Returns the short name of the instrument

        Returns
        -------
        : str
          instrument id
        """
        inst_id = super().instrument_id

        if inst_id not in ["THEMIS_VIS"]:
@@ -93,10 +265,23 @@ class OdyThemisVisIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, No

    @property
    def sensor_model_version(self):
        """
          Returns
        -------
        : int
          version of the sensor model
        """
        return 1

    @property
    def spacecraft_name(self):
        """
        Returns the name of the spacecraft
        Returns
        -------
        : str
        Full name of the spacecraft
        """
        name = super().spacecraft_name.replace('_', ' ')
        if name != "MARS ODYSSEY":
            raise Exception("{name} for label is not a valid Mars Odyssey spacecraft name")
@@ -104,10 +289,18 @@ class OdyThemisVisIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, No

    @property
    def ikid(self):
        """
        Returns the Naif ID code for the instrument

        Returns
        -------
        : int
          Naif ID used to for identifying the instrument in Spice kernels
        """
        return self.label['IsisCube']['Kernels']['NaifFrameCode']

    @property
    def ephemeris_start_time(self):
    def start_time(self):
        """
        The starting ephemeris time, in seconds

@@ -118,7 +311,10 @@ class OdyThemisVisIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, No
        : double
          Starting ephemeris time in seconds
        """
        og_start_time = super().ephemeris_start_time
        clock_start_count = self.label['IsisCube']['Instrument']['SpacecraftClockCount']

        # ran into weird quirk that if clock count ends with a zero, str() will drop the last zero causing scs2e to return a different et.
        og_start_time = spice.scs2e(self.spacecraft_id, f'{clock_start_count:.3f}')

        offset = self.label["IsisCube"]["Instrument"]["SpacecraftClockOffset"]
        if isinstance(offset, pvl.collections.Quantity):
@@ -129,43 +325,187 @@ class OdyThemisVisIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, No
                # if not milliseconds, the units are probably seconds
                offset = offset.value

        return og_start_time + offset - (self.line_exposure_duration/2)
        return og_start_time + offset - (self.exposure_duration / 2)
    
    @property
    def line_exposure_duration(self):
    def ephemeris_start_time(self):
        """
        The line exposure duration of the image, in seconds
        Returns the ephemeris start time of the image.
        Expects spacecraft_id to be defined. This should be the integer
        Naif ID code for the spacecraft.

        Returns
        -------
        : float
          Line exposure duration in seconds
          ephemeris start time of the image
        """
        line_exposure_duration = self.label['IsisCube']['Instrument']['ExposureDuration']
        if isinstance(line_exposure_duration, pvl.collections.Quantity):
            units = line_exposure_duration.units
            if "ms" in units.lower():
                line_exposure_duration = line_exposure_duration.value * 0.001
            else:
                # if not milliseconds, the units are probably seconds
                line_exposure_duration = line_exposure_duration.value
        else:
            # if no units are available, assume the exposure duration is given in milliseconds
            line_exposure_duration = line_exposure_duration * 0.001
        return line_exposure_duration
        return self.band_times[0]
    
    @property
    def center_ephemeris_time(self):
        """
        Returns the center ephemeris times based off the last band start time.
        Expects spacecraft_id to be defined. This should be the integer
        Naif ID code for the spacecraft.

        Returns
        -------
        : double
          Center ephemeris time for an image
        """
        center_frame = math.floor(self.num_frames / 2)
        return (self.band_times[-1] + center_frame * self.interframe_delay) + self.exposure_duration
    
    @property
    def ephemeris_stop_time(self):
        """
        Returns the ephemeris stop time of the image.
        Expects spacecraft_id to be defined. This should be the integer
        Naif ID code for the spacecraft.

        Returns
        -------
        : float
          ephemeris stop time of the image
        """
        return (max(self.band_times) + self.num_frames * self.interframe_delay) + self.exposure_duration

    @property
    def focal_length(self):
        """
        The focal length of the instrument

        Returns
        -------
        float :
            The focal length in millimeters
        """
        return 202.059

    @property
    def detector_center_line(self):
        return 0
        """
        Returns the center detector line.

        Returns
        -------
        : float
          Detector line of the principal point
        """
        return 512.0

    @property
    def detector_center_sample(self):
        return 0
        """
        Returns the center detector sample.

        Returns
        -------
        : float
          Detector sample of the principal point
        """
        return 512.0
    
    @property
    def sensor_name(self):
        """
        Returns the name of the sensor

        Returns
        -------
        : str
          Name of the sensor
        """
        return self.label['IsisCube']['Instrument']['SpacecraftName']

    @property
    def num_frames(self):
        """
        Number of frames in the image

        Returns
        -------
        : int
          Number of frames in the image
        """
        return self.label['IsisCube']['Instrument']['NumFramelets']

    @property
    def framelet_height(self):
        """
        Computes the framelet height based on number lines, number of frames and sampling factor

        Returns
        -------
        : float
          height of framelet
        """
        return self.image_lines / (self.num_frames / self.sampling_factor)
    
    @property
    def sampling_factor(self):
        """
        Returns the summing factor from the PDS3 label that is defined by the SpatialSumming

        Returns
        -------
        : int
          Number of samples and lines combined from the original data to produce a single pixel in this image
        """
        return self.label['IsisCube']['Instrument']['SpatialSumming']
    
    @property
    def interframe_delay(self):
        """
        The interframe delay in seconds

        Returns
        -------
        : float
          interframe delay in seconds
        """
        return self.label['IsisCube']['Instrument']['InterframeDelay']
    
    @property
    def band_times(self):
        """
        Computes the band offset and adds to each band's start time

        Returns
        -------
        : list
          Adjusted start times for each band
        """
        self._num_bands = self.label["IsisCube"]["Core"]["Dimensions"]["Bands"]
        times = []

        org_bands = self.label["IsisCube"]["BandBin"]["FilterNumber"]

        for vband in range(self._num_bands):
            if isinstance(org_bands, (list, tuple)):
                timeband = org_bands[vband]
            else:
                timeband = org_bands

            if 'ReferenceBand' in self.label['IsisCube']['Instrument']:
                ref_band = self.label['IsisCube']['Instrument']['ReferenceBand']
                wavelength_to_timeband = [2, 5, 3, 4, 1]
                timeband = wavelength_to_timeband[ref_band - 1]

            band_offset = ((timeband - 1) * self.interframe_delay) - (self.exposure_duration / 2.0)

            time = self.start_time + band_offset
            times.append(time)
            
        return times
    
    @property
    def framelets_flipped(self):
        """
        Checks if framelets are flipped

        Returns
        -------
        : bool
        """
        return True
 No newline at end of file
Loading