Commit 910a04f2 authored by acpaquette's avatar acpaquette Committed by GitHub
Browse files

Kaguya and LRO Image Inversion Changes, and HRSC Update (#329)

* Added adjustments for inverted kaguyaTc images

* Updated mex driver

* Updated kaguya and mex tests associated with driver updates

* Updated LRO with a flipped focal2pixel lines if the spacecraft is going backwards

* Updated lro driver to get the correct direction of the spacecraft

* Update the lro and kaguya tests

* Added a doc string to the kaguya spacecraft_direction property
parent a500e0c0
Loading
Loading
Loading
Loading
+91 −0
Original line number Diff line number Diff line
@@ -129,6 +129,24 @@ class LroLrocPds3LabelNaifSpiceDriver(LineScanner, NaifSpice, Pds3Label, Driver)
        """
        return super().detector_center_sample - 0.5

    @property
    def focal2pixel_lines(self):
        """
        Expects ikid to be defined. This must be the integer Naif id code of
        the instrument. For LROC NAC this is flipped depending on the spacecraft
        direction.

        Returns
        -------
        : list<double>
          focal plane to detector lines
        """
        focal2pixel_lines = np.array(list(spice.gdpool('INS{}_ITRANSL'.format(self.ikid), 0, 3)))
        if self.spacecraft_direction < 0:
            return focal2pixel_lines
        else:
            return -focal2pixel_lines

    @property
    def ephemeris_start_time(self):
        """
@@ -222,6 +240,34 @@ class LroLrocPds3LabelNaifSpiceDriver(LineScanner, NaifSpice, Pds3Label, Driver)
        """
        return self.crosstrack_summing

    @property
    def spacecraft_direction(self):
        """
        Returns the x axis of the first velocity vector relative to the
        spacecraft. This indicates of the craft is moving forwards or backwards.

        From LROC Frame Kernel: lro_frames_2014049_v01.tf
        "+X axis is in the direction of the velocity vector half the year. The
        other half of the year, the +X axis is opposite the velocity vector"

        Hence we rotate the first velocity vector into the sensor reference
        frame, but the X component of that vector is inverted compared to the
        spacecraft so a +X indicates backwards and -X indicates forwards

        The returned velocity is also slightly off from the spacecraft velocity
        due to the sensor being attached to the craft with wax.

        Returns
        -------
        direction : double
                    X value of the first velocity relative to the sensor
        """
        rotation = self.frame_chain.compute_rotation(self.target_frame_id, self.sensor_frame_id)
        positions, velocities, times = self.sensor_position
        velocity = rotation.rotate_velocity_at([positions[0]], [velocities[0]], [times[0]])[0]
        return velocity[0]



class LroLrocIsisLabelNaifSpiceDriver(LineScanner, NaifSpice, IsisLabel, Driver):
    @property
@@ -343,6 +389,24 @@ class LroLrocIsisLabelNaifSpiceDriver(LineScanner, NaifSpice, IsisLabel, Driver)
         """
        return super().exposure_duration * (1 + self.multiplicative_line_error) + self.additive_line_error

    @property
    def focal2pixel_lines(self):
        """
        Expects ikid to be defined. This must be the integer Naif id code of
        the instrument. For LROC NAC this is flipped depending on the spacecraft
        direction.

        Returns
        -------
        : list<double>
          focal plane to detector lines
        """
        focal2pixel_lines = np.array(list(spice.gdpool('INS{}_ITRANSL'.format(self.ikid), 0, 3)))
        if self.spacecraft_direction < 0:
            return focal2pixel_lines
        else:
            return -focal2pixel_lines

    @property
    def multiplicative_line_error(self):
        """
@@ -404,3 +468,30 @@ class LroLrocIsisLabelNaifSpiceDriver(LineScanner, NaifSpice, IsisLabel, Driver)
          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 spacecraft_direction(self):
        """
        Returns the x axis of the first velocity vector relative to the
        spacecraft. This indicates of the craft is moving forwards or backwards.

        From LROC Frame Kernel: lro_frames_2014049_v01.tf
        "+X axis is in the direction of the velocity vector half the year. The
        other half of the year, the +X axis is opposite the velocity vector"

        Hence we rotate the first velocity vector into the sensor reference
        frame, but the X component of that vector is inverted compared to the
        spacecraft so a +X indicates backwards and -X indicates forwards

        The returned velocity is also slightly off from the spacecraft velocity
        due to the sensor being attached to the craft with wax.

        Returns
        -------
        direction : double
                    X value of the first velocity relative to the sensor
        """
        rotation = self.frame_chain.compute_rotation(self.target_frame_id, self.sensor_frame_id)
        positions, velocities, times = self.sensor_position
        velocity = rotation.rotate_velocity_at([positions[0]], [velocities[0]], [times[0]])[0]
        return velocity[0]
+0 −11
Original line number Diff line number Diff line
@@ -270,17 +270,6 @@ class MexHrscPds3NaifSpiceDriver(LineScanner, Pds3Label, NaifSpice, RadialDistor
        return FILTER_SPECIFIC_LOOKUP[self.fikid][2]


    @property
    def detector_start_line(self):
        """
        Returns
        -------
        : int
          Detector sample corresponding to the first image sample
        """
        return 1


    @property
    def detector_center_line(self):
        """
+26 −3
Original line number Diff line number Diff line
@@ -245,7 +245,10 @@ class KaguyaTcPds3NaifSpiceDriver(LineScanner, Pds3Label, NaifSpice, Driver):
          focal plane to detector lines
        """
        pixel_size = spice.gdpool('INS{}_PIXEL_SIZE'.format(self.ikid), 0, 1)[0]
        if self.spacecraft_direction < 0:
            return [0, -1/pixel_size, 0]
        elif self.spacecraft_direction > 0:
            return [0, 1/pixel_size, 0]


    @property
@@ -306,7 +309,7 @@ class KaguyaTcPds3NaifSpiceDriver(LineScanner, Pds3Label, NaifSpice, Driver):
        return spice.gdpool('INS{}_BORESIGHT'.format(self.ikid), 1, 1)[0]

    @property
    def line_exposure_duration(self):
    def exposure_duration(self):
        """
        Returns Line Exposure Duration

@@ -432,7 +435,27 @@ class KaguyaTcPds3NaifSpiceDriver(LineScanner, Pds3Label, NaifSpice, Driver):
        : int
          Detector sample corresponding to the first image sample
        """
        return self.label["FIRST_PIXEL_NUMBER"]
        return self.label["FIRST_PIXEL_NUMBER"] - .5

    @property
    def detector_start_line(self):
        if self.spacecraft_direction < 0:
            return super().detector_start_line
        elif self.spacecraft_direction > 0:
            return 1

    @property
    def spacecraft_direction(self):
        """
        Gets the moving direction of the spacecraft from the label, where -1 is moving
        as intended and 1 is moving inverted.

        Returns
        -------
        : int
          Moving direction of the spacecraft
        """
        return int(self.label['SATELLITE_MOVING_DIRECTION'])


    @property
+67 −57
Original line number Diff line number Diff line
@@ -6,7 +6,8 @@ import spiceypy as spice
from importlib import reload
import json

from unittest.mock import PropertyMock, patch
import unittest
from unittest.mock import MagicMock, PropertyMock, patch

from conftest import get_image_label, get_image_kernels, convert_kernels, compare_dicts

@@ -54,9 +55,9 @@ image_dict = {
            'detector_center': {
                'line': 0.5,
                'sample': 2048.0},
            'starting_detector_line': 0,
            'starting_detector_sample': 1,
            'focal2pixel_lines': [0, -142.85714285714286, 0],
            'starting_detector_line': 1,
            'starting_detector_sample': .5,
            'focal2pixel_lines': [0, 142.85714285714286, 0],
            'focal2pixel_samples': [0, 0, -142.85714285714286],
            'optical_distortion': {
                'kaguyalism': {
@@ -216,72 +217,81 @@ def test_kaguya_load(test_kernels, label_type, formatter, image):

    assert compare_dicts(usgscsm_isd_obj, image_dict[image][formatter]) == []

@pytest.fixture(params=["Pds3NaifDriver"])
def driver(request):
    if request.param == "Pds3NaifDriver":
# ========= Test pdslabel and naifspice driver =========
class test_pds_naif(unittest.TestCase):

    def setUp(self):
        label = get_image_label("TC1S2B0_01_06691S820E0465", "pds3")
        return KaguyaTcPds3NaifSpiceDriver(label)
        self.driver = KaguyaTcPds3NaifSpiceDriver(label)

def test_short_mission_name(driver):
    assert driver.short_mission_name == 'selene'
    def test_short_mission_name(self):
        assert self.driver.short_mission_name == 'selene'

def test_utc_start_time(driver):
    assert driver.utc_start_time == datetime.datetime(2009, 4, 5, 20, 9, 53, 607478, tzinfo=datetime.timezone.utc)
    def test_utc_start_time(self):
        assert self.driver.utc_start_time == datetime.datetime(2009, 4, 5, 20, 9, 53, 607478, tzinfo=datetime.timezone.utc)

def test_utc_stop_time(driver):
    assert driver.utc_stop_time == datetime.datetime(2009, 4, 5, 20, 10, 23, 864978, tzinfo=datetime.timezone.utc)
    def test_utc_stop_time(self):
        assert self.driver.utc_stop_time == datetime.datetime(2009, 4, 5, 20, 10, 23, 864978, tzinfo=datetime.timezone.utc)

def test_instrument_id(driver):
    assert driver.instrument_id == 'LISM_TC1_STF'
    def test_instrument_id(self):
        assert self.driver.instrument_id == 'LISM_TC1_STF'

def test_sensor_frame_id(driver):
    def test_sensor_frame_id(self):
        with patch('ale.drivers.selene_drivers.spice.namfrm', return_value=12345) as namfrm:
        assert driver.sensor_frame_id == 12345
            assert self.driver.sensor_frame_id == 12345
            namfrm.assert_called_with('LISM_TC1_HEAD')

def test_instrument_host_name(driver):
    assert driver.instrument_host_name == 'SELENE-M'
    def test_instrument_host_name(self):
        assert self.driver.instrument_host_name == 'SELENE-M'

def test_ikid(driver):
    def test_ikid(self):
        with patch('ale.drivers.selene_drivers.spice.bods2c', return_value=12345) as bods2c:
        assert driver.ikid == 12345
            assert self.driver.ikid == 12345
            bods2c.assert_called_with('LISM_TC1')

def test_spacecraft_name(driver):
    assert driver.spacecraft_name == 'SELENE'
    def test_spacecraft_name(self):
        assert self.driver.spacecraft_name == 'SELENE'

def test_spacecraft_clock_start_count(driver):
    assert driver.spacecraft_clock_start_count == 922997380.174174
    def test_spacecraft_clock_start_count(self):
        assert self.driver.spacecraft_clock_start_count == 922997380.174174

def test_spacecraft_clock_stop_count(driver):
    assert driver.spacecraft_clock_stop_count == 922997410.431674
    def test_spacecraft_clock_stop_count(self):
        assert self.driver.spacecraft_clock_stop_count == 922997410.431674

def test_ephemeris_start_time(driver):
    def test_ephemeris_start_time(self):
        with patch('ale.drivers.selene_drivers.spice.sct2e', return_value=12345) as sct2e, \
             patch('ale.drivers.selene_drivers.spice.bods2c', return_value=-12345) as bods2c:
        assert driver.ephemeris_start_time == 12345
            assert self.driver.ephemeris_start_time == 12345
            sct2e.assert_called_with(-12345, 922997380.174174)

def test_detector_center_line(driver):
    def test_detector_center_line(self):
        with patch('ale.drivers.selene_drivers.spice.gdpool', return_value=np.array([54321, 12345])) as gdpool, \
             patch('ale.drivers.selene_drivers.spice.bods2c', return_value=-12345) as bods2c:
        assert driver.detector_center_line == 12344.5
            assert self.driver.detector_center_line == 12344.5
            gdpool.assert_called_with('INS-12345_CENTER', 0, 2)

def test_detector_center_sample(driver):
    def test_detector_center_sample(self):
        with patch('ale.drivers.selene_drivers.spice.gdpool', return_value=np.array([54321, 12345])) as gdpool, \
             patch('ale.drivers.selene_drivers.spice.bods2c', return_value=-12345) as bods2c:
        assert driver.detector_center_sample == 54320.5
            assert self.driver.detector_center_sample == 54320.5
            gdpool.assert_called_with('INS-12345_CENTER', 0, 2)

def test_focal2pixel_samples(driver):
    def test_focal2pixel_samples(self):
        with patch('ale.drivers.selene_drivers.spice.gdpool', return_value=np.array([2])) as gdpool, \
             patch('ale.drivers.selene_drivers.spice.bods2c', return_value=-12345) as bods2c:
        assert driver.focal2pixel_samples == [0, 0, -1/2]
            assert self.driver.focal2pixel_samples == [0, 0, -1/2]
            gdpool.assert_called_with('INS-12345_PIXEL_SIZE', 0, 1)

def test_focal2pixel_lines(driver):
    def test_focal2pixel_lines(self):
        with patch('ale.drivers.selene_drivers.spice.gdpool', return_value=np.array([2])) as gdpool, \
         patch('ale.drivers.selene_drivers.spice.bods2c', return_value=-12345) as bods2c:
        assert driver.focal2pixel_lines == [0, -1/2, 0]
             patch('ale.drivers.selene_drivers.spice.bods2c', return_value=-12345) as bods2c, \
             patch('ale.drivers.selene_drivers.KaguyaTcPds3NaifSpiceDriver.spacecraft_direction', \
             new_callable=PropertyMock) as spacecraft_direction:
            spacecraft_direction.return_value = 1
            assert self.driver.focal2pixel_lines == [0, 1/2, 0]
            spacecraft_direction.return_value = -1
            assert self.driver.focal2pixel_lines == [0, -1/2, 0]
            gdpool.assert_called_with('INS-12345_PIXEL_SIZE', 0, 1)

    def test_spacecraft_direction(self):
        assert self.driver.spacecraft_direction == 1
+115 −54
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@ import pytest
import numpy as np
import os
import unittest
from unittest.mock import PropertyMock, patch
from unittest.mock import MagicMock, PropertyMock, patch
import spiceypy as spice
import json

@@ -10,6 +10,7 @@ import ale
from ale import util
from ale.drivers.lro_drivers import LroLrocPds3LabelNaifSpiceDriver
from ale.drivers.lro_drivers import LroLrocIsisLabelNaifSpiceDriver
from ale.transformation import TimeDependentRotation

from conftest import get_image_label, get_image_kernels, convert_kernels, compare_dicts

@@ -200,43 +201,54 @@ def test_kernels():
        for kern in kern_list:
            os.remove(kern)

@pytest.fixture(params=["Pds3NaifDriver"])
def driver(request):
    if request.param == "Pds3NaifDriver":
        label = get_image_label("M103595705LE", "pds3")
        return LroLrocPds3LabelNaifSpiceDriver(label)
@pytest.mark.parametrize("label_type", ['isis3'])
@pytest.mark.parametrize("formatter", ['isis'])
@pytest.mark.parametrize("image", image_dict.keys())
def test_load_isis(test_kernels, label_type, formatter, image):
    label_file = get_image_label(image, label_type)
    isis_isd = ale.loads(label_file, props={'kernels': test_kernels[image]}, formatter=formatter, verbose=True)
    isis_isd_obj = json.loads(isis_isd)
    print(json.dumps(isis_isd_obj, indent=4))
    assert compare_dicts(isis_isd_obj, image_dict[image][formatter]) == []

def test_short_mission_name(driver):
    assert driver.short_mission_name=='lro'
# ========= Test pdslabel and naifspice driver =========
class test_pds_naif(unittest.TestCase):

def test_instrument_id_left(driver):
    driver.label['FRAME_ID'] = 'LEFT'
    assert driver.instrument_id == 'LRO_LROCNACL'
    def setUp(self):
        label = get_image_label('M103595705LE', 'pds3')
        self.driver = LroLrocPds3LabelNaifSpiceDriver(label)

    def test_short_mission_name(self):
        assert self.driver.short_mission_name=='lro'

def test_instrument_id_right(driver):
    driver.label['FRAME_ID'] = 'RIGHT'
    assert driver.instrument_id == 'LRO_LROCNACR'
    def test_instrument_id_left(self):
        self.driver.label['FRAME_ID'] = 'LEFT'
        assert self.driver.instrument_id == 'LRO_LROCNACL'

    def test_instrument_id_right(self):
        self.driver.label['FRAME_ID'] = 'RIGHT'
        assert self.driver.instrument_id == 'LRO_LROCNACR'

def test_spacecraft_name(driver):
    assert driver.spacecraft_name == 'LRO'
    def test_spacecraft_name(self):
        assert self.driver.spacecraft_name == 'LRO'

def test_sensor_model_version(driver):
    assert driver.sensor_model_version == 2
    def test_sensor_model_version(self):
        assert self.driver.sensor_model_version == 2

def test_odtk(driver):
    def test_odtk(self):
        with patch('ale.drivers.lro_drivers.spice.gdpool', return_value=np.array([1.0])) as gdpool, \
             patch('ale.drivers.lro_drivers.spice.bods2c', return_value=-12345) as bods2c:
        assert driver.odtk == [1.0]
            assert self.driver.odtk == [1.0]
            gdpool.assert_called_with('INS-12345_OD_K', 0, 1)

def test_usgscsm_distortion_model(driver):
    def test_usgscsm_distortion_model(self):
        with patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.odtk', \
                   new_callable=PropertyMock) as odtk:
            odtk.return_value = [1.0]
        distortion_model = driver.usgscsm_distortion_model
            distortion_model = self.driver.usgscsm_distortion_model
            assert distortion_model['lrolrocnac']['coefficients'] == [1.0]

def test_ephemeris_start_time(driver):
    def test_ephemeris_start_time(self):
        with patch('ale.drivers.lro_drivers.spice.scs2e', return_value=5) as scs2e, \
             patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.exposure_duration', \
                   new_callable=PropertyMock) as exposure_duration, \
@@ -244,24 +256,44 @@ def test_ephemeris_start_time(driver):
                   new_callable=PropertyMock) as spacecraft_id:
            exposure_duration.return_value = 0.1
            spacecraft_id.return_value = 1234
        assert driver.ephemeris_start_time == 107.4
            assert self.driver.ephemeris_start_time == 107.4
            scs2e.assert_called_with(1234, "1/270649237:07208")

def test_exposure_duration(driver):
    def test_exposure_duration(self):
        with patch('ale.base.label_pds3.Pds3Label.exposure_duration', \
                   new_callable=PropertyMock) as exposure_duration:
            exposure_duration.return_value = 1
        assert driver.exposure_duration == 1.0045
            assert self.driver.exposure_duration == 1.0045

    @patch('ale.transformation.FrameChain')
    @patch('ale.transformation.FrameChain.from_spice', return_value=ale.transformation.FrameChain())
    @patch('ale.transformation.FrameChain.compute_rotation', return_value=TimeDependentRotation([[0, 0, 1, 0]], [0], 0, 0))
    def test_spacecraft_direction(self, compute_rotation, from_spice, frame_chain):
        with patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.target_frame_id', \
             new_callable=PropertyMock) as target_frame_id, \
             patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.sensor_frame_id', \
             new_callable=PropertyMock) as sensor_frame_id, \
             patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.ephemeris_start_time', \
             new_callable=PropertyMock) as ephemeris_start_time, \
             patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.ephemeris_stop_time', \
             new_callable=PropertyMock) as ephemeris_end_time, \
             patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.sensor_position', \
             new_callable=PropertyMock) as sensor_position:
            sensor_position.return_value = [[np.array([50, 50, 50])], [np.array([1, 1, 1])], [0]]
            assert self.driver.spacecraft_direction < 0
            compute_rotation.assert_called_with(target_frame_id.return_value, sensor_frame_id.return_value)

    def test_focal2pixel_lines(self):
        with patch('ale.drivers.lro_drivers.spice.gdpool', return_value=[0, 1, 0]) as gdpool, \
             patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.ikid', \
             new_callable=PropertyMock) as ikid, \
             patch('ale.drivers.lro_drivers.LroLrocPds3LabelNaifSpiceDriver.spacecraft_direction', \
             new_callable=PropertyMock) as spacecraft_direction:
            spacecraft_direction.return_value = -1
            np.testing.assert_array_equal(self.driver.focal2pixel_lines, [0, 1, 0])
            spacecraft_direction.return_value = 1
            np.testing.assert_array_equal(self.driver.focal2pixel_lines, [0, -1, 0])

@pytest.mark.parametrize("label_type", ['isis3'])
@pytest.mark.parametrize("formatter", ['isis'])
@pytest.mark.parametrize("image", image_dict.keys())
def test_load_isis(test_kernels, label_type, formatter, image):
    label_file = get_image_label(image, label_type)
    isis_isd = ale.loads(label_file, props={'kernels': test_kernels[image]}, formatter=formatter, verbose=True)
    isis_isd_obj = json.loads(isis_isd)
    print(json.dumps(isis_isd_obj, indent=4))
    assert compare_dicts(isis_isd_obj, image_dict[image][formatter]) == []

# ========= Test isislabel and naifspice driver =========
class test_isis_naif(unittest.TestCase):
@@ -323,3 +355,32 @@ class test_isis_naif(unittest.TestCase):

    def test_sampling_factor(self):
        assert self.driver.sampling_factor == 1

    @patch('ale.transformation.FrameChain')
    @patch('ale.transformation.FrameChain.from_spice', return_value=ale.transformation.FrameChain())
    @patch('ale.transformation.FrameChain.compute_rotation', return_value=TimeDependentRotation([[0, 0, 1, 0]], [0], 0, 0))
    def test_spacecraft_direction(self, compute_rotation, from_spice, frame_chain):
        with patch('ale.drivers.lro_drivers.LroLrocIsisLabelNaifSpiceDriver.target_frame_id', \
             new_callable=PropertyMock) as target_frame_id, \
             patch('ale.drivers.lro_drivers.LroLrocIsisLabelNaifSpiceDriver.sensor_frame_id', \
             new_callable=PropertyMock) as sensor_frame_id, \
             patch('ale.drivers.lro_drivers.LroLrocIsisLabelNaifSpiceDriver.ephemeris_start_time', \
             new_callable=PropertyMock) as ephemeris_start_time, \
             patch('ale.drivers.lro_drivers.LroLrocIsisLabelNaifSpiceDriver.ephemeris_stop_time', \
             new_callable=PropertyMock) as ephemeris_end_time, \
             patch('ale.drivers.lro_drivers.LroLrocIsisLabelNaifSpiceDriver.sensor_position', \
             new_callable=PropertyMock) as sensor_position:
            sensor_position.return_value = [[np.array([50, 50, 50])], [np.array([1, 1, 1])], [0]]
            assert self.driver.spacecraft_direction < 0
            compute_rotation.assert_called_with(target_frame_id.return_value, sensor_frame_id.return_value)

    def test_focal2pixel_lines(self):
        with patch('ale.drivers.lro_drivers.spice.gdpool', return_value=[0, 1, 0]) as gdpool, \
             patch('ale.drivers.lro_drivers.LroLrocIsisLabelNaifSpiceDriver.ikid', \
             new_callable=PropertyMock) as ikid, \
             patch('ale.drivers.lro_drivers.LroLrocIsisLabelNaifSpiceDriver.spacecraft_direction', \
             new_callable=PropertyMock) as spacecraft_direction:
            spacecraft_direction.return_value = -1
            np.testing.assert_array_equal(self.driver.focal2pixel_lines, [0, 1, 0])
            spacecraft_direction.return_value = 1
            np.testing.assert_array_equal(self.driver.focal2pixel_lines, [0, -1, 0])
Loading