From 246ae894592f56f719260b86ce608dbcf26bd0d2 Mon Sep 17 00:00:00 2001
From: AustinSanders <austinsanders1993@gmail.com>
Date: Wed, 4 May 2022 14:31:38 -0700
Subject: [PATCH] Add filter-specific calculations and tests (#450)

* Added filter-specific calculations and tests

* Addressed PR feedback
---
 ale/drivers/lro_drivers.py        | 159 +++++++++++++++++++++++++++++-
 tests/pytests/test_lro_drivers.py |  62 ++++++------
 2 files changed, 190 insertions(+), 31 deletions(-)

diff --git a/ale/drivers/lro_drivers.py b/ale/drivers/lro_drivers.py
index 94b3e41..5eb3f72 100644
--- a/ale/drivers/lro_drivers.py
+++ b/ale/drivers/lro_drivers.py
@@ -892,6 +892,44 @@ class LroLrocWacIsisLabelIsisSpiceDriver(PushFrame, IsisLabel, IsisSpice, Radial
         return 2
 
 
+    @property
+    def filter_number(self):
+        """
+        Return the filter number from the cub label
+
+        Returns
+        -------
+        : int
+          The filter number
+        """
+        try:
+            return self.label['IsisCube']['BandBin']['FilterNumber'][0]
+        except:
+            return self.label['IsisCube']['BandBin']['FilterNumber']
+
+
+    @property
+    def fikid(self):
+        """
+        Naif ID code of the filter dependent instrument codes.
+
+        Expects ikid to be defined. This should be the integer Naif ID code for
+        the instrument.
+
+        Returns
+        -------
+        : int
+          Naif ID code used in calculating focal length
+        """
+        if self.instrument_id == "LRO_LROCWAC_UV":
+            base = -85640
+        elif self.instrument_id == "LRO_LROCWAC_VIS":
+            base = -85630
+
+        fikid = base - self.filter_number
+        return fikid
+
+
     @property
     def odtk(self):
         """
@@ -902,7 +940,9 @@ class LroLrocWacIsisLabelIsisSpiceDriver(PushFrame, IsisLabel, IsisSpice, Radial
         : list
           Radial distortion coefficients.
         """
-        return [self.naif_keywords.get('INS{}_OD_K'.format(self.ikid), None)]
+        coeffs = self.naif_keywords.get('INS{}_OD_K'.format(self.fikid), None)
+        coeffs = [x * -1 for x in coeffs]
+        return coeffs
 
 
     @property
@@ -989,6 +1029,12 @@ class LroLrocWacIsisLabelNaifSpiceDriver(PushFrame, IsisLabel, NaifSpice, Radial
 
     @property
     def sensor_name(self):
+        """
+        Returns
+        -------
+        : String
+          The name of the spacecraft
+        """
         return self.label['IsisCube']['Instrument']['SpacecraftName']
 
 
@@ -1002,7 +1048,9 @@ class LroLrocWacIsisLabelNaifSpiceDriver(PushFrame, IsisLabel, NaifSpice, Radial
         : list
           Radial distortion coefficients.
         """
-        return spice.gdpool('INS{}_OD_K'.format(self.ikid), 0, 3).tolist()
+        coeffs = spice.gdpool('INS{}_OD_K'.format(self.fikid), 0, 3).tolist()
+        coeffs = [x * -1 for x in coeffs]
+        return coeffs
 
 
     @property
@@ -1028,7 +1076,13 @@ class LroLrocWacIsisLabelNaifSpiceDriver(PushFrame, IsisLabel, NaifSpice, Radial
 
     @property
     def framelets_flipped(self):
-        return self.label['IsisCube']['Instrument']['SpacecraftName'] == "Yes"
+        """
+        Returns
+        -------
+        : boolean
+          True if framelets are flipped, else false
+        """
+        return self.label['IsisCube']['Instrument']['DataFlipped'] == "Yes"
 
 
     @property
@@ -1041,12 +1095,111 @@ class LroLrocWacIsisLabelNaifSpiceDriver(PushFrame, IsisLabel, NaifSpice, Radial
 
     @property
     def num_frames(self):
+        """
+        Number of frames in the image
+
+        Returns
+        -------
+        : int
+          Number of frames in the image
+        """
         return self.image_lines // (self.framelet_height // self.sampling_factor)
 
 
     @property
     def framelet_height(self):
+        """
+        Return the number of lines in a framelet.
+
+        Returns
+        -------
+        : int
+          The number of lines in a framelet
+        """
         if self.instrument_id == "LRO_LROCWAC_UV":
             return 16
         elif self.instrument_id == "LRO_LROCWAC_VIS":
             return 14
+
+
+    @property
+    def filter_number(self):
+        """
+        Return the filter number from the cub label
+
+        Returns
+        -------
+        : int
+          The filter number
+        """
+        try:
+            return self.label['IsisCube']['BandBin']['FilterNumber'][0]
+        except:
+            return self.label['IsisCube']['BandBin']['FilterNumber']
+
+
+    @property
+    def fikid(self):
+        """
+        Naif ID code of the filter dependent instrument codes.
+
+        Expects ikid to be defined. This should be the integer Naif ID code for
+        the instrument.
+
+        Returns
+        -------
+        : int
+          Naif ID code used in calculating focal length
+        """
+        if self.instrument_id == "LRO_LROCWAC_UV":
+            base = -85640
+        elif self.instrument_id == "LRO_LROCWAC_VIS":
+            base = -85630
+
+        fikid = base - self.filter_number
+        return fikid
+
+
+    @property
+    def pixel2focal_x(self):
+        """
+        Expects fikid to be defined. This must be the integer Naif id code of the filter
+
+        Returns
+        -------
+        : list<double>
+        detector to focal plane x
+        """
+        return list(spice.gdpool('INS{}_TRANSX'.format(self.fikid), 0, 3))
+
+
+    @property
+    def pixel2focal_y(self):
+        """
+        Expects fikid to be defined. This must be the integer Naif id code of the filter
+
+        Returns
+        -------
+        : list<double>
+        detector to focal plane y
+        """
+        return list(spice.gdpool('INS{}_TRANSY'.format(self.fikid), 0, 3))
+
+
+    @property
+    def detector_start_line(self):
+        """
+        Filter-specific starting line
+
+        Returns
+        -------
+        : int
+          Zero based Detector line corresponding to the first image line
+        """
+        offset = list(spice.gdpool('INS{}_FILTER_OFFSET'.format(self.fikid), 0, 3))
+        try:
+            # If multiple items are present, use the first one
+            offset = offset[0]
+        except (IndexError, TypeError):
+            pass
+        return super().detector_start_line + offset
diff --git a/tests/pytests/test_lro_drivers.py b/tests/pytests/test_lro_drivers.py
index 0bac2f7..e16d1d6 100644
--- a/tests/pytests/test_lro_drivers.py
+++ b/tests/pytests/test_lro_drivers.py
@@ -276,21 +276,17 @@ class test_wac_isis_naif(unittest.TestCase):
         label = get_image_label('wac0000a1c4.uv.even', 'isis3')
         self.driver = LroLrocWacIsisLabelNaifSpiceDriver(label)
 
-
     def test_short_mission_name(self):
         assert self.driver.short_mission_name == 'lro'
 
-
     def test_intrument_id(self):
         assert self.driver.instrument_id == 'LRO_LROCWAC_UV'
 
-
     def test_ephemeris_start_time(self):
         with patch('ale.drivers.lro_drivers.spice.scs2e', return_value=321) as scs2e:
             np.testing.assert_almost_equal(self.driver.ephemeris_start_time, 321)
             scs2e.assert_called_with(-85, '1/274692469:15073')
 
-
     def test_detector_center_sample(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:
@@ -298,7 +294,6 @@ class test_wac_isis_naif(unittest.TestCase):
             gdpool.assert_called_with('INS-12345_BORESIGHT_SAMPLE', 0, 1)
             bods2c.assert_called_with('LRO_LROCWAC_UV')
 
-
     def test_detector_center_line(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:
@@ -306,51 +301,53 @@ class test_wac_isis_naif(unittest.TestCase):
             gdpool.assert_called_with('INS-12345_BORESIGHT_LINE', 0, 1)
             bods2c.assert_called_with('LRO_LROCWAC_UV')
 
-
-    def test_usgscsm_distortion_model(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:
-            distortion_model = self.driver.usgscsm_distortion_model
-            assert distortion_model['radial']['coefficients'] == [1.0]
-            gdpool.assert_called_with('INS-12345_OD_K', 0, 3)
-            bods2c.assert_called_with('LRO_LROCWAC_UV')
-
-
     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 self.driver.odtk == [1.0]
-             gdpool.assert_called_with('INS-12345_OD_K', 0, 3)
-             bods2c.assert_called_with('LRO_LROCWAC_UV')
-
+        with patch('ale.drivers.lro_drivers.spice.gdpool', return_value=np.array([1.0])) as gdpool:
+             assert self.driver.odtk == [-1.0]
+             gdpool.assert_called_with('INS-85641_OD_K', 0, 3)
 
     def test_light_time_correction(self):
         assert self.driver.light_time_correction == 'LT+S'
 
-
     def test_exposure_duration(self):
         np.testing.assert_almost_equal(self.driver.exposure_duration, 0.04)
 
-
     def test_sensor_name(self):
         assert self.driver.sensor_name == "LUNAR RECONNAISSANCE ORBITER"
 
-
     def test_framelets_flipped(self):
         assert self.driver.framelets_flipped == False
 
-
     def test_sampling_factor(self):
         assert self.driver.sampling_factor == 4
 
-
     def test_num_frames(self):
         assert self.driver.num_frames == 260
 
-
     def test_framelet_height(self):
         assert self.driver.framelet_height == 16
 
+    def test_filter_number(self):
+        assert self.driver.filter_number == 1
+
+    def test_fikid(self):
+        assert self.driver.fikid == -85641
+
+    def test_pixel2focal_x(self):
+        with patch('ale.drivers.lro_drivers.spice.gdpool', return_value=np.array([0, 0, -0.009])) as gdpool:
+             assert self.driver.pixel2focal_x == [0, 0, -0.009]
+             gdpool.assert_called_with('INS-85641_TRANSX', 0, 3)
+
+    def test_pixel2focal_y(self):
+        with patch('ale.drivers.lro_drivers.spice.gdpool', return_value=np.array([0, 0.009, 0])) as gdpool:
+             assert self.driver.pixel2focal_y == [0, 0.009, 0]
+             gdpool.assert_called_with('INS-85641_TRANSY', 0, 3)
+
+    def test_detector_start_line(self):
+        with patch('ale.drivers.lro_drivers.spice.gdpool', return_value=np.array([244])) as gdpool:
+             assert self.driver.detector_start_line == 244
+             gdpool.assert_called_with('INS-85641_FILTER_OFFSET', 0, 3)
+
 # ========= Test WAC isislabel and isis spice driver =========
 class test_wac_isis_isis(unittest.TestCase):
 
@@ -368,4 +365,13 @@ class test_wac_isis_isis(unittest.TestCase):
         np.testing.assert_almost_equal(self.driver.exposure_duration, 0.04)
 
     def test_usgscsm_distortion_model(self):
-        assert self.driver.usgscsm_distortion_model == {'radial': {'coefficients': [[-0.024, -0.0007]]}}
+        assert self.driver.usgscsm_distortion_model == {'radial': {'coefficients': [-0.0258246, -4.66139e-05, -0.000144651]}}
+
+    def test_filter_number(self):
+        assert self.driver.filter_number == 1
+
+    def test_fikid(self):
+        assert self.driver.fikid == -85641
+
+    def test_odtk(self):
+        assert self.driver.odtk == [-0.0258246, -4.66139e-05, -0.000144651]
-- 
GitLab