diff --git a/ale/drivers/kaguya_drivers.py b/ale/drivers/kaguya_drivers.py index 3677d304c6ea4f13cc58d57c6a0e173ddc5f8427..57f081e78784615fcdf96c2ce11b78e5f43839ec 100644 --- a/ale/drivers/kaguya_drivers.py +++ b/ale/drivers/kaguya_drivers.py @@ -13,7 +13,7 @@ from ale.base.label_pds3 import Pds3Label from ale.base.type_sensor import LineScanner -class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): +class KaguyaTcPds3NaifSpiceDriver(LineScanner, Pds3Label, NaifSpice, Driver): """ Driver for a PDS3 Kaguya Terrain Camera (TC) images. Specifically level2b0 mono and stereo images. @@ -48,7 +48,7 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): COMPRESS = D/T short for DCT or through, we assume image has been decompressed already SWATCH = swatch mode, different swatch modes have different FOVs """ - instrument = self.label.get("INSTRUMENT_ID") + instrument = super().instrument_id swath = self.label.get("SWATH_MODE_ID")[0] sd = self.label.get("PRODUCT_SET_ID").split("_")[1].upper() @@ -65,23 +65,23 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): presumably because they are not affected by the addtional parameters encoded in the ikid returned by self.ikid. This method exists for those gdpool calls. """ - return spice.bods2c("LISM_{}".format(self.label.get("INSTRUMENT_ID"))) + return spice.bods2c("LISM_{}".format(super().instrument_id)) @property def clock_stop_count(self): return self.label.get('CORRECTED_SC_CLOCK_STOP_COUNT').value @property - def ephemermis_stop_time(self): - return spice.sct2e(self.spacecraft_id, self.ephemeris_stop_time) + def ephemeris_stop_time(self): + return spice.sct2e(self.spacecraft_id, super().ephemeris_stop_time) @property def clock_start_count(self): return self.label.get('CORRECTED_SC_CLOCK_START_COUNT').value @property - def ephemermis_start_time(self): - return spice.sct2e(self.spacecraft_id, self.ephemeris_start_time) + def ephemeris_start_time(self): + return spice.sct2e(self.spacecraft_id, super().ephemeris_start_time) @property def detector_center_line(self): @@ -99,7 +99,7 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): qua = np.empty((len(ephem), 4)) for i, time in enumerate(ephem): - instrument = self.label.get("INSTRUMENT_ID") + instrument = super().instrument_id # Find the rotation matrix camera2bodyfixed = spice.pxform("LISM_{}_HEAD".format(instrument), self.reference_frame, @@ -117,8 +117,8 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): Kaguya uses a slightly more accurate "mean Earth" reference frame for moon obvervations. see https://darts.isas.jaxa.jp/pub/spice/SELENE/kernels/fk/moon_assoc_me.tf """ - if self.target_name.lower == "moon": - "MOON_ME" + if self.target_name.lower() == "moon": + return "MOON_ME" else: # TODO: How do we handle no target? return "NO TARGET" @@ -176,7 +176,7 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): # if its downloaded from Jaxa's image search: # (https://darts.isas.jaxa.jp/planet/pdap/selene/product_search.html#) try: - return self.label['CORRECTED_SAMPLING_INTERVAL'][0].value * 0.001 # Scale to seconds + return self.label['CORRECTED_SAMPLING_INTERVAL'][0].value * 0.001 # Scale to seconds except: return self.label['CORRECTED_SAMPLING_INTERVAL'].value * 0.001 # Scale to seconds @@ -192,7 +192,7 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): return float(spice.gdpool('INS{}_FOCAL_LENGTH'.format(self._tc_id), 0, 1)[0]) @property - def optical_distortion(self): + def usgscsm_distortion_model(self): """ Kaguya uses a unique radial distortion model so we need to overwrite the method packing the distortion model into the ISD. @@ -222,7 +222,7 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): } @property - def starting_detector_sample(self): + def detector_start_sample(self): """ Returns starting detector sample @@ -266,3 +266,18 @@ class KaguyaTcPds3NaifSpiceDriver(Driver, LineScanner, Pds3Label, NaifSpice): """ return self.label["FIRST_PIXEL_NUMBER"] + + @property + def detector_start_line(self): + return 1 + + + @property + def sensor_model_version(self): + """ + Returns + ------- + : int + model version + """ + return 1 diff --git a/tests/pytests/test_kaguya_drivers.py b/tests/pytests/test_kaguya_drivers.py index ed9845be821a553280fd9128cedcab7927b9ff09..3e22e91fa3aa828fe1958a2e6f65790f4a6a2959 100644 --- a/tests/pytests/test_kaguya_drivers.py +++ b/tests/pytests/test_kaguya_drivers.py @@ -1,166 +1,118 @@ import pytest +import numpy as np +import pvl +import ale from ale.drivers import kaguya_drivers -from ale.drivers.kaguya_drivers import KaguyaTcPds3NaifSpiceDriver +from ale.base import data_naif +from ale.base import label_pds3 + +from unittest.mock import PropertyMock, patch # 'Mock' the spice module where it is imported from conftest import SimpleSpice, get_mockkernels simplespice = SimpleSpice() + +data_naif.spice = simplespice kaguya_drivers.spice = simplespice +label_pds3.spice = simplespice + +from ale.drivers.kaguya_drivers import KaguyaTcPds3NaifSpiceDriver KaguyaTcPds3NaifSpiceDriver.metakernel = get_mockkernels @pytest.fixture -def kaguya_tclabel(): - return """ - PDS_VERSION_ID = "PDS3" - /*** FILE FORMAT ***/ - RECORD_TYPE = "UNDEFINED" - FILE_NAME = "TC1W2B0_01_00296N081E2387.img" - PRODUCT_ID = "TC1W2B0_01_00296N081E2387" - DATA_FORMAT = "PDS" - - /*** POINTERS TO START BYTE OFFSET OF OBJECTS IN FILE ***/ - ^IMAGE = 7609 - - /*** GENERAL DATA DESCRIPTION PARAMETERS ***/ - SOFTWARE_NAME = "RGC_TC_w_Level2B0 (based on RGC_TC_MI version 2.10.1)" - SOFTWARE_VERSION = "1.0.0" - PROCESS_VERSION_ID = "L2B" - PRODUCT_CREATION_TIME = 2013-07-16T20:12:53Z - PROGRAM_START_TIME = 2013-07-16T20:08:01Z - PRODUCER_ID = "LISM" - PRODUCT_SET_ID = "TC_w_Level2B0" - PRODUCT_VERSION_ID = "01" - REGISTERED_PRODUCT = "Y" - ILLUMINATION_CONDITION = "MORNING" - LEVEL2A_FILE_NAME = "TC1W2A0_02TSF00296_001_0022.img" - SPICE_METAKERNEL_FILE_NAME = "RGC_INF_TCv401IK_MIv200IK_SPv105IK_RISE100h_02_LongCK_D_V02_de421_110706.mk" - - /*** SCENE RELATED PARAMETERS ***/ - MISSION_NAME = "SELENE" - SPACECRAFT_NAME = "SELENE-M" - DATA_SET_ID = "TC1_Level2B" - INSTRUMENT_NAME = "Terrain Camera 1" - INSTRUMENT_ID = "TC1" - MISSION_PHASE_NAME = "InitialCheckout" - REVOLUTION_NUMBER = 296 - STRIP_SEQUENCE_NUMBER = 1 - SCENE_SEQUENCE_NUMBER = 22 - UPPER_LEFT_DAYTIME_FLAG = "Day" - UPPER_RIGHT_DAYTIME_FLAG = "Day" - LOWER_LEFT_DAYTIME_FLAG = "Day" - LOWER_RIGHT_DAYTIME_FLAG = "Day" - TARGET_NAME = "MOON" - OBSERVATION_MODE_ID = "NORMAL" - SENSOR_DESCRIPTION = "Imagery type:Pushbroom. ImageryMode:Mono,Stereo. ExposureTimeMode:Long,Middle,Short. CompressionMode:NonComp,DCT. Q-table:32 patterns. H-table:4 patterns. SwathMode:F(Full),N(Nominal),H(Half). First pixel number:1(F),297(N),1172(H)." - SENSOR_DESCRIPTION2 = "Pixel size:7x7[micron^2](TC1/TC2). Wavelength range:430-850[nm](TC1/TC2). A/D rate:10[bit](TC1/TC2). Slant angle:+/-15[degree] (from nadir to +x of S/C)(TC1/TC2). Focal length:72.45/72.63[mm](TC1/TC2). F number:3.97/3.98(TC1/TC2)." - DETECTOR_STATUS = {"TC1:ON","TC2:ON","MV:OFF","MN:OFF","SP:ON"} - EXPOSURE_MODE_ID = "SHORT" - LINE_EXPOSURE_DURATION = 1.625000 - SPACECRAFT_CLOCK_START_COUNT = 878074165.1875 - SPACECRAFT_CLOCK_STOP_COUNT = 878074195.4450 - CORRECTED_SC_CLOCK_START_COUNT = 878074165.186621 - CORRECTED_SC_CLOCK_STOP_COUNT = 878074195.443901 - START_TIME = 2007-11-02T21:29:27.123714Z - STOP_TIME = 2007-11-02T21:29:57.381214Z - CORRECTED_START_TIME = 2007-11-02T21:29:27.122835Z - CORRECTED_STOP_TIME = 2007-11-02T21:29:57.380115Z - LINE_SAMPLING_INTERVAL = 6.500000 - CORRECTED_SAMPLING_INTERVAL = 6.499953 - UPPER_LEFT_LATITUDE = 7.290785 - UPPER_LEFT_LONGITUDE = 238.410490 - UPPER_RIGHT_LATITUDE = 7.288232 - UPPER_RIGHT_LONGITUDE = 238.991705 - LOWER_LEFT_LATITUDE = 8.820028 - LOWER_LEFT_LONGITUDE = 238.417311 - LOWER_RIGHT_LATITUDE = 8.817605 - LOWER_RIGHT_LONGITUDE = 238.999370 - LOCATION_FLAG = "A" - ROLL_CANT = "NO" - SCENE_CENTER_LATITUDE = 8.053752 - SCENE_CENTER_LONGITUDE = 238.704621 - INCIDENCE_ANGLE = 28.687 - EMISSION_ANGLE = 17.950 - PHASE_ANGLE = 31.600 - SOLAR_AZIMUTH_ANGLE = 108.126 - FOCAL_PLANE_TEMPERATURE = 18.85 - TELESCOPE_TEMPERATURE = 18.59 - SATELLITE_MOVING_DIRECTION = "-1" - FIRST_SAMPLED_LINE_POSITION = "UPPERMOST" - FIRST_DETECTOR_ELEMENT_POSITION = "LEFT" - A_AXIS_RADIUS = 1737.400 - B_AXIS_RADIUS = 1737.400 - C_AXIS_RADIUS = 1737.400 - DEFECT_PIXEL_POSITION = "N/A" - - /*** CAMERA RELATED PARAMETERS ***/ - SWATH_MODE_ID = "FULL" - FIRST_PIXEL_NUMBER = 1 - LAST_PIXEL_NUMBER = 1600 - SPACECRAFT_ALTITUDE = 108.719 - SPACECRAFT_GROUND_SPEED = 1.530 - TC1_TELESCOPE_TEMPERATURE = 18.70 - TC2_TELESCOPE_TEMPERATURE = 18.70 - DPU_TEMPERATURE = 14.60 - TM_TEMPERATURE = 18.01 - TM_RADIATOR_TEMPERATURE = 17.67 - Q_TABLE_ID = "N/A" - HUFFMAN_TABLE_ID = "N/A" - DATA_COMPRESSION_PERCENT_MEAN = 100.0 - DATA_COMPRESSION_PERCENT_MAX = 100.0 - DATA_COMPRESSION_PERCENT_MIN = 100.0 - - /*** DESCRIPTION OF OBJECTS CONTAINED IN THE FILE ***/ - - OBJECT = IMAGE - ENCODING_TYPE = "N/A" - ENCODING_COMPRESSION_PERCENT = 100.0 - NOMINAL_LINE_NUMBER = 4088 - NOMINAL_OVERLAP_LINE_NUMBER = 568 - OVERLAP_LINE_NUMBER = 568 - LINES = 4656 - LINE_SAMPLES = 1600 - SAMPLE_TYPE = "MSB_INTEGER" - SAMPLE_BITS = 16 - IMAGE_VALUE_TYPE = "RADIANCE" - UNIT = "W/m**2/micron/sr" - SCALING_FACTOR = 1.30000e-02 - OFFSET = 0.00000e+00 - MIN_FOR_STATISTICAL_EVALUATION = 0 - MAX_FOR_STATISTICAL_EVALUATION = 32767 - SCENE_MAXIMUM_DN = 7602 - SCENE_MINIMUM_DN = 1993 - SCENE_AVERAGE_DN = 2888.6 - SCENE_STDEV_DN = 370.2 - SCENE_MODE_DN = 2682 - SHADOWED_AREA_MINIMUM = 0 - SHADOWED_AREA_MAXIMUM = 0 - SHADOWED_AREA_PERCENTAGE = 0 - INVALID_TYPE = ("SATURATION" , "MINUS" , "DUMMY_DEFECT" , "OTHER") - INVALID_VALUE = (-20000 , -21000 , -22000 , -23000) - INVALID_PIXELS = (0 , 0 , 0 , 0) - END_OBJECT = IMAGE - - OBJECT = PROCESSING_PARAMETERS - DARK_FILE_NAME = "TC1_DRK_00293_02951_S_N_b05.csv" - FLAT_FILE_NAME = "TC1_FLT_00293_04739_N_N_b05.csv" - EFFIC_FILE_NAME = "TC1_EFF_PRFLT_N_N_v01.csv" - NONLIN_FILE_NAME = "TC1_NLT_PRFLT_N_N_v01.csv" - RAD_CNV_COEF = 3.790009 - L2A_DEAD_PIXEL_THRESHOLD = 30 - L2A_SATURATION_THRESHOLD = 1023 - DARK_VALID_MINIMUM = -5 - RADIANCE_SATURATION_THRESHOLD = 425.971000 - END_OBJECT = PROCESSING_PARAMETERS - END - """ - -def test_kaguya_creation(kaguya_tclabel): - #with KaguyaTcPds3NaifSpiceDriver(kaguya_tclabel) as m: - # d = m.to_dict() - # assert isinstance(d, dict) - - # Need to insert new tests here, one for each property unique to this driver - assert True +def driver(): + return KaguyaTcPds3NaifSpiceDriver("") + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_instrument_id(driver): + with patch.dict(driver.label, {'SWATH_MODE_ID':'NOMINAL', 'PRODUCT_SET_ID':'TC_w_Level2B0' }) as f: + assert driver.instrument_id == 'LISM_123_WTN' + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_tc_id(driver): + assert driver._tc_id == -12345 + +def test_clock_stop_count(driver): + with patch.dict(driver.label, {'CORRECTED_SC_CLOCK_STOP_COUNT': + pvl._collections.Units(value=105, units='')}) as f: + assert driver.clock_stop_count == 105 + +def test_clock_start_count(driver): + with patch.dict(driver.label, {'CORRECTED_SC_CLOCK_START_COUNT': + pvl._collections.Units(value=501, units='')}) as f: + assert driver.clock_start_count == 501 + +@patch('ale.base.data_naif.NaifSpice.ephemeris_stop_time', 800) +@patch('ale.base.data_naif.NaifSpice.spacecraft_id', 123) +def test_ephemeris_stop_time(driver): + assert driver.ephemeris_stop_time == 0.1 + +@patch('ale.base.data_naif.NaifSpice.ephemeris_start_time', 800) +@patch('ale.base.data_naif.NaifSpice.spacecraft_id', 123) +def test_ephemeris_start_time(driver): + assert driver.ephemeris_start_time == 0.1 + +def test_detector_center_line(driver): + assert driver.detector_center_line == 0 + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_detector_center_sample(driver): + assert driver.detector_center_sample == 0 + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +@patch('ale.base.label_pds3.Pds3Label.target_name', 'MOON') +def test_sensor_orientation(driver): + with patch('ale.base.type_sensor.LineScanner.ephemeris_time', new_callable=PropertyMock) as mock_time: + mock_time.return_value = np.linspace(100,200,2) + assert driver._sensor_orientation == [[2,3,4,1], [2,3,4,1]] + +def test_reference_frame(driver): + with patch('ale.base.label_pds3.Pds3Label.target_name', new_callable=PropertyMock) as mock_target_name: + mock_target_name.return_value = 'MOOn' + assert driver.reference_frame == 'MOON_ME' + mock_target_name.return_value = 'sun' + assert driver.reference_frame == 'NO TARGET' + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_focal2pixel_samples(driver): + assert driver.focal2pixel_samples == [0, 0, -1] + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_focal2pixel_lines(driver): + assert driver.focal2pixel_lines == [0, -1, 0] + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_odkx(driver): + assert driver._odkx == [1, 1, 1, 1] + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_odky(driver): + assert driver._odky == [1, 1, 1, 1] + +def test_line_exposure_duration(driver): + with patch.dict(driver.label, {'CORRECTED_SAMPLING_INTERVAL': + pvl._collections.Units(value=1000, units='')}) as f: + assert driver.line_exposure_duration == 1 + with patch.dict(driver.label, {'CORRECTED_SAMPLING_INTERVAL': + [pvl._collections.Units(value=10000, units=''), 1]}) as f: + assert driver.line_exposure_duration == 10 + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_focal_length(driver): + assert driver.focal_length == 1 + +@patch('ale.base.label_pds3.Pds3Label.instrument_id', 123) +def test_usgscsm_distortion_model(driver): + distortion_model = driver.usgscsm_distortion_model + assert distortion_model['kaguyatc']['x'] == [1, 1, 1, 1] + assert distortion_model['kaguyatc']['y'] == [1, 1, 1, 1] + +def test_detector_start_sample(driver): + with patch.dict(driver.label, {'FIRST_PIXEL_NUMBER': 123}) as f: + assert driver.detector_start_sample == 123 + +def test_sensor_model_version(driver): + assert driver.sensor_model_version == 1