From 0ba7b24e2f71317a98eb24ceb49b989360ec08c3 Mon Sep 17 00:00:00 2001
From: Austin Sanders <arsanders@usgs.gov>
Date: Thu, 13 Jun 2024 13:01:21 -0600
Subject: [PATCH] M3 PDS3 NaifSpice Driver (#608)

* Chandrayaan m3 PDS3 NaifSpice Driver and tests

* Chandrayaan 1 M3 PDS test data

* Removed incorrect docstring return information

* renamed LBL to lbl for case-sensitive OS

* Updated docstrings
---
 ale/drivers/chandrayaan_drivers.py            | 203 +++++++++
 ...3T20090630T083407_V03_L1B_cropped_pds3.lbl | 178 ++++++++
 .../M3T20090630T083407_V03_TIM_cropped.TAB    |   5 +
 .../data/isds/chandrayaan_m3_nadir_isd.json   | 418 ++++++++++++++++++
 tests/pytests/test_chandrayaan_driver.py      |  70 ++-
 5 files changed, 867 insertions(+), 7 deletions(-)
 create mode 100644 tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_L1B_cropped_pds3.lbl
 create mode 100644 tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_TIM_cropped.TAB
 create mode 100644 tests/pytests/data/isds/chandrayaan_m3_nadir_isd.json

diff --git a/ale/drivers/chandrayaan_drivers.py b/ale/drivers/chandrayaan_drivers.py
index 86bb108..a98f874 100644
--- a/ale/drivers/chandrayaan_drivers.py
+++ b/ale/drivers/chandrayaan_drivers.py
@@ -3,8 +3,211 @@ import spiceypy as spice
 from ale.base import Driver
 from ale.base.data_naif import NaifSpice
 from ale.base.label_isis import IsisLabel
+from ale.base.label_pds3 import Pds3Label
 from ale.base.type_distortion import NoDistortion, ChandrayaanMrffrDistortion
 from ale.base.type_sensor import LineScanner, Radar
+from csv import reader
+
+
+class Chandrayaan1M3Pds3NaifSpiceDriver(LineScanner, Pds3Label, NaifSpice, NoDistortion, Driver):
+
+    @property
+    def ikid(self):
+        """
+        Returns the Naif ID code for the Moon Mineralogy Mapper
+
+        Returns
+        -------
+        : int
+          Naif ID used to for identifying the instrument in Spice kernels
+        """
+        return -86520
+
+    @property
+    def sensor_frame_id(self):
+        """
+        Returns the Naif ID code for the sensor reference frame
+
+        Returns
+        -------
+        : int
+          Naif ID code for the sensor frame
+        """
+        return spice.bods2c("CH1")
+
+
+    @property
+    def spacecraft_name(self):
+        """
+        Returns the name of the spacecraft
+
+        Returns
+        -------
+        : str
+          Full name of the spacecraft
+        """
+        return self.label['MISSION_ID']
+
+
+    @property
+    def platform_name(self):
+        """
+        Returns the name of the platform.  For M3, this is reflected by the mission name.
+
+        Returns
+        -------
+        : str
+          Name of the platform that the sensor is on
+        """
+        return self.label['MISSION_NAME']
+
+
+    @property
+    def image_lines(self):
+        """
+        Return the number of lines in the image.
+
+        Returns
+        -------
+        : int
+          Number of lines in the image
+        """
+        return self.label.get('RDN_FILE').get('RDN_IMAGE').get('LINES')
+
+
+    @property
+    def image_samples(self):
+        """
+        Return the number of samples in the image.
+
+        Returns
+        -------
+        : int
+          Number of samples in the image
+        """
+        return self.label.get('RDN_FILE').get('RDN_IMAGE').get('LINE_SAMPLES')
+
+
+    def read_timing_data(self):
+        """
+        Read data from the timing file.
+
+        Returns
+        -------
+        None
+        """
+        if hasattr(self, '_utc_times'):
+            return(self._lines, self._utc_times)
+
+        # Read timing file as structured text.  Reads each row as a list.
+        with open(self.utc_time_table, 'r') as time_file:
+            lists = list(reader(time_file, skipinitialspace=True, delimiter=" "))
+
+        # Transpose such that each column is a list. Unpack and ignore anything that's not lines and times.
+        self._lines, self._utc_times, *_ = zip(*lists)
+
+
+    @property
+    def ephemeris_start_time(self):
+        """
+        Returns
+        -------
+        : double
+          The start time of the image in ephemeris seconds past the J2000 epoch.
+        """
+        if not hasattr(self, '_ephemeris_start_time'):
+            et = spice.utc2et(self.utc_times[0])
+            et -= (.5 * self.line_exposure_duration)
+            clock_time = spice.sce2s(self.sensor_frame_id, et)
+            self._ephemeris_start_time = spice.scs2e(self.sensor_frame_id, clock_time)
+        return self._ephemeris_start_time
+
+
+    @property
+    def ephemeris_stop_time(self):
+        """
+        Returns
+        -------
+        : double
+          The stop time of the image in ephemeris seconds past the J2000 epoch.
+        """
+        return self.ephemeris_start_time + (self.image_lines * self.exposure_duration)
+                        
+
+    @property
+    def utc_time_table(self):
+        """ 
+        Returns
+        -------
+        : str
+          The name of the file containing the line timing information
+        """
+        if not hasattr(self, '_utc_time_table'):
+            self._utc_time_table = self.label['UTC_FILE']['^UTC_TIME_TABLE']
+        return self._utc_time_table
+
+
+    @property
+    def utc_times(self):
+        """ UTC time of the center of each line
+        """
+        if not hasattr(self, '_utc_times'):
+            self.read_timing_data()
+
+        if self._utc_times[0] > self._utc_times[-1]:
+            return list(reversed(self._utc_times))
+        return self._utc_times
+      
+
+    @property
+    def sampling_factor(self):
+        """
+        Returns the summing factor from the PDS3 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.
+
+        Information found in M3 SIS
+
+        Returns
+        -------
+        : int
+          Number of samples and lines combined from the original data to produce a single pixel in this image
+        """
+        instrument_mode = self.label['INSTRUMENT_MODE_ID']
+        if instrument_mode.upper() == "GLOBAL":
+            return 2
+        else:
+            return 1
+
+                
+    @property
+    def line_exposure_duration(self):
+        """
+        Line exposure duration returns the time between the exposures for
+        subsequent lines.
+
+        Logic found in ISIS translation file "Chandrayaan1M3Instrument.trn"
+
+        Returns
+        -------
+        : float
+          Returns the line exposure duration in seconds from the PDS3 label.
+        """
+        instrument_mode = self.label['INSTRUMENT_MODE_ID']
+        if instrument_mode.upper() == "GLOBAL":
+            return .10176
+        elif instrument_mode.upper() == "TARGET":
+            return .05088
+
+    @property
+    def sensor_model_version(self):
+        """
+        Returns
+        -------
+        : int
+          version of the sensor model
+        """
+        return 1
 
 class Chandrayaan1M3IsisLabelNaifSpiceDriver(LineScanner, IsisLabel, NaifSpice, NoDistortion, Driver):
     
diff --git a/tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_L1B_cropped_pds3.lbl b/tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_L1B_cropped_pds3.lbl
new file mode 100644
index 0000000..59c2369
--- /dev/null
+++ b/tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_L1B_cropped_pds3.lbl
@@ -0,0 +1,178 @@
+PDS_VERSION_ID = PDS3
+LABEL_REVISION_NOTE = "2009-01-26, S. Lundeen, 2010-12-07, S. Lundeen, 2011-09-20, S. Lundeen"
+DATA_SET_ID = CH1-ORB-L-M3-4-L1B-RADIANCE-V3.0
+PRODUCT_ID = M3T20090630T083407_V03_RDN
+RECORD_TYPE = UNDEFINED
+MISSION_ID = CH1
+MISSION_NAME = CHANDRAYAAN-1
+INSTRUMENT_HOST_ID = CH1-ORB
+INSTRUMENT_HOST_NAME = "CHANDRAYAAN-1 ORBITER"
+INSTRUMENT_NAME = "MOON MINERALOGY MAPPER"
+INSTRUMENT_ID = M3
+TARGET_NAME = MOON
+TARGET_TYPE = SATELLITE
+MISSION_PHASE_NAME = "PRIMARY MISSION"
+PRODUCT_TYPE = CALIBRATED_IMAGE
+PRODUCT_CREATION_TIME = 2009-05-10T18:01:10
+START_TIME = 2009-06-30T08:34:07
+STOP_TIME = 2009-06-30T08:34:36
+SPACECRAFT_CLOCK_START_COUNT = 12/1759028.348
+SPACECRAFT_CLOCK_STOP_COUNT = 12/1759056.993
+ORBIT_NUMBER = 2804
+PRODUCT_VERSION_TYPE = ACTUAL
+PRODUCT_VERSION_ID = "3.0"
+SOURCE_PRODUCT_ID = M3T20090630T083407_V01_L0.IMG
+PRODUCER_INSTITUTION_NAME = "JET PROPULSION LABORATORY"
+SOFTWARE_NAME = m3t_l1b_v07.exe
+SOFTWARE_VERSION_ID = "07"
+DESCRIPTION = "M3 Level 1B data product which contains pixel located, radiometrically-calibrated data."
+SOLAR_DISTANCE = 1.01711556761 <AU>
+INSTRUMENT_MODE_ID = TARGET
+DETECTOR_TEMPERATURE = 161.99
+CH1:SWATH_WIDTH = 608 <pixel>
+CH1:SWATH_LENGTH = 564 <pixel>
+CH1:SPACECRAFT_YAW_DIRECTION = FORWARD
+CH1:ORBIT_LIMB_DIRECTION = ASCENDING
+SPACECRAFT_ORIENTATION = (N/A, N/A, N/A)
+CH1:INITIAL_SC_ORIENTATION = (0.233580805487, 2.281265294933, 4.003902254047)
+CH1:SC_ORIENTATION_EPOCH_TDB_TIME = 299617824.351
+CH1:SC_ORIENTATION_RATE = (N/A, N/A, N/A)
+CH1:SC_ROTATION_AXIS_VECTOR = (0.049715054267, -0.997329135941, 0.053507083471)
+CH1:SC_ROTATION_RATE = 0.046843426581
+^DESCRIPTION = L1B_NAV_DESC.ASC
+CH1:SPECTRAL_CALIBRATION_FILE_NAME = M3T20070912_RDN_SPC.TAB
+CH1:RAD_GAIN_FACTOR_FILE_NAME = M3T20070912_RDN_GAIN.TAB
+Object = RDN_FILE
+  ^RDN_IMAGE = M3T20090630T083407_V03_RDN_cropped.IMG
+  RECORD_TYPE = FIXED_LENGTH
+  RECORD_BYTES = 7296
+  FILE_RECORDS = 5
+  Object = RDN_IMAGE
+    LINES = 5
+    LINE_SAMPLES = 608
+    SAMPLE_TYPE = PC_REAL
+    SAMPLE_BITS = 32
+    UNIT = "W/(m^2 um sr)"
+    BANDS = 3
+    BAND_STORAGE_TYPE = LINE_INTERLEAVED
+    LINE_DISPLAY_DIRECTION = DOWN
+    SAMPLE_DISPLAY_DIRECTION = RIGHT
+  End_Object
+End_Object
+Object = RDN_HDR_FILE
+  ^RDN_ENVI_HEADER = M3T20090630T083407_V03_RDN.HDR
+  RECORD_TYPE = VARIABLE_LENGTH
+  FILE_RECORDS = 803
+  Object = RDN_ENVI_HEADER
+    INTERCHANGE_FORMAT = ASCII
+    BYTES = 25037
+    HEADER_TYPE = ENVI
+    DESCRIPTION = "Header file for compatibility with the commercial software package ENVI."
+  End_Object
+End_Object
+Object = LOC_FILE
+  ^LOC_IMAGE = M3T20090630T083407_V03_LOC_cropped.IMG
+  RECORD_TYPE = FIXED_LENGTH
+  RECORD_BYTES = 14592
+  FILE_RECORDS = 5
+  Object = LOC_IMAGE
+    LINES = 5
+    LINE_SAMPLES = 608
+    SAMPLE_TYPE = PC_REAL
+    SAMPLE_BITS = 64
+    BANDS = 3
+    BAND_STORAGE_TYPE = LINE_INTERLEAVED
+    BAND_NAME = (Longitude, Latitude, Radius)
+    LINE_DISPLAY_DIRECTION = DOWN
+    SAMPLE_DISPLAY_DIRECTION = RIGHT
+  End_Object
+End_Object
+Object = LOC_HDR_FILE
+  ^LOC_ENVI_HEADER = M3T20090630T083407_V03_LOC.HDR
+  RECORD_TYPE = VARIABLE_LENGTH
+  FILE_RECORDS = 16
+  Object = LOC_ENVI_HEADER
+    INTERCHANGE_FORMAT = ASCII
+    BYTES = 371
+    HEADER_TYPE = ENVI
+    DESCRIPTION = "Header file for compatibility with the commercial software package ENVI."
+  End_Object
+End_Object
+Object = OBS_FILE
+  ^OBS_IMAGE = M3T20090630T083407_V03_OBS_cropped.IMG
+  RECORD_TYPE = FIXED_LENGTH
+  RECORD_BYTES = 24320
+  FILE_RECORDS = 5
+  Object = OBS_IMAGE
+    LINES = 5
+    LINE_SAMPLES = 608
+    SAMPLE_TYPE = PC_REAL
+    SAMPLE_BITS = 32
+    BANDS = 10
+    BAND_STORAGE_TYPE = LINE_INTERLEAVED
+    BAND_NAME = ("To-Sun AZM", "To-Sun Zenith", "To-Inst AZM", "To-Inst Zenith", Phase-angle, "To-Sun Path Length", "To-Inst Path Length", "Facet Slope", "Facet Aspect", "Facet Cos i")
+    LINE_DISPLAY_DIRECTION = DOWN
+    SAMPLE_DISPLAY_DIRECTION = RIGHT
+  End_Object
+End_Object
+Object = OBS_HDR_FILE
+  ^OBS_ENVI_HEADER = M3T20090630T083407_V03_OBS.HDR
+  RECORD_TYPE = VARIABLE_LENGTH
+  FILE_RECORDS = 21
+  Object = OBS_ENVI_HEADER
+    INTERCHANGE_FORMAT = ASCII
+    BYTES = 706
+    HEADER_TYPE = ENVI
+    DESCRIPTION = "Header file for compatibility with the commercial software package ENVI."
+  End_Object
+End_Object
+Object = UTC_FILE
+  ^UTC_TIME_TABLE = M3T20090630T083407_V03_TIM_cropped.TAB
+  RECORD_TYPE = FIXED_LENGTH
+  RECORD_BYTES = 57
+  FILE_RECORDS = 5
+  Object = UTC_TIME_TABLE
+    NAME = "UTC OBSERVATION TIMING DATA"
+    INTERCHANGE_FORMAT = ASCII
+    ROWS = 5
+    COLUMNS = 4
+    ROW_BYTES = 57
+    Object = COLUMN
+      COLUMN_NUMBER = 1
+      NAME = "LINE NUMBER"
+      DATA_TYPE = ASCII_INTEGER
+      START_BYTE = 1
+      BYTES = 6
+      FORMAT = I6
+      DESCRIPTION = "Record number for each RDN image line"
+    End_Object
+    Object = COLUMN
+      COLUMN_NUMBER = 2
+      NAME = UTC_TIME
+      DATA_TYPE = TIME
+      START_BYTE = 8
+      BYTES = 26
+      FORMAT = A26
+      DESCRIPTION = "UTC Time for the middle of the integration period for each RDN image line expressed as YYYY-MM-DDTHH:MM:SS.SSSSSS"
+    End_Object
+    Object = COLUMN
+      COLUMN_NUMBER = 3
+      NAME = YEAR
+      DATA_TYPE = CHARACTER
+      START_BYTE = 35
+      BYTES = 4
+      FORMAT = I4
+      DESCRIPTION = "Decimal Day of Year (DDOY) Year reference extracted from the earliest time of each RDN image line"
+    End_Object
+    Object = COLUMN
+      COLUMN_NUMBER = 4
+      NAME = DDOY
+      DATA_TYPE = DATE
+      START_BYTE = 40
+      BYTES = 16
+      FORMAT = F16.12
+      DESCRIPTION = "Decimal Day of Year represented as the number of days elapsed since 00:00 UTC of January 1 of the year associated with the time stamp of the first line of the RDN image file. DDOY is expressed using seventeen characters where 1-3 = three characters that contain the integer number of days; 4 = a decimal point; 5-16 = twelve charact- ers after the decimal for the fractional part of the day of year value."
+    End_Object
+  End_Object
+End_Object
+End
\ No newline at end of file
diff --git a/tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_TIM_cropped.TAB b/tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_TIM_cropped.TAB
new file mode 100644
index 0000000..0932173
--- /dev/null
+++ b/tests/pytests/data/M3T20090630T083407_V03_RDN/M3T20090630T083407_V03_TIM_cropped.TAB
@@ -0,0 +1,5 @@
+     1 2009-06-30T08:34:35.653371 2009 180.357357101487
+     2 2009-06-30T08:34:35.602491 2009 180.357356512598
+     3 2009-06-30T08:34:35.551611 2009 180.357355923710
+     4 2009-06-30T08:34:35.500731 2009 180.357355334822
+     5 2009-06-30T08:34:35.449851 2009 180.357354745933
diff --git a/tests/pytests/data/isds/chandrayaan_m3_nadir_isd.json b/tests/pytests/data/isds/chandrayaan_m3_nadir_isd.json
new file mode 100644
index 0000000..bf80f20
--- /dev/null
+++ b/tests/pytests/data/isds/chandrayaan_m3_nadir_isd.json
@@ -0,0 +1,418 @@
+ {
+  "isis_camera_version": 1,
+  "image_lines": 5,
+  "image_samples": 608,
+  "name_platform": "CHANDRAYAAN-1",
+  "name_sensor": "MOON MINERALOGY MAPPER",
+  "reference_height": {
+    "maxheight": 1000,
+    "minheight": -1000,
+    "unit": "m"
+  },
+  "name_model": "USGS_ASTRO_LINE_SCANNER_SENSOR_MODEL",
+  "interpolation_method": "lagrange",
+  "line_scan_rate": [
+    [
+      0.5,
+      -0.12720000743865967,
+      0.05088
+    ]
+  ],
+  "starting_ephemeris_time": 299622941.60814995,
+  "center_ephemeris_time": 299622941.73534995,
+  "radii": {
+    "semimajor": 1737.4,
+    "semiminor": 1737.4,
+    "unit": "km"
+  },
+  "body_rotation": {
+    "time_dependent_frames": [
+      31006,
+      1
+    ],
+    "ck_table_start_time": 299622941.60814995,
+    "ck_table_end_time": 299622941.86254996,
+    "ck_table_original_size": 2,
+    "ephemeris_times": [
+      299622941.60814995,
+      299622941.86254996
+    ],
+    "quaternions": [
+      [
+        -0.9747735513065916,
+        0.19646555501476617,
+        -0.009172349392417702,
+        0.1055162422212761
+      ],
+      [
+        -0.9747735156174205,
+        0.196465551731104,
+        -0.009172415802241563,
+        0.10551657226330902
+      ]
+    ],
+    "angular_velocities": [
+      [
+        6.124937754497467e-08,
+        -1.0239801705539717e-06,
+        2.4560118095097265e-06
+      ],
+      [
+        6.12493776573662e-08,
+        -1.0239801709466094e-06,
+        2.456011809503279e-06
+      ]
+    ],
+    "constant_frames": [
+      31001,
+      31007,
+      31006
+    ],
+    "constant_rotation": [
+      0.9999998732547144,
+      -0.00032928542237557133,
+      0.00038086961867138755,
+      0.00032928600021094723,
+      0.9999999457843062,
+      -1.4544409378362713e-06,
+      -0.00038086911909607826,
+      1.5798557868269087e-06,
+      0.9999999274681067
+    ],
+    "reference_frame": 1
+  },
+  "instrument_pointing": {
+    "time_dependent_frames": [
+      -86,
+      1
+    ],
+    "ck_table_start_time": 299622941.60814995,
+    "ck_table_end_time": 299622941.86254996,
+    "ck_table_original_size": 6,
+    "ephemeris_times": [
+      299622941.60814995,
+      299622941.65902996,
+      299622941.70991,
+      299622941.76078993,
+      299622941.81166995,
+      299622941.86254996
+    ],
+    "quaternions": [
+      [
+        0.04661238772647475,
+        -0.692389430047604,
+        0.7198383853973618,
+        -0.01602689543580429
+      ],
+      [
+        0.04659829485244615,
+        -0.692423944862404,
+        0.719806464803485,
+        -0.0160103942843507
+      ],
+      [
+        0.046584207173847626,
+        -0.6924585919315193,
+        0.7197744130572963,
+        -0.015993886651673537
+      ],
+      [
+        0.04657012448653539,
+        -0.6924933610486642,
+        0.7197422399398765,
+        -0.01597737322473273
+      ],
+      [
+        0.046556047309271195,
+        -0.6925282769138067,
+        0.7197099216524531,
+        -0.01596085238170966
+      ],
+      [
+        0.046541973820848646,
+        -0.692563256361473,
+        0.719677538184117,
+        -0.01594432952811621
+      ]
+    ],
+    "angular_velocities": null,
+    "reference_frame": 1,
+    "constant_frames": [
+      -86
+    ],
+    "constant_rotation": [
+      1.0,
+      0.0,
+      0.0,
+      0.0,
+      1.0,
+      0.0,
+      0.0,
+      0.0,
+      1.0
+    ]
+  },
+  "naif_keywords": {
+    "BODY301_RADII": [
+      1737.4,
+      1737.4,
+      1737.4
+    ],
+    "BODY_FRAME_CODE": 31001,
+    "BODY_CODE": 301,
+    "INS-86520_ITRANSS": [
+      -1.9610889460163698,
+      -0.054945530371164404,
+      -37.036996280362
+    ],
+    "INS-86520_BORESIGHT_SAMPLE": 304.5,
+    "TKFRAME_-86520_RELATIVE": "CH1_SPACECRAFT",
+    "INS-86520_OD_K": [
+      0.000379921103637315,
+      0.000213226448051461,
+      -4.0186287875859195e-08
+    ],
+    "FRAME_-86520_CLASS": 4.0,
+    "INS-86520_DECENTER": [
+      0.004047,
+      -5e-06
+    ],
+    "INS-86520_CK_REFERENCE_ID": 1.0,
+    "INS-86520_PLATFORM_ID": -86000.0,
+    "TKFRAME_-86520_UNITS": "DEGREES",
+    "INS-86520_LT_SURFACE_CORRECT": "TRUE",
+    "INS-86520_SWAP_OBSERVER_TARGET": "TRUE",
+    "TKFRAME_-86520_ANGLES": [
+      0.0,
+      0.0,
+      0.0
+    ],
+    "FRAME_-86520_CENTER": -86.0,
+    "INS-86520_LIGHTTIME_CORRECTION": "NONE",
+    "INS-86520_PIXEL_PITCH": 0.027,
+    "FRAME_-86520_NAME": "CHANDRAYAAN-1_M3",
+    "TKFRAME_-86520_AXES": [
+      1.0,
+      2.0,
+      3.0
+    ],
+    "TKFRAME_-86520_SPEC": "ANGLES",
+    "INS-86520_TRANSX": [
+      -0.64,
+      -4.00552916405788e-05,
+      0.0269999702883839
+    ],
+    "INS-86520_TRANSY": [
+      -0.052,
+      -0.0269999702883839,
+      -4.00552916405788e-05
+    ],
+    "FRAME_-86520_CLASS_ID": -86520.0,
+    "INS-86520_CK_FRAME_ID": -86000.0,
+    "INS-86520_BORESIGHT_LINE": 0.0,
+    "INS-86520_FOCAL_LENGTH": 39.63,
+    "INS-86520_PP": [
+      0.0,
+      0.0
+    ],
+    "INS-86520_ITRANSL": [
+      23.7008204518524,
+      37.036996280362,
+      -0.054945530371164404
+    ],
+    "BODY301_POLE_RA": [
+      269.9949,
+      0.0031,
+      0.0
+    ],
+    "BODY301_NUT_PREC_PM": [
+      3.561,
+      0.1208,
+      -0.0642,
+      0.0158,
+      0.0252,
+      -0.0066,
+      -0.0047,
+      -0.0046,
+      0.0028,
+      0.0052
+    ],
+    "BODY301_NUT_PREC_RA": [
+      -3.8787000000000003,
+      -0.1204,
+      0.07,
+      -0.0172,
+      0.0,
+      0.0072,
+      0.0,
+      0.0,
+      0.0,
+      -0.0052
+    ],
+    "BODY301_LONG_AXIS": 0.0,
+    "BODY301_NUT_PREC_DEC": [
+      1.5419,
+      0.0239,
+      -0.0278,
+      0.0068,
+      0.0,
+      -0.0029,
+      0.0009,
+      0.0,
+      0.0,
+      0.0008
+    ],
+    "BODY301_POLE_DEC": [
+      66.5392,
+      0.013,
+      0.0
+    ],
+    "BODY301_PM": [
+      38.3213,
+      13.17635815,
+      -1.3999999999999999e-12
+    ]
+  },
+  "detector_sample_summing": 1,
+  "detector_line_summing": 1,
+  "focal_length_model": {
+    "focal_length": 39.63
+  },
+  "detector_center": {
+    "line": 0.0,
+    "sample": 304.5
+  },
+  "focal2pixel_lines": [
+    23.7008204518524,
+    37.036996280362,
+    -0.054945530371164404
+  ],
+  "focal2pixel_samples": [
+    -1.9610889460163698,
+    -0.054945530371164404,
+    -37.036996280362
+  ],
+  "optical_distortion": {
+    "radial": {
+      "coefficients": [
+        0.0,
+        0.0,
+        0.0
+      ]
+    }
+  },
+  "starting_detector_line": 0,
+  "starting_detector_sample": 0,
+  "instrument_position": {
+    "spk_table_start_time": 299622941.60814995,
+    "spk_table_end_time": 299622941.86254996,
+    "spk_table_original_size": 6,
+    "ephemeris_times": [
+      299622941.60814995,
+      299622941.65902996,
+      299622941.70991,
+      299622941.76078993,
+      299622941.81166995,
+      299622941.86254996
+    ],
+    "positions": [
+      [
+        87.43803283072197,
+        170.58382785758494,
+        1937.3684802589921
+      ],
+      [
+        87.43510506145041,
+        170.50391417711066,
+        1937.376298445722
+      ],
+      [
+        87.43218518920048,
+        170.4240123258735,
+        1937.3841107949738
+      ],
+      [
+        87.4292732456229,
+        170.3441224440555,
+        1937.3919172883918
+      ],
+      [
+        87.4263692523569,
+        170.26424439143344,
+        1937.3997179348762
+      ],
+      [
+        87.42347324097759,
+        170.1843783074444,
+        1937.4075127164094
+      ]
+    ],
+    "velocities": [
+      [
+        -0.05762002410373986,
+        -1.5707459933015884,
+        0.1537165891795824
+      ],
+      [
+        -0.05746510464971223,
+        -1.5705139595334539,
+        0.15360195064617482
+      ],
+      [
+        -0.057309612713474595,
+        -1.5702809919217755,
+        0.15348712189339614
+      ],
+      [
+        -0.05715359501889533,
+        -1.5700471605505897,
+        0.15337213718299325
+      ],
+      [
+        -0.05699694014477726,
+        -1.5698122982929306,
+        0.15325691481115888
+      ],
+      [
+        -0.05684002521205642,
+        -1.569576970816976,
+        0.15314173131322809
+      ]
+    ],
+    "reference_frame": 1
+  },
+  "sun_position": {
+    "spk_table_start_time": 299622941.60814995,
+    "spk_table_end_time": 299622941.86254996,
+    "spk_table_original_size": 2,
+    "ephemeris_times": [
+      299622941.60814995,
+      299622941.86254996
+    ],
+    "positions": [
+      [
+        -22279392.552556925,
+        138086311.0772047,
+        59901634.029351056
+      ],
+      [
+        -22279399.993010454,
+        138086310.29030323,
+        59901633.69455383
+      ]
+    ],
+    "velocities": [
+      [
+        -29.247063783209654,
+        -3.0931654356540093,
+        -1.3160264669418211
+      ],
+      [
+        -29.24706420755107,
+        -3.0931669393224728,
+        -1.3160271856310302
+      ]
+    ],
+    "reference_frame": 1
+  }
+}
\ No newline at end of file
diff --git a/tests/pytests/test_chandrayaan_driver.py b/tests/pytests/test_chandrayaan_driver.py
index 637d7cb..25ad058 100644
--- a/tests/pytests/test_chandrayaan_driver.py
+++ b/tests/pytests/test_chandrayaan_driver.py
@@ -2,17 +2,13 @@ from cgi import test
 import pytest
 import ale
 import os
-import pvl
 
-import numpy as np
-from ale.drivers import co_drivers
-from ale.formatters.formatter import to_isd
 import unittest
-from unittest.mock import PropertyMock, patch
+from unittest.mock import patch
 import json
-from conftest import get_image_label, get_image_kernels, get_isd, convert_kernels, compare_dicts, get_table_data
+from conftest import get_image_label, get_image_kernels, get_isd, convert_kernels, compare_dicts
 
-from ale.drivers.chandrayaan_drivers import Chandrayaan1M3IsisLabelNaifSpiceDriver, Chandrayaan1MRFFRIsisLabelNaifSpiceDriver
+from ale.drivers.chandrayaan_drivers import Chandrayaan1M3IsisLabelNaifSpiceDriver, Chandrayaan1MRFFRIsisLabelNaifSpiceDriver, Chandrayaan1M3Pds3NaifSpiceDriver
 
 @pytest.fixture()
 def m3_kernels(scope="module", autouse=True):
@@ -48,6 +44,66 @@ def test_chandrayaan_mrffr_load(mrffr_kernels):
     x = compare_dicts(isd_obj, compare_dict)
     assert x == []
 
+def test_chandrayaan_m3_pds_load(m3_kernels):
+    label_file = get_image_label("M3T20090630T083407_V03_L1B_cropped", label_type="pds3")
+    compare_dict = get_isd("chandrayaan_m3_nadir")
+
+    # Patch the full path of the timing table onto the driver
+    with patch("ale.drivers.chandrayaan_drivers.Chandrayaan1M3Pds3NaifSpiceDriver.utc_time_table", os.path.dirname(label_file)+"/M3T20090630T083407_V03_TIM_cropped.TAB"):
+        isd_str = ale.loads(label_file, props={"kernels": m3_kernels, "nadir": True})
+        isd_obj = json.loads(isd_str)
+        x = compare_dicts(isd_obj, compare_dict)
+        assert x == []
+
+
+class test_chandrayaan_m3_pds_naif(unittest.TestCase):
+
+    def setUp(self):
+        label = get_image_label("M3T20090630T083407_V03_L1B_cropped", "pds3")
+        self.driver = Chandrayaan1M3Pds3NaifSpiceDriver(label)
+
+    def test_ikid(self):
+        assert self.driver.ikid == -86520
+
+    def test_sensor_frame_id(self):
+        assert self.driver.sensor_frame_id == -86
+
+    def test_spacecraft_name(self):
+        assert self.driver.spacecraft_name == 'CH1'
+
+    def test_image_lines(self):
+        assert self.driver.image_lines == 5
+
+    def test_image_samples(self):
+        assert self.driver.image_samples == 608
+
+    def test_ephemeris_start_time(self):
+        label_file = get_image_label("M3T20090630T083407_V03_L1B_cropped", label_type="pds3")
+        with patch("ale.drivers.chandrayaan_drivers.spice.scs2e", return_value=12345) as scs2e,\
+              patch("ale.drivers.chandrayaan_drivers.spice.utc2et", return_value=12345) as utc2et,\
+                patch("ale.drivers.chandrayaan_drivers.spice.sce2s", return_value=12345) as sce2s,\
+                 patch("ale.drivers.chandrayaan_drivers.Chandrayaan1M3Pds3NaifSpiceDriver.utc_time_table", os.path.dirname(label_file)+"/M3T20090630T083407_V03_TIM_cropped.TAB"):
+            assert self.driver.ephemeris_start_time == 12345
+
+    def test_ephemeris_stop_time(self):
+        label_file = get_image_label("M3T20090630T083407_V03_L1B_cropped", label_type="pds3")
+        with patch("ale.drivers.chandrayaan_drivers.spice.scs2e", return_value=12345) as scs2e,\
+              patch("ale.drivers.chandrayaan_drivers.spice.utc2et", return_value=12345) as utc2et,\
+                patch("ale.drivers.chandrayaan_drivers.spice.sce2s", return_value=12345) as sce2s,\
+                 patch("ale.drivers.chandrayaan_drivers.Chandrayaan1M3Pds3NaifSpiceDriver.utc_time_table", os.path.dirname(label_file)+"/M3T20090630T083407_V03_TIM_cropped.TAB"):
+            assert self.driver.ephemeris_stop_time == 12345.2544
+
+    def test_utc_times(self):
+        label_file = get_image_label("M3T20090630T083407_V03_L1B_cropped", label_type="pds3")
+        with patch("ale.drivers.chandrayaan_drivers.Chandrayaan1M3Pds3NaifSpiceDriver.utc_time_table", os.path.dirname(label_file)+"/M3T20090630T083407_V03_TIM_cropped.TAB"):
+            assert self.driver.utc_times[0] == '2009-06-30T08:34:35.449851'
+
+    def test_sampling_factor(self):
+        assert self.driver.sampling_factor == 1
+
+    def test_line_exposure_duration(self):
+        assert self.driver.line_exposure_duration == .05088
+
 
 # ========= Test chandrayaan isislabel and naifspice driver =========
 class test_chandrayaan_isis_naif(unittest.TestCase):
-- 
GitLab