Unverified Commit 650d42ee authored by Amy Stamile's avatar Amy Stamile Committed by GitHub
Browse files

Leisa driver (#474)

* Initial Leisa Driver

* Updated nh tests

* Added sensor_name

* Removed Jupiter test data and added Pluto test data.

* Removed light time correction and increased padding in KernelSlice

* Fixed exposure and increased padding in KernelSlice

* fixed exposure duration int error

* Fixes CI failures. Updated isd.

* separated load tests for lorri and leisa.

* Added Leisa unit tests
parent 88c48c01
Loading
Loading
Loading
Loading
+123 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ from ale.base.type_distortion import NoDistortion
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_sensor import LineScanner

class NewHorizonsLorriIsisLabelNaifSpiceDriver(Framer, IsisLabel, NaifSpice, NoDistortion, Driver):
    """
@@ -65,3 +66,125 @@ class NewHorizonsLorriIsisLabelNaifSpiceDriver(Framer, IsisLabel, NaifSpice, NoD
    @property
    def sensor_name(self):
        return self.label['IsisCube']['Instrument']['SpacecraftName']


class NewHorizonsLeisaIsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoDistortion, Driver):
    """
    Driver for reading New Horizons LEISA ISIS3 Labels.
    """

    @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 = {
            "LEISA" : "NH_RALPH_LEISA"
        }
        return id_lookup[super().instrument_id]

    @property
    def ikid(self):
        """
        Overridden to grab the ikid from the Isis Cube since there is no way to
        obtain this value with a spice bods2c call. Isis sets this value during
        ingestion, based on the original fits file.

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


    @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 spice.scs2e(self.spacecraft_id, self.spacecraft_clock_start_count)

    @property
    def ephemeris_stop_time(self):
        """
        ISIS doesn't preserve the spacecraft stop count that we can use to get
        the ephemeris stop time of the image, so compute the ephemeris stop time
        from the start time and the exposure duration.
        """
        return self.ephemeris_start_time + self.exposure_duration * self.image_lines

    @property
    def detector_center_line(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
        -------
        : float
          Detector line of the principal point
        """
        return 0

    @property
    def detector_center_sample(self):
        """
        Returns the center detector sample. Expects ikid to be defined. This should
        be an integer containing the Naif Id code of the instrument.

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

    @property
    def sensor_name(self):
        """
        Returns the name of the instrument. Need to over-ride isis_label because
        InstrumentName is not defined in the ISIS label for NH Leisa cubes.

        Returns
        -------
        : str
        Name of the sensor
        """
        return self.instrument_id

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

    @property
    def exposure_duration(self):
        """
        The exposure duration of the image, in seconds

        Returns
        -------
        : float
          Exposure duration in seconds
        """
        return self.label['IsisCube']['Instrument']['ExposureDuration']
+11 −14
Original line number Diff line number Diff line
%% Cell type:code id: tags:

``` python
import spiceypy as spice
import pvl
import os
import re
import subprocess
from ale import util
from itertools import chain
import io
import networkx as nx


# These should be provided when running this script.
cube = "/home/acpaquette/B10_013341_1010_XN_79S172W.cub"
output_dir = "/Users/jmapel/ale/nb_test" # Output dir for created kernel files
data_dir = "/usgs/cpkgs/isis3/data/" # Dir of where to pull original kernels from
ckslicer_loc = "/Users/jmapel/ale/ckslicer"
cube = "leisa/lsb_0296962438_0x53c_eng/lsb_0296962438_0x53c_eng-spiced.cub"
output_dir = "leisa/lsb_0296962438_0x53c_eng/" # Output dir for created kernel files
data_dir = "isis3/isis_data/" # Dir of where to pull original kernels from

def merge_intervals(intervals):
    """
    Merge a set of intervals. The intervals are assumed to be closed, that is they include the end-points.

    Parameters
    ----------
    intervals : list
                The input list of intrvals where each interval is a tuple of (start, end)

    Returns
    -------
    : list
      A sorted list of the merged intervals
    """
    sorted_intervals = sorted(intervals, key=lambda tup: tup[0])
    merged = [sorted_intervals[0]]
    for interval in sorted_intervals[1:]:
        # No intersection
        if interval[0] > merged[-1][1]:
            merged.append(interval)
        # Intersection, but new interval isn't wholey contained
        elif interval[1] > merged[-1][1]:
            merged[-1] = (merged[-1][0], interval[1])
    return merged

def add_light_time_correction(cube_info, padding=5):
def add_light_time_correction(cube_info, padding=120):
    """
    Compute the time intervals for the image and any light time correction

    Parameters
    ----------
    cube_info : ordered dict
                The cube info from ale.util.generate_kernels_from_cube
    padding : float
              Time padding in seconds to add to each interval

    Returns
    -------
    : list
      A sorted list of the intervals as (start_et, stop_et)
    """
    image_start_et = spice.scs2e(cube_info['SpacecraftID'], cube_info['SpacecraftClockCount'])

    image_end_et = image_start_et + cube_info['ExposureDuration'] * cube_info['Lines']

    inst_state, inst_lt = spice.spkez(cube_info['SpacecraftID'], image_start_et, 'J2000', 'LT+S', 0)
    target_state, target_lt = spice.spkez(cube_info['TargetID'], image_start_et, 'J2000', 'LT+S', 0)
    sun_state, sun_lt = spice.spkez(10, image_start_et, 'J2000', 'LT+S', cube_info['TargetID'])
    inst_state, inst_lt = spice.spkez(cube_info['SpacecraftID'], image_start_et, 'J2000', 'NONE', 0)
    target_state, target_lt = spice.spkez(cube_info['TargetID'], image_start_et, 'J2000', 'NONE', 0)
    sun_state, sun_lt = spice.spkez(10, image_start_et, 'J2000', 'NONE', cube_info['TargetID'])

    intervals = [
        (image_start_et - padding, image_end_et + padding),
        (image_start_et - padding - inst_lt, image_end_et + padding - inst_lt),
        (image_start_et - padding - target_lt, image_end_et + padding - target_lt),
        (image_start_et - padding - sun_lt, image_end_et + padding - sun_lt)]

    return merge_intervals(intervals)
```

%% Cell type:code id: tags:

``` python
# These are the processing steps. This will make use of the cube provided further up to create smaller,
# more manageable kernel files for ale testing purposes. This currently only handles ck and spk files.

# Get dictionary of kernel lists from cube
cube_info = util.generate_kernels_from_cube(cube, format_as = 'dict')

# Replace path variables with absolute paths for kernels
for kernel_list in cube_info:
    for index, kern in enumerate(cube_info[kernel_list]):
        if kern is not None:
            cube_info[kernel_list][index] = data_dir + kern.strip('$')

# Create ordered list of kernels for furnishing
kernels = [kernel for kernel in chain.from_iterable(cube_info.values()) if isinstance(kernel, str)]
spice.furnsh(kernels)

# Loads cube as pvl to extract rest of data
cube_pvl = pvl.load(cube)

# Save other necesary info in cube_info dict
cube_info.update(Lines = cube_pvl['IsisCube']['Core']['Dimensions']['Lines'])
cube_info.update(Lines = 400)
cube_info.update(SpacecraftClockCount = cube_pvl['IsisCube']['Instrument']['SpacecraftClockCount'])
cube_info.update(ExposureDuration = cube_pvl['IsisCube']['Instrument']['LineExposureDuration'].value * 0.001)
cube_info.update(SpacecraftClockCount = cube_pvl['IsisCube']['Instrument']['SpacecraftClockStartCount'])
cube_info.update(ExposureDuration = cube_pvl['IsisCube']['Instrument']['ExposureDuration'])
cube_info.update(TargetID = spice.bods2c(cube_pvl['IsisCube']['Instrument']['TargetName']))
cube_info.update(SpacecraftID = spice.bods2c(cube_pvl['IsisCube']['Instrument']['SpacecraftName']))

# Account for light time correction
intervals = add_light_time_correction(cube_info)

# For each binary ck kernel specified in cube, run the ckslicer, comment and to-transfer commands
for ck in [k for k in kernels if k.lower().endswith('.bc')]:
    ck_path, ck_file_extension = os.path.splitext(ck)
    ck_basename = os.path.basename(ck_path)
    for index, interval in enumerate(intervals):
        for frame in util.get_ck_frames(ck):
            output_basename = os.path.join(output_dir, ck_basename + '_' + str(index) + '_sliced_' + str(frame))
            output_kern = output_basename + ck_file_extension
            output_comments = output_basename + '.cmt'
            start_sclk = spice.sce2s(cube_info['SpacecraftID'], interval[0])
            end_sclk = spice.sce2s(cube_info['SpacecraftID'], interval[1])

            # Create new sliced ck kernel
            ckslicer_command = [ckslicer_loc,
            ckslicer_command = ["ckslicer",
                                    '-LSK {}'.format(cube_info['LeapSecond'][0]),
                                    '-SCLK {}'.format(cube_info['SpacecraftClock'][0]),
                                    '-INPUTCK {}'.format(ck),
                                    '-OUTPUTCK {}'.format(output_kern),
                                    '-ID {}'.format(frame),
                                    '-TIMETYPE {}'.format('SCLK'),
                                    '-START {}'.format(start_sclk),
                                    '-STOP {}'.format(end_sclk)]
            subprocess.run(ckslicer_command, check=True)

            # Remove old comments from new ck kernel
            commnt_command = ['commnt', '-d {}'.format(output_kern)]
            subprocess.run(commnt_command, check=True)

            with open(output_comments, 'w+') as comment_file:
                comment_file.write("This CK is for testing with the image: {}\n".format(cube))
                comment_file.write("\nThis CK was generated using the following command: {}\n")
                comment_file.write(" ".join(ckslicer_command))

            # Add new comments to new ck kernel
            new_commnts_command = ["commnt", "-a {}".format(output_kern), output_comments]
            subprocess.run(new_commnts_command, check=True)

            # Create the transfer file of the new ck kernel
            subprocess.run(["toxfr", output_kern], check=True)

# Create the config file for the spkmerge command
for index, interval in enumerate(intervals):
    output_spk_basename = os.path.join(output_dir, os.path.basename(os.path.splitext(cube)[0]) + '_' + str(index))
    output_spk = output_spk_basename + '.bsp'
    start_utc = spice.et2utc(interval[0], 'c', 3)
    end_utc = spice.et2utc(interval[1], 'c', 3)
    spk_dep_tree = util.create_spk_dependency_tree([k for k in kernels if k.lower().endswith('.bsp')])
    config_string = util.spkmerge_config_string(spk_dep_tree,
                                                output_spk,
                                                [cube_info['TargetID'], cube_info['SpacecraftID'], 10],
                                                cube_info['LeapSecond'][0],
                                                start_utc,
                                                end_utc)
    with open(output_spk_basename + '.conf', 'w+') as spk_config:
        spk_config.write(config_string)

    # Create the new SPK
    spkmerge_command = ["spkmerge", spk_config.name]
    subprocess.run(spkmerge_command, check=True)

    # Create the transfer file of the new SPK kernel
    subprocess.run(["toxfr", output_spk], check=True)
```

%% Cell type:code id: tags:

``` python
```
+54 −0
Original line number Diff line number Diff line
%% Cell type:code id: tags:

``` python
fileName = 'ale/leisa/lsb_0296962438_0x53c_eng/lsb_0296962438_0x53c_eng-spiced.cub'

import os
os.environ["ISISDATA"] = "isis3/isis_data"
os.environ["ISISTESTDATA"] = "isis_testData"
os.environ["ISISROOT"] = "isis/ISIS3/build"

import ale
from ale.drivers.nh_drivers import NewHorizonsLeisaIsisLabelNaifSpiceDriver
from ale.formatters.formatter import to_isd
import spiceypy as spice
from ale.drivers import AleJsonEncoder
```

%% Cell type:code id: tags:

``` python
from ale.util import generate_kernels_from_cube
kernels = generate_kernels_from_cube(fileName, expand=True, format_as='list')
```

%% Cell type:code id: tags:

``` python
with NewHorizonsLeisaIsisLabelNaifSpiceDriver(fileName, props = {"kernels": kernels}) as driver:
    isisString = to_isd(driver)
```

%% Cell type:code id: tags:

``` python
isisString
```

%% Cell type:code id: tags:

``` python
import json

isis_dict = isisString

json_file = os.path.splitext(fileName)[0] + '.json'

with open(json_file, 'w') as fp:
    json.dump(isis_dict, fp, cls = AleJsonEncoder)

with open(json_file, 'r') as fp:
    isis_dict = json.load(fp)

isis_dict.keys()
```
+1 −0
Original line number Diff line number Diff line
{"isis_camera_version": 1, "image_lines": 368, "image_samples": 256, "name_platform": "NEW HORIZONS", "name_sensor": "NH_RALPH_LEISA", "reference_height": {"maxheight": 1000, "minheight": -1000, "unit": "m"}, "name_model": "USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL", "interpolation_method": "lagrange", "line_scan_rate": [[0.5, -157.50400000810623, 0.856]], "starting_ephemeris_time": 487928588.0336103, "center_ephemeris_time": 487928745.5376103, "radii": {"semimajor": 1188.3, "semiminor": 1188.3, "unit": "km"}, "body_rotation": {"time_dependent_frames": [10019, 1], "ck_table_start_time": 487928588.0336103, "ck_table_end_time": 487928903.0416103, "ck_table_original_size": 5, "ephemeris_times": [487928588.0336103, 487928666.7856103, 487928745.5376103, 487928824.28961027, 487928903.0416103], "quaternions": [[-0.2447405651322848, 0.2727488147412229, -0.6923053902236965, -0.6216296216423436], [-0.24501922793442102, 0.27243841475512237, -0.6924275986998515, -0.6215198377098623], [-0.2452978414907075, 0.27212796001179584, -0.6925496680060897, -0.6214099288589402], [-0.24557640574494083, 0.2718174505738702, -0.6926715981177864, -0.6212998951117484], [-0.24585492064113273, 0.27150688650375393, -0.6927933890104353, -0.6211897364904025]], "angular_velocities": [[-7.719038104508623e-06, 8.279682752122315e-06, -1.2223224358447397e-06], [-7.719038104508623e-06, 8.279682752122315e-06, -1.22232243584474e-06], [-7.719038104508622e-06, 8.279682752122315e-06, -1.222322435844739e-06], [-7.719038104508623e-06, 8.279682752122315e-06, -1.2223224358447393e-06], [-7.719038104508623e-06, 8.279682752122316e-06, -1.2223224358447386e-06]], "reference_frame": 1}, "instrument_pointing": {"time_dependent_frames": [-98000, 1], "ck_table_start_time": 487928588.0336103, "ck_table_end_time": 487928903.0416103, "ck_table_original_size": 5, "ephemeris_times": [487928588.0336103, 487928666.7856103, 487928745.5376103, 487928824.28961027, 487928903.0416103], "quaternions": [[-0.6894448798229785, -0.13288333554010637, -0.30018901711893975, 0.645673548182655], [-0.687721087896909, -0.13374109956420746, -0.29978054587038167, 0.6475219284834335], [-0.6859672573646195, -0.13441668765411288, -0.2995880589786557, 0.6493289388446816], [-0.6843925306207158, -0.13520640845056833, -0.2991531939769342, 0.6510249286147723], [-0.68280490039217, -0.13588350538291064, -0.2989481901333484, 0.6526431801384941]], "angular_velocities": [[-6.0016600701510346e-05, -3.4315085391481984e-05, 3.616461098110838e-05], [-2.9998984431665234e-05, -2.9941950083650682e-05, 5.4011064302860286e-05], [-5.7451833433989726e-05, -2.640886089874711e-05, 3.345134823897179e-05], [-2.2375968791183237e-05, -2.5852223731490175e-05, 5.491960013111816e-05], [-1.3816947750491701e-05, -3.320701389161546e-05, 5.328198819154163e-05]], "reference_frame": 1, "constant_frames": [-98901, -98201, -98000], "constant_rotation": [-0.001805871124864633, 0.0037494052664166673, 0.9999913403573196, 0.018161770046999218, 0.9998281559785996, -0.0037159953166251633, -0.999833430596437, 0.01815490216391774, -0.001873656632970122]}, "naif_keywords": {"BODY999_RADII": [1188.3, 1188.3, 1188.3], "BODY_FRAME_CODE": 10019, "BODY_CODE": 999, "FRAME_-98901_CLASS": 4.0, "INS-98901_TRANSX": [0.0, 0.04, 0.0], "INS-98901_TRANSY": [0.04, 0.0, 0.04], "TKFRAME_-98901_UNITS": "DEGREES", "INS-98901_PIXEL_PITCH": 0.04, "INS-98901_FOCAL_LENGTH": 644.486, "FRAME_-98901_NAME": "ISIS_NH_RALPH_LEISA", "INS-98901_ITRANSL": [-1.0, 0.0, 25.0], "FRAME_-98901_CLASS_ID": -98901.0, "INS-98901_ITRANSS": [0.0, 25.0, 0.0], "TKFRAME_-98901_ANGLES": [0.0, 90.0, 0.0], "TKFRAME_-98901_AXES": [1.0, 2.0, 3.0], "TKFRAME_-98901_SPEC": "ANGLES", "FRAME_-98901_CENTER": -98.0, "TKFRAME_-98901_RELATIVE": "NH_RALPH_LEISA", "BODY999_PM": [302.695, 56.3625225, 0.0], "BODY999_POLE_RA": [132.993, 0.0, 0.0], "BODY999_POLE_DEC": [-6.163, 0.0, 0.0], "BODY999_LONG_AXIS": 0.0}, "detector_sample_summing": 1, "detector_line_summing": 1, "focal_length_model": {"focal_length": 644.486}, "detector_center": {"line": 0, "sample": 0}, "starting_detector_line": 0, "starting_detector_sample": 0, "focal2pixel_lines": [-1.0, 0.0, 25.0], "focal2pixel_samples": [0.0, 25.0, 0.0], "optical_distortion": {"radial": {"coefficients": [0.0, 0.0, 0.0]}}, "instrument_position": {"spk_table_start_time": 487928588.0336103, "spk_table_end_time": 487928903.0416103, "spk_table_original_size": 5, "ephemeris_times": [487928588.0336103, 487928666.7856103, 487928745.5376103, 487928824.28961027, 487928903.0416103], "positions": [[-341893.24779512954, 29580910.11543975, 7720840.361930893], [-341880.7808565185, 29579860.06353102, 7720567.900988498], [-341868.3126599601, 29578810.0127891, 7720295.440004236], [-341855.84320573055, 29577759.963213038, 7720022.97897548], [-341843.3724940212, 29576709.914803796, 7719750.517901289]], "velocities": [[0.15829834503568418, -13.333661290526594, -3.4597333648983915], [0.15831431122399225, -13.333646475303395, -3.4597338961260395], [0.15833027850391226, -13.333631661980828, -3.459734446767892], [0.1583462468689395, -13.333616850568019, -3.4597350168341365], [0.15836221629776992, -13.333602041080965, -3.4597356063085027]], "reference_frame": 1}, "sun_position": {"spk_table_start_time": 487928745.5376103, "spk_table_end_time": 487928745.5376103, "spk_table_original_size": 1, "ephemeris_times": [487928745.5376103], "positions": [[-1185193768.8247254, 4445753700.58781, 1744608601.9356186]], "velocities": [[-5.384652174342424, -0.7983186588678509, 1.3987335963733494]], "reference_frame": 1}}
 No newline at end of file
Loading