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

Marci Driver (#475)

* Initial Marci driver for ale

* Added Marci tests and corrected comparison function

* Added kernel data for use in Marci load tests

* Pushed marci ISD for use in loads test

* Adds function doc strings to Marci and HiRISE drivers

* Updated comparison function and marci load test

* Added another set of doc string info

* Cached reference frame
parent 650d42ee
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -306,10 +306,12 @@ class NaifSpice():
        : str
        String name of the target reference frame
        """
        if not hasattr(self, "_reference_frame"):
            try:
            return spice.cidfrm(spice.bodn2c(self.target_name))[1]
                self._reference_frame = spice.cidfrm(spice.bodn2c(self.target_name))[1]
            except:
            return 'IAU_{}'.format(self.target_name)
                self._reference_frame = 'IAU_{}'.format(self.target_name)
        return self._reference_frame

    @property
    def sun_position(self):
+313 −12
Original line number Diff line number Diff line
@@ -5,11 +5,262 @@ from ale.base.data_naif import NaifSpice
from ale.base.data_isis import IsisSpice
from ale.base.label_pds3 import Pds3Label
from ale.base.label_isis import IsisLabel
from ale.base.type_distortion import RadialDistortion
from ale.base.type_distortion import RadialDistortion, NoDistortion
from ale.base.type_sensor import LineScanner

from ale import util

class MroMarciIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoDistortion, Driver):

    @property
    def instrument_id(self):
        """
        Returns an instrument id for uniquely identifying the instrument, but often
        also used to be piped into Spice Kernels to acquire IKIDs. Therefore they
        the same ID the Spice expects in bods2c calls.

        Returns
        -------
        : str
          instrument id
        """
        id_lookup = {
          "Marci" : {
            "BLUE" : "MRO_MARCI_VIS",
            "GREEN" : "MRO_MARCI_VIS",
            "ORANGE" : "MRO_MARCI_VIS",
            "RED" : "MRO_MARCI_VIS",
            "NIR" : "MRO_MARCI_VIS",
            "LONG_UV" : "MRO_MARCI_UV",
            "SHORT_UV" : "MRO_MARCI_UV"
          }
        }
        # This should likely return a list but would only matter in USGSCSM
        band_bin = self.label["IsisCube"]["BandBin"]["FilterName"][0]
        return id_lookup[super().instrument_id][band_bin]

    @property
    def base_ikid(self):
        """
        Returns the Naif ID code for the instrument
        Expects the instrument_id to be defined. This must be a string containing
        the short name of the instrument.

        Returns
        -------
        : int
          Naif ID used to for identifying the instrument in Spice kernels
        """
        if not hasattr(self, "_base_ikid"):
            self._base_ikid = spice.bods2c("MRO_MARCI")
        return self._base_ikid

    @property
    def flipped_framelets(self):
        """
        Gets the keyword from the ISIS label to determine if the data is flipped or not

        Returns
        -------
        : bool
          True if flipped and False if not flipped
        """
        if not hasattr(self, "_flipped_framelets"):
            self._flipped_framelets = (self.label["IsisCube"]["Instrument"]["DataFlipped"] != 0)
        return self._flipped_framelets

    def compute_marci_time(self, line):
        """
        Computes the marci time based on the line given. Original code taken
        from the ISIS Marci Camera/PushFrameCameraDetectorMap.

        Returns
        -------
        : list
            List of times associated with the bands of the image
        """
        if not hasattr(self, "_num_framelets"):
            self._num_bands = self.label["IsisCube"]["Core"]["Dimensions"]["Bands"]
            # is the detector line summing/line scale factor
            sum_mode = self.label["IsisCube"]["Instrument"]["SummingMode"]

            framelet_offset_factor = self.label["IsisCube"]["Instrument"]["ColorOffset"]
            if self.flipped_framelets:
                framelet_offset_factor *= -1

            self._framelet_offset_lookup = {
              "NIR" : 0 * framelet_offset_factor,
              "RED" : 1 * framelet_offset_factor,
              "ORANGE" : 2 * framelet_offset_factor,
              "GREEN" : 3 * framelet_offset_factor,
              "BLUE" : 4 * framelet_offset_factor,
              "LONG_UV" : 5 * framelet_offset_factor,
              "SHORT_UV" : 6 * framelet_offset_factor,
            }
            self._filters = self.label["IsisCube"]["BandBin"]["FilterName"]

            self._framelet_rate = self.label["IsisCube"]["Instrument"]["InterframeDelay"].value
            framelet_height = 16

            self._actual_framelet_height = framelet_height / sum_mode

            num_lines = self.label["IsisCube"]["Core"]["Dimensions"]["Lines"]
            self._num_framelets = num_lines / (16 / sum_mode)

        times = []
        for band in range(self._num_bands):
            framelet = ((line - 0.5) / self._actual_framelet_height) + 1
            framelet_offset = self._framelet_offset_lookup[self._filters[band]]
            adjusted_framelet = framelet - framelet_offset

            time = self.start_time
            # Keeping in line with ISIS
            if not self.flipped_framelets:
                time += (adjusted_framelet - 1) * self._framelet_rate
            else:
                time += (self._num_framelets - adjusted_framelet) * self._framelet_rate
            times.append(time)
        return times

    @property
    def start_time(self):
        """
        Start time of the image computed from the SpacecraftClockCount/SpacecraftClockStartCount
        in the IsisLabel. This is then translated to the center of a line.

        Returns
        -------
        : float
          The start time as it relates to ISIS of the image
        """
        if not hasattr(self, "_start_time"):
            start_time = super().ephemeris_start_time
            start_time -= ((self.exposure_duration / 1000.0) / 2.0)
            self._start_time = start_time
        return self._start_time

    @property
    def ephemeris_start_time(self):
        """
        Returns the starting ephemeris time of the image. Generated based on
        the returns from compute_marci_time, and whether the image is flipped or
        not.

        Returns
        -------
        : double
          Starting ephemeris time of the image
        """
        if not hasattr(self, "_ephemeris_start_time"):
            if not self.flipped_framelets:
                line = 0.5
            else:
                line = self.label["IsisCube"]["Core"]["Dimensions"]["Lines"] + 0.5
            self._ephemeris_start_time = min(self.compute_marci_time(line))
        return self._ephemeris_start_time

    @property
    def ephemeris_stop_time(self):
        """
        Returns the ending ephemeris time of the image. Generated based on
        the returns from compute_marci_time, and whether the image is flipped or
        not.

        Returns
        -------
        : double
          Starting ephemeris time of the image
        """
        if not hasattr(self, "_ephemeris_stop_time"):
            if not self.flipped_framelets:
                line = self.label["IsisCube"]["Core"]["Dimensions"]["Lines"] + 0.5
            else:
                line = 0.5
            self._ephemeris_stop_time = max(self.compute_marci_time(line))
        return self._ephemeris_stop_time

    @property
    def detector_center_line(self):
        """
        Returns the center detector line. This is a placeholder for use within
        Isis. Since Isis does not use much of the ISD this value allows the as
        accurate ISD for use in Isis but will be inaccurate for USGSCSM.

        Returns
        -------
        : float
          Detector sample of the principal point
        """
        return 0

    @property
    def detector_center_sample(self):
        """
        Returns the center detector sample. This is a placeholder for use within
        Isis. Since Isis does not use much of the ISD this value allows the as
        accurate ISD for use in Isis but will be inaccurate for USGSCSM.

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

    @property
    def focal2pixel_samples(self):
        """
        Expects base_ikid to be defined. This must be the integer Naif id code of the instrument

        Returns
        -------
        : list<double>
          focal plane to detector samples
        """
        return list(spice.gdpool('INS{}_ITRANSS'.format(self.base_ikid), 0, 3))

    @property
    def focal2pixel_lines(self):
        """
        Expects base_ikid to be defined. This must be the integer Naif id code of the instrument

        Returns
        -------
        : list<double>
          focal plane to detector lines
        """
        return list(spice.gdpool('INS{}_ITRANSL'.format(self.base_ikid), 0, 3))

    @property
    def naif_keywords(self):
        """
        Adds all naifkeywords that contain the base_ikid to the already
        populated naif keywords

        Returns
        -------
        : dict
          Dictionary of keywords and values that ISIS creates and attaches to the label
        """
        return {**super().naif_keywords, **util.query_kernel_pool(f"*{self.base_ikid}*")}

    @property
    def sensor_name(self):
        """
        ISIS doesn't propergate this to the ingested cube label, so hard-code it.
        """
        return "COLOR IMAGER CAMERA"

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


class MroCtxIsisLabelIsisSpiceDriver(LineScanner, IsisLabel, IsisSpice, RadialDistortion, Driver):

@@ -161,6 +412,8 @@ class MroCtxIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, RadialDi
    @property
    def sensor_model_version(self):
        """
        The ISIS Sensor model number for HiRise in ISIS. This is likely just 1
        
        Returns
        -------
        : int
@@ -306,9 +559,17 @@ class MroHiRiseIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, Radia
        """
        return "HIRISE CAMERA"


    @property
    def un_binned_rate(self):
        """
        Taken from the ISIS HiRise Camera model, returns the un_binned_rate
        based on various hardcoded values and the DeltaLineTimerCount.

        Returns
        -------
        : float
          The un_binned_rate of the image
        """
        if not hasattr(self, "_un_binned_rate"):
            delta_line_timer_count = self.label["IsisCube"]["Instrument"]["DeltaLineTimerCount"]
            self._un_binned_rate = (74.0 + (delta_line_timer_count / 16.0)) / 1000000.0
@@ -316,6 +577,15 @@ class MroHiRiseIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, Radia

    @property
    def ephemeris_start_time(self):
        """
        Computes the ephemeris_start_time of the image based on infomation from
        the ISIS camera model.

        Returns
        -------
        : float
          Starting ephemeris time of the image
        """
        if not hasattr(self, "_ephemeris_start_time"):
            tdi_mode = self.label["IsisCube"]["Instrument"]["Tdi"]
            bin_mode = self.label["IsisCube"]["Instrument"]["Summing"]
@@ -338,24 +608,52 @@ class MroHiRiseIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, Radia

    @property
    def exposure_duration(self):
        """
        Returns the computed exposure duration based on the exposure duration
        computed in the ISIS HiRise camera model.

        Returns
        -------
        : float
          The exposure duration of the image
        """
        if not hasattr(self, "_exposure_duration"):
            self._exposure_duration = self.un_binned_rate * self.label["IsisCube"]["Instrument"]["Summing"]
        return self._exposure_duration

    @property
    def ccd_ikid(self):
        """
        Computes the CCD ikid to get the necessary naifkeywords for Isis.

        Returns
        -------
        : int
          CCD ikid
        """
        if not hasattr(self, "_ccd_ikid"):
            ccd_number = hirise_ccd_lookup[self.label["IsisCube"]["Instrument"]["CpmmNumber"]]
        return spice.bods2c("MRO_HIRISE_CCD{}".format(ccd_number))
            self._ccd_ikid = spice.bods2c("MRO_HIRISE_CCD{}".format(ccd_number))
        return self._ccd_ikid

    @property
    def sensor_frame_id(self):
        """
        Hard coded sensor_frame_id based on the Isis HiRise camera model.

        Returns
        -------
        : int
          The sensor frame id
        """
        return -74690

    @property
    def detector_center_sample(self):
    def detector_center_line(self):
        """
        Returns the center detector sample. Expects ikid to be defined. This should
        be an integer cont?aining the Naif Id code of the instrument.
        Returns the center detector line. This is a placeholder for use within
        Isis. Since Isis does not use much of the ISD this value allows the as
        accurate ISD for use in Isis but will be inaccurate for USGSCSM.

        Returns
        -------
@@ -365,10 +663,11 @@ class MroHiRiseIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, Radia
        return 0

    @property
    def detector_center_line(self):
    def detector_center_sample(self):
        """
        Returns the center detector line. Expects ikid to be defined. This should
        be an integer containing the Naif Id code of the instrument.
        Returns the center detector sample. This is a placeholder for use within
        Isis. Since Isis does not use much of the ISD this value allows the as
        accurate ISD for use in Isis but will be inaccurate for USGSCSM.

        Returns
        -------
@@ -380,8 +679,8 @@ class MroHiRiseIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, Radia
    @property
    def naif_keywords(self):
        """
        Updated set of naif keywords containing the NaifIkCode for the specific
        Juno filter used when taking the image.
        Adds all naifkeywords that contain the ccd_ikid to the already
        populated naif keywords

        Returns
        -------
@@ -393,6 +692,8 @@ class MroHiRiseIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, Radia
    @property
    def sensor_model_version(self):
        """
        The ISIS Sensor model number for HiRise in ISIS. This is likely just 1

        Returns
        -------
        : int
+11 −2
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import warnings
import numpy as np
import json
import pvl
import numbers
from glob import glob
from pathlib import Path

@@ -60,8 +61,16 @@ def compare_dicts(ldict, rdict):
        elif isinstance(item, np.ndarray) or isinstance(item, list):
            if len(item) != len(rdict[key]):
                differences.append(f'Array sizes of key {key} are not equal {item} : {rdict[key]}.')
            elif not np.allclose(item, rdict[key]):
            else:
                list_item = np.array(item).flat[0]
                if isinstance(list_item, numbers.Number):
                    if not np.allclose(item, rdict[key]):
                        differences.append(f'Array values of key {key} are not almost equal {item} : {rdict[key]}.')
                elif isinstance(list_item, str):
                    if not (np.array(item) == np.array(rdict[key])).all():
                        differences.append(f'Array values of key {key} are not equal {item} : {rdict[key]}.')
                else:
                    raise TypeError("No comparison handled for underlying list type {} for key {}".format(list_item, key))
        elif isinstance(item, str):
            if item.lower() != rdict[key].lower():
                differences.append(f'Values of key {key} are not equal {item} : {rdict[key]}.')
+565 −0

File added.

Preview size limit exceeded, changes collapsed.

+3233 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading