planetarypy / pvl

Python implementation of PVL (Parameter Value Language)
BSD 3-Clause "New" or "Revised" License
19 stars 19 forks source link

PVL Round Trip from caminfo: Time precision issue #97

Closed jlaura closed 2 years ago

jlaura commented 2 years ago

Describe the bug Using the caminfo file reproduced at the end of this report, pvl reads the file with no issue. When I attempt to dump back to a string, pvl errors with the following ValueError.

ValueError: PDS labels can only encode time values to the milisecond precision, and this time (2008-07-17 20:15:35.204835+00:00) has too much precision.

To Reproduce

import pvl
caminfo = pvl.load(caminfo.file)
caminfo = caminfo['Caminfo']
pvl.dumps(caminfo['IsisLabel'])

Expected behavior I would expect to be able to round trip PVL data through the library.

Error logs or terminal captures

ValueError: PDS labels can only encode time values to the milisecond precision, and this time (2008-07-17 20:15:35.204835+00:00) has too much precision.

Your Environment (please complete the following information):

Additional context

This works when I specify the encoder, but I am not confident that a newish user would read the docs and test a different encoder when they see a time precision error on a PVL file that is round tripped.

This works:

import pvl
caminfo = pvl.load(caminfo.file)
caminfo = caminfo['Caminfo']
pvl.dumps(caminfo['IsisLabel'], , encoder=pvl.encoder.PVLEncoder())
caminfo.pvl ``` Object = Caminfo Object = Parameters Program = caminfo IsisVersion = "6.0.0 | 2021-08-31" RunDate = 2021-10-02T13:49:39 IsisId = KAGUYA/TC1/2008-07-17T20:15:35.204835 From = TC1S2B0_01_03448S320E0799.cub Lines = 4656 Samples = 3496 Bands = 1 End_Object Object = IsisLabel Object = IsisCube Object = Core StartByte = 65537 Format = Tile TileSamples = 874 TileLines = 776 Group = Dimensions Samples = 3496 Lines = 4656 Bands = 1 End_Group Group = Pixels Type = SignedWord ByteOrder = Lsb Base = 0.0 Multiplier = 0.013 End_Group End_Object Group = Instrument MissionName = SELENE SpacecraftName = KAGUYA InstrumentName = "TERRAIN CAMERA 1" InstrumentId = TC1 TargetName = MOON ObservationModeId = NORMAL SensorDescription = "Imagery type:Pushbroom. ImageryMode:Mono,Stereo. ExposureTimeMode:Long,Middle,Shor- t. CompressionMode:NonComp,DCT. Q-table:32 patterns. H-table:4 patterns. SwathMode:F(Full),N(Nominal),H(Ha- lf). First pixel number:1(F),297(N),1172(H)." SensorDescription2 = "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)." StartTime = 2008-07-17T20:15:35.204835 StopTime = 2008-07-17T20:16:05.462060 OriginalStartTime = 2008-07-17T20:15:35.206200 OriginalStopTime = 2008-07-17T20:16:05.463700 ExposureModeId = MIDDLE ExposureDuration = 3.250000 OriginalSpacecraftClockStartCount = "900360927.2715 " OriginalSpacecraftClockStopCount = "900360957.5290 " SpacecraftClockStartCount = 900360927.270135 SpacecraftClockStopCount = 900360957.527360 OriginalLineSamplingInterval = 6.500000 LineSamplingInterval = 6.499941 SwathModeId = NOMINAL IlluminationCondition = EVENING End_Group Group = Archive ProductId = TC1S2B0_01_03448S320E0799 SoftwareName = RGC_TC_s_Level2B0 SoftwareVersion = 1.0.0 ProcessVersionId = L2B ProductCreationTime = 2013-06-08T05:20:08 ProgramStartTime = 2013-06-08T05:13:02 ProducerId = LISM ProductSetId = TC_s_Level2B0 ProductVersionId = 01 RegisteredProduct = Y Level2AFileName = TC1S2A0_02DMN03448_002_0033.img SpiceMetakernelFileName = RGC_INF_TCv401IK_MIv200IK_SPv105IK_RISE- 100i_05_LongCK_D_V02_de421_110706.mk DataSetId = SLN-L-TC-3-S-LEVEL2B0-V1.0 ImageValueType = RADIANCE ImageUnit = W/m**2/micron/sr MinForStatisticalEvaluation = 0 MaxForStatisticalEvaluation = 32767 SceneMaximumDn = 3161 SceneMinimumDn = 0 SceneAverageDn = 345.0 SceneStdevDn = 266.9 SceneModeDn = 18 ShadowedAreaMinimum = 0 ShadowedAreaMaximum = 0 ShadowedAreaPercentage = 0 InvalidType = (SATURATION, MINUS, DUMMY_DEFECT, OTHER) InvalidValue = (-20000, -21000, -22000, -23000) InvalidPixels = (0, 2, 0, 0) DarkFileName = TC1_DRK_02952_04739_M_C_b05.csv FlatFileName = TC1_FLT_00293_04739_N_C_b05.csv EfficFileName = TC1_EFF_PRFLT_N_N_v01.csv NonlinFileName = TC1_NLT_PRFLT_N_N_v01.csv RadCnvCoef = 3.790009 L2aDeadPixelThreshold = 30 L2aSaturationThreshold = 1023 DarkValidMinimum = -5 RadianceSaturationThreshold = 425.971000 UpperLeftLatitude = -32.789566 UpperLeftLongitude = 80.631630 UpperRightLatitude = -32.776189 UpperRightLongitude = 79.085159 LowerLeftLatitude = -31.266527 LowerLeftLongitude = 80.634632 LowerRightLatitude = -31.254154 LowerRightLongitude = 79.107535 End_Group Group = BandBin FilterName = BroadBand Center = 640nm Width = 420nm End_Group Group = Kernels NaifCkCode = -131350 NaifFrameCode = -131351 LeapSecond = $base/kernels/lsk/naif0012.tls TargetAttitudeShape = ($base/kernels/pck/pck00009.tpc, $kaguya/kernels/pck/moon_080317.tf, $kaguya/kernels/pck/moon_assoc_me.tf) TargetPosition = (Table, $kaguya/kernels/tspk/moon_pa_de421_1900-2- 050.bpc, $kaguya/kernels/tspk/de421.bsp) InstrumentPointing = (Table, $kaguya/kernels/ck/SEL_M_ALL_D_V02.BC, $kaguya/kernels/fk/SEL_V01.TF) Instrument = $kaguya/kernels/ik/SEL_TC_V01.TI SpacecraftClock = $kaguya/kernels/sclk/SEL_M_V01.TSC InstrumentPosition = (Table, $kaguya/kernels/spk/SEL_M_071020_090610_S- GMH_02.BSP) InstrumentAddendum = $kaguya/kernels/iak/kaguyaTcAddendum007.ti ShapeModel = $base/dems/ldem_128ppd_Mar2011_clon180_ra- dius_pad.cub InstrumentPositionQuality = Reconstructed InstrumentPointingQuality = Reconstructed CameraVersion = 2 Source = isis End_Group End_Object Object = Label Bytes = 65536 End_Object Object = Table Name = InstrumentPointing StartByte = 32628064 Bytes = 1088 Records = 17 ByteOrder = Lsb TimeDependentFrames = (-131000, 1) ConstantFrames = (-131350, -131320, -131000) ConstantRotation = (0.96621809368586, -9.13008670889038e-04, -0.25772419725208, 7.985363329953e-04, 0.99999953055997, -5.4883472482896e-04, 0.25772457735688, 3.24491906175013e-04, 0.96621836917501) CkTableStartTime = 269597800.33372 CkTableEndTime = 269597830.59744 CkTableOriginalSize = 4657 FrameTypeCode = 3 Description = "Created by spiceinit" Kernels = ($kaguya/kernels/ck/SEL_M_ALL_D_V02.BC, $kaguya/kernels/fk/SEL_V01.TF) Group = Field Name = J2000Q0 Type = Double Size = 1 End_Group Group = Field Name = J2000Q1 Type = Double Size = 1 End_Group Group = Field Name = J2000Q2 Type = Double Size = 1 End_Group Group = Field Name = J2000Q3 Type = Double Size = 1 End_Group Group = Field Name = AV1 Type = Double Size = 1 End_Group Group = Field Name = AV2 Type = Double Size = 1 End_Group Group = Field Name = AV3 Type = Double Size = 1 End_Group Group = Field Name = ET Type = Double Size = 1 End_Group End_Object Object = Table Name = InstrumentPosition StartByte = 32629152 Bytes = 168 Records = 3 ByteOrder = Lsb CacheType = HermiteSpline SpkTableStartTime = 269597800.33372 SpkTableEndTime = 269597830.59744 SpkTableOriginalSize = 4657.0 Description = "Created by spiceinit" Kernels = $kaguya/kernels/spk/SEL_M_071020_090610_SGMH_02.- BSP Group = Field Name = J2000X Type = Double Size = 1 End_Group Group = Field Name = J2000Y Type = Double Size = 1 End_Group Group = Field Name = J2000Z Type = Double Size = 1 End_Group Group = Field Name = J2000XV Type = Double Size = 1 End_Group Group = Field Name = J2000YV Type = Double Size = 1 End_Group Group = Field Name = J2000ZV Type = Double Size = 1 End_Group Group = Field Name = ET Type = Double Size = 1 End_Group End_Object Object = Table Name = BodyRotation StartByte = 32629320 Bytes = 128 Records = 2 ByteOrder = Lsb TimeDependentFrames = (31006, 1) ConstantFrames = (31001, 31007, 31006) ConstantRotation = (0.99999987325471, -3.29285422375571e-04, 3.80869618671387e-04, 3.29286000210947e-04, 0.99999994578431, -1.45444093783627e-06, -3.80869119096078e-04, 1.57985578682691e-06, 0.99999992746811) CkTableStartTime = 269597800.33372 CkTableEndTime = 269597830.59744 CkTableOriginalSize = 2 FrameTypeCode = 6 Description = "Created by spiceinit" Kernels = ($kaguya/kernels/tspk/moon_pa_de421_1900-2050.bpc, $kaguya/kernels/tspk/de421.bsp, $base/kernels/pck/pck00009.tpc, $kaguya/kernels/pck/moon_080317.tf, $kaguya/kernels/pck/moon_assoc_me.tf) SolarLongitude = 156.58130254847 Group = Field Name = J2000Q0 Type = Double Size = 1 End_Group Group = Field Name = J2000Q1 Type = Double Size = 1 End_Group Group = Field Name = J2000Q2 Type = Double Size = 1 End_Group Group = Field Name = J2000Q3 Type = Double Size = 1 End_Group Group = Field Name = AV1 Type = Double Size = 1 End_Group Group = Field Name = AV2 Type = Double Size = 1 End_Group Group = Field Name = AV3 Type = Double Size = 1 End_Group Group = Field Name = ET Type = Double Size = 1 End_Group End_Object Object = Table Name = SunPosition StartByte = 32629448 Bytes = 112 Records = 2 ByteOrder = Lsb CacheType = Linear SpkTableStartTime = 269597800.33372 SpkTableEndTime = 269597830.59744 SpkTableOriginalSize = 2.0 Description = "Created by spiceinit" Kernels = ($kaguya/kernels/tspk/moon_pa_de421_1900-2050.bp- c, $kaguya/kernels/tspk/de421.bsp) Group = Field Name = J2000X Type = Double Size = 1 End_Group Group = Field Name = J2000Y Type = Double Size = 1 End_Group Group = Field Name = J2000Z Type = Double Size = 1 End_Group Group = Field Name = J2000XV Type = Double Size = 1 End_Group Group = Field Name = J2000YV Type = Double Size = 1 End_Group Group = Field Name = J2000ZV Type = Double Size = 1 End_Group Group = Field Name = ET Type = Double Size = 1 End_Group End_Object Object = History Name = IsisCube StartByte = 32629560 Bytes = 1506 End_Object Object = OriginalLabel Name = IsisCube StartByte = 32620890 Bytes = 7174 End_Object Object = NaifKeywords BODY_CODE = 301 BODY301_RADII = (1737.4, 1737.4, 1737.4) BODY_FRAME_CODE = 31001 INS-131351_SWAP_OBSERVER_TARGET = TRUE INS-131351_LIGHTTIME_CORRECTION = NONE INS-131351_LT_SURFACE_CORRECT = FALSE INS-131351_FOCAL_LENGTH = 72.45 INS-131351_PIXEL_PITCH = 0.007 CLOCK_ET_-131_900360927.270135_COMPUTED = 736e5568bc11b041 INS-131351_TRANSX = (0.0, 0.0, -0.007) INS-131351_TRANSY = (0.0, -0.007, 0.0) INS-131351_ITRANSS = (0.0, 0.0, -142.857142857) INS-131351_ITRANSL = (0.0, -142.857142857, 0.0) INS-131351_BORESIGHT_SAMPLE = 2048.0 INS-131351_BORESIGHT_LINE = 0.5 INS-131351_DISTORTION_COEF_X = (-9.6499e-04, 9.8441e-04, 8.5773e-06, -3.7438e-06) INS-131351_DISTORTION_COEF_Y = (-0.0013796, 1.3502e-05, 2.7251e-06, -6.1938e-06) INS-131351_BORESIGHT = (-0.0725, 0.0214) End_Object End_Object Object = OriginalLabel PDS_VERSION_ID = PDS3 /* ** FILE FORMAT ** */ RECORD_TYPE = UNDEFINED FILE_NAME = TC1S2B0_01_03448S320E0799.img /* ** POINTERS TO START BYTE OFFSET OF OBJECTS IN FILE ** */ ^IMAGE = (TC1S2B0_01_03448S320E0799.img, 1 ) /* ** BASIC INFORMATION ** */ MISSION_NAME = SELENE DATA_SET_ID = SLN-L-TC-3-S-LEVEL2B0-V1.0 DATA_SET_NAME = "SELENE MOON TC 3 MONO LEVEL2B0 V1.0" L2DB_ORIGINAL_ID = TC_s_Level2B0 PRODUCT_ID = TC1S2B0_01_03448S320E0799 INSTRUMENT_TYPE = IMAGER INSTRUMENT_ID = TC1 INSTRUMENT_NAME = "TERRAIN CAMERA 1" INSTRUMENT_HOST_NAME = "SELENE MAIN ORBITER" TARGET_TYPE = SATELLITE TARGET_NAME = MOON START_TIME = 2008-07-17T20:15:35.206200 STOP_TIME = 2008-07-17T20:16:05.463700 /* ** GENERAL DATA DESCRIPTION PARAMETERS ** */ SOFTWARE_NAME = RGC_TC_s_Level2B0 SOFTWARE_VERSION = 1.0.0 PROCESS_VERSION_ID = L2B PRODUCT_CREATION_TIME = 2013-06-08T05:20:08 PROGRAM_START_TIME = 2013-06-08T05:13:02 PRODUCER_ID = LISM PRODUCT_SET_ID = TC_s_Level2B0 PRODUCT_VERSION_ID = 01 REGISTERED_PRODUCT = Y ILLUMINATION_CONDITION = EVENING LEVEL2A_FILE_NAME = TC1S2A0_02DMN03448_002_0033.img SPICE_METAKERNEL_FILE_NAME = RGC_INF_TCv401IK_MIv200IK_SPv105IK_RISE1- 00i_05_LongCK_D_V02_de421_110706.mk /* ** SCENE RELATED PARAMETERS ** */ MISSION_PHASE_NAME = Nominal REVOLUTION_NUMBER = 3448 STRIP_SEQUENCE_NUMBER = 2 SCENE_SEQUENCE_NUMBER = 33 UPPER_LEFT_DAYTIME_FLAG = Day UPPER_RIGHT_DAYTIME_FLAG = Day LOWER_LEFT_DAYTIME_FLAG = Day LOWER_RIGHT_DAYTIME_FLAG = Day 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:OFF, MV:OFF, MN:OFF, SP:ON) EXPOSURE_MODE_ID = MIDDLE LINE_EXPOSURE_DURATION = 3.250000 SPACECRAFT_CLOCK_START_COUNT = "900360927.2715 " SPACECRAFT_CLOCK_STOP_COUNT = "900360957.5290 " CORRECTED_SC_CLOCK_START_COUNT = 900360927.270135 CORRECTED_SC_CLOCK_STOP_COUNT = 900360957.527360 CORRECTED_START_TIME = 2008-07-17T20:15:35.204835 CORRECTED_STOP_TIME = 2008-07-17T20:16:05.462060 LINE_SAMPLING_INTERVAL = 6.500000 CORRECTED_SAMPLING_INTERVAL = 6.499941 UPPER_LEFT_LATITUDE = -32.789566 UPPER_LEFT_LONGITUDE = 80.631630 UPPER_RIGHT_LATITUDE = -32.776189 UPPER_RIGHT_LONGITUDE = 79.085159 LOWER_LEFT_LATITUDE = -31.266527 LOWER_LEFT_LONGITUDE = 80.634632 LOWER_RIGHT_LATITUDE = -31.254154 LOWER_RIGHT_LONGITUDE = 79.107535 LOCATION_FLAG = A ROLL_CANT = NO SCENE_CENTER_LATITUDE = -32.025538 SCENE_CENTER_LONGITUDE = 79.866101 INCIDENCE_ANGLE = 79.791 EMISSION_ANGLE = 14.859 PHASE_ANGLE = 81.838 SOLAR_AZIMUTH_ANGLE = 277.284 FOCAL_PLANE_TEMPERATURE = 17.48 TELESCOPE_TEMPERATURE = 17.22 SATELLITE_MOVING_DIRECTION = +1 FIRST_SAMPLED_LINE_POSITION = UPPERMOST FIRST_DETECTOR_ELEM_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 = NOMINAL FIRST_PIXEL_NUMBER = 297 LAST_PIXEL_NUMBER = 3788 SPACECRAFT_ALTITUDE = 113.081 SPACECRAFT_GROUND_SPEED = 1.523 TC1_TELESCOPE_TEMPERATURE = 17.33 TC2_TELESCOPE_TEMPERATURE = 16.99 DPU_TEMPERATURE = 11.35 TM_TEMPERATURE = 16.30 TM_RADIATOR_TEMPERATURE = 11.67 Q_TABLE_ID = SF008S_A HUFFMAN_TABLE_ID = DCTNML_A DATA_COMPRESSION_PERCENT_MEAN = 17.6 DATA_COMPRESSION_PERCENT_MAX = 20.4 DATA_COMPRESSION_PERCENT_MIN = 15.6 /* ** DESCRIPTION OF OBJECTS CONTAINED IN THE FILE ** */ Object = IMAGE COMPRESSION_TYPE = DCT_DECOMPRESSED COMPRESSION_PERCENT = 14.7 NOMINAL_LINE_NUMBER = 4088 NOMINAL_OVERLAP_LINE_NUMBER = 568 OVERLAP_LINE_NUMBER = 568 LINES = 4656 LINE_SAMPLES = 3496 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 = 3161 SCENE_MINIMUM_DN = 0 SCENE_AVERAGE_DN = 345.0 SCENE_STDEV_DN = 266.9 SCENE_MODE_DN = 18 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, 2, 0, 0) End_Object Object = PROCESSING_PARAMETERS DARK_FILE_NAME = TC1_DRK_02952_04739_M_C_b05.csv FLAT_FILE_NAME = TC1_FLT_00293_04739_N_C_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 End_Object Object = Statistics MeanValue = 4.4850017705493 StandardDeviation = 3.470610515188 MinimumValue = -273.0 MaximumValue = 41.093 PercentHIS = 0.0 PercentHRS = 0.0 PercentLIS = 0.0 PercentLRS = 0.0 PercentNull = 0.0 TotalPixels = 16277376.0 End_Object Object = Geometry BandsUsed = 1 ReferenceBand = 1 OriginalBand = 1 Target = MOON StartTime = 2008-07-17T20:15:35.1500815 EndTime = 2008-07-17T20:16:05.4138068 CenterLine = 2328.0 CenterSample = 1748.0 CenterLatitude = -32.029549234266 CenterLongitude = 79.870272020315 CenterRadius = 1736712.5376081 RightAscension = 349.02345687877 Declination = 46.096711215966 UpperLeftLongitude = 80.643259700944 UpperLeftLatitude = -32.788902616188 LowerLeftLongitude = 80.644802406807 LowerLeftLatitude = -31.267405030743 LowerRightLongitude = 79.109187973418 LowerRightLatitude = -31.262355856091 UpperRightLongitude = 79.088461118662 UpperRightLatitude = -32.786412418797 PhaseAngle = 81.765924928992 EmissionAngle = 14.733877019508 IncidenceAngle = 79.794593365301 NorthAzimuth = 89.342277203456 OffNadir = 13.80783306372 SolarLongitude = 156.57991225668 LocalTime = 17.166849212264 TargetCenterDistance = 1850.6924455296 SlantDistance = 117.60534009361 SampleResolution = 11.362834791653 LineResolution = 11.362834791653 PixelResolution = 11.362834791653 MeanGroundResolution = 11.475306207817 SubSolarAzimuth = 7.8194246341667 SubSolarGroundAzimuth = 277.28214288415 SubSolarLatitude = 0.67533584007578 SubSolarLongitude = 2.3675338363511 SubSpacecraftAzimuth = 270.06602477645 SubSpacecraftGroundAzimuth = 180.82854483258 SubSpacecraftLatitude = -32.955495369011 SubSpacecraftLongitude = 79.854313939634 ParallaxX = 0.26294965050378 ParallaxY = -0.0038027366701225 ShadowX = -0.70409602118271 ShadowY = -5.5099540668607 HasLongitudeBoundary = FALSE HasNorthPole = FALSE HasSouthPole = FALSE ObliqueSampleResolution = 11.749176556564 ObliqueLineResolution = 11.749176556564 ObliquePixelResolution = 11.749176556564 ObliqueDetectorResolution = 11.749176556564 End_Object Object = Polygon CentroidLine = 2337.9009837535 CentroidSample = 1745.8680213956 CentroidLatitude = -32.026364452424 CentroidLongitude = 79.871264024716 CentroidRadius = 1736718.5941665 SurfaceArea = 1833.2272708465 GlobalCoverage = 0.004837 SampleIncrement = 350 LineIncrement = 466 GisFootprint = "MULTIPOLYGON (((80.6434876663541473 -32.7890226016897728, 80.4867149622814537 -32.7889790178580185, 80.3300818155634602 -32.7889241901795998, 80.1743499766139109 -32.7863924359174561, 80.0177113335634829 -32.7850985578128089, 79.8609096144315771 -32.7848234399050114, 79.7046308149245135 -32.7856481738699159, 79.5493563354836795 -32.7870226859914453, 79.3958767772271159 -32.7899330186778215, 79.2408702054832617 -32.7880206664435292, 79.0882347398693355 -32.7865258350765103, 79.0884663323392374 -32.6327625597091711, 79.0889927321426143 -32.4792344194404308, 79.0928195529679527 -32.3287999953625871, 79.0984765240582703 -32.1793966642782578, 79.1007396266613370 -32.0251917303287073, 79.0999637067579329 -31.8674461676154479, 79.1000653863259657 -31.7118329729199147, 79.1036595396319200 -31.5618330875657200, 79.1080300073396927 -31.4135071179673488, 79.1089655294199048 -31.2622233587428546, 79.2640915501579002 -31.2664668168857993, 79.4173506391043844 -31.2689408031720930, 79.5699828473166093 -31.2701481282308009, 79.7214985199506998 -31.2628554037968804, 79.8757661395609517 -31.2647752169161173, 80.0298944873273825 -31.2661989417618464, 80.1835397744880396 -31.2683823818172222, 80.3371488315513034 -31.2694860909033281, 80.4921107196288688 -31.2680841915774828, 80.6450270328969481 -31.2672789832655802, 80.6466247230461448 -31.4182090005113928, 80.6477766393020659 -31.5703328473075899, 80.6460430612381600 -31.7257264129554599, 80.6473981726540075 -31.8763274616077510, 80.6461211118626693 -32.0288063924445083, 80.6444702720300910 -32.1809419224140285, 80.6412301860177934 -32.3351498799972461, 80.6419329580510151 -32.4858593622135245, 80.6426892951403858 -32.6378194898311520, 80.6434876663541473 -32.7890226016897728)))" Group = Mapping TargetName = MOON EquatorialRadius = 1737400.0 PolarRadius = 1737400.0 LatitudeType = Planetocentric LongitudeDirection = PositiveEast LongitudeDomain = 360 MinimumLatitude = -32.790632362852 MaximumLatitude = -31.262183375965 MinimumLongitude = 79.087451383936 MaximumLongitude = 80.64832541182 PixelResolution = 11.329319211842 ProjectionName = Sinusoidal CenterLongitude = 79.870272020315 End_Group End_Object End_Object End ```
rbeyer commented 2 years ago

This is expected behavior based on the module's design. Of course, that doesn't mean that it is the right behavior.

The pvl module's approach is: read everything, but write out standards-compliant PDS3 labels by default. From the README:

On the flip side, when dumping a Python object to PVL text (via pvl.dumps() and pvl.dump()), the library will default to writing PDS3-Standards-compliant PVL text, which in some ways is the most restrictive, but the most likely version of PVL text that you need if you're writing it out (this is different from pre-1.0 versions of pvl).

Don't get me wrong, these different versions of PVL-text, the lack of consistent validating software, and the permissiveness of the format structure over three decades have resulted in quite a mess. The pre-1.0 versions of pvl did not distinguish between the different "dialects" of PVL-text: strict PVL, ODL, PDS3 ODL, and ISIS PVL-text that pvl now does. The pre-1.0 pvl read as much as it could, and wrote out PVL-text which conformed to the PVL standard, but with some options. You can make pvl write out PVL-text like the pre-1.0 architecture via:

pvl.dumps(label, encoder=pvl.PVLEncoder(end_delimiter=False, newline='\r\n')).encode()

It decided to forego the optional-but-recommended end-delimiter, and opted for \r\n newlines.

This meant that the PVL-text that pvl used to write out would not validate against the PDS3 standard, at the very least. And while we use the acronym "PVL" all the time and think about PDS3 labels as "PVL" they really aren't. In fact, almost no one really uses strict PVL. The PDS3 standard is actually a variation on ODL (neither a strict sub-set nor a super-set of ODL, because that would have been too useful), which is itself a variation on PVL.

Therefore it would be incorrect to assume that strict PVL is a superset of all possible PVL-text, and assume that it spans a wider gamut of PVL-text possibilities. No matter what "output dialect" we settled on for the "default," there exists broken PVL-text that could be read by the omnivorous parser but would not be able to be written for any particular dialect (so just as you have this example that cannot be written to PDS3 PVL-text, we could also construct examples that do the same for strict PVL PVL-text, ODL PVL-text, etc.).

Given the spread of bad options, the decision was made for the default writing-out to be PDS3 based on the presumptions described in the README. If the output would not be written out to a strict PDS3 format, what dialect would you suggest? And why?

I suppose we could rescind the "default" option, and require an output dialect choice, which might make users more "aware" of their choices, but it might not.

You talk about a "round trip" through the library, but that's not really how pvl is constructed. The "load" operation is atomic and distinct, it converts PVL-text to a Python dict-like, but that Python dict-like doesn't "remember" that it was once PVL-text. The "dump" operations are similarly distinct, they take any Python dict-like and attempt to write out the PVL-text specified by the encoder.

I suppose we could attempt to determine whether the "loaded" PVL-text conformed to a particular dialect, and hold on to that in the returned dict-like. However, that would require multiple decodings of the PVL-text to determine which dialects it conformed to (or none). And then if the "dump" operations got a dict-like that had been made by the loaders they could use that dialect-type as the "default" dialect to encode. However, we'd still error if the loaders could not determine a dialect type, or if the Python dict-like was not made by the pvl module.

Alternately, rather than pay the price on "load" we could pay it on "dump." If a Python dict-like can't be written out as PDS3-compliant PVL-text, maybe try ODL or PVL, and if those work, write them out that way, and only fail if no possible dialect can be used.

I'm not super-wild about these options, because I think it will increase the amount of non-PDS3-compliant PVL-text, but maybe that's a silly concern.

Another alternate would be if the Python dict-like couldn't be written as PDS3-compliant PVL-text, attempt the alternate encodings, and still error, but let the user know that maybe specifying a different encoding would work (and then be explicit about what the code line is that would work). This would increase the process time of the error process (but typically dict-likes processed by pvl are pretty small), would maintain the current behavior, but would provide better feedback to the user.

jlaura commented 2 years ago

@rbeyer I appreciate the walk through the logic and apologize for the delay in responding. The issue got lost in a mountain of start of the year GitHub notifications.

First, this is a bummer that we do not have one consistent standard that we can depend upon. Having gotten that out of the way, I understand why the choice was made and think that trying to consistently guess what flavor of the specification in the one that is desired is not a particularly attractive solution.

I think the best approach is to close this issue and chock this up to case of RTFM for the user. Question: Are the differences between these PVL flavors documented anywhere or is this knowledge bouncing around in the maintainers heads? I ask because my tenuous, maybe helpful solution, would be to link the quoted documentation text above to some indication of what PDS3 compliant PVL supports that other formats do not. I am also very happy to see this closed with a read the docs.

I suspect I am further muddying the waters because what I really want to do is push the PVL to JSON, remap the obnoxious duplicate keys (OBJECT = TABLE anyone...), and then push this into JSONB for efficient database work. This is not pertinent to this issue, but an example where this 'standard' is meeting another 'standard' (both pejorative quotes because both are permissive enough to be problematic at the implementation level) and causing lots of friction.

rbeyer commented 2 years ago

@jlaura that's a fair solution. There was an existing documentation page: https://pvl.readthedocs.io/en/latest/standards.html, to which I added the third paragraph there which might be useful, but please feel free to PR any other items from our exchange above that you think a new user would find valuable.

Tell me more about this conversion to JSON that you need. I'm not sure why that would require a "round trip" through pvl. Seems like you'd want to do a pvl.load() to get started, and then manipulate the returned dict-like in Python to eliminate duplicate keys, etc., and then just use the Python standard library json module to write that dict out to a .json file rather than trying to write out some PVL-text, but there's clearly other stuff going on.

jlaura commented 2 years ago

I'll check that and get a PR in if appropriate. Thanks!

The only reason to round trip is entirely lazy and artificial. Support I don't want to store the ISIS data or the ISIS label, but want to provide a cloud optimized geotiff with an associated ISIS label. I might: preprocess to cub -> pull the isis label into a DB (as JSONB with remapped keys) -> convert the cub to COG and delete the cub -> write ancillary data (e.g., the label). It would be 'nice' to keep the data only in a DB AND use the data in the DB for efficient queries (so JSONB is a must). In all honesty, this is asking the DB/Data to do a lot all at once for convenience and a small amount of storage savings.

^^^^^^^^^^^^nice to have but not a thing.

rbeyer commented 2 years ago

I'm going to close this, but feel free to re-open if needed.