From 12c1aede216d468d474172fb2127778e487bc15a Mon Sep 17 00:00:00 2001 From: Jay Laura <jlaura@usgs.gov> Date: Fri, 28 Jul 2023 07:28:47 -0700 Subject: [PATCH] Updates for # in PVL. (#195) * Fixes 194 and simplifies serial generation. * Updates CTX label to include a comment. * Fixces SQLAlchemy Bind for pytests * Updates GDAL dependency * Fixes pandas import for frame_equal test * Fixes isis serial number generation. * Fixes proj string on test data. * Fixes conditional imports for gdal, osr, ogr. * Fixes projection test for gdal > 3. * Upates CHANGELOG to include testing fixes. --- CHANGELOG.md | 3 + environment.yml | 4 +- plio/examples/CTX/ctx.pvl | 2 + ...s_MGS_MOLA_ClrShade_MAP2_90.0N0.0_POLA.tif | Bin 9280 -> 10510 bytes plio/io/__init__.py | 20 +---- plio/io/extract_metadata.py | 5 +- plio/io/io_db.py | 9 +- plio/io/io_gdal.py | 4 +- plio/io/isis_serial_number.py | 82 ++---------------- plio/io/tests/test_io_bae.py | 2 +- plio/io/tests/test_io_gdal.py | 14 +-- plio/io/tests/test_metadata.py | 4 +- 12 files changed, 37 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b34bd..ab23f36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ release. ## [Unreleased] ### Fixed +- Tests for gdal > 3 and pvl > 1.0. This includes fixing the `k` value on the MOLA polar stereographic test data and updating the proj string for GDAL > 3 (new fields are included). +- Conditional GDAL import to support gdal > 3.0 +- `generate_isis_serial` to work on cubes that have been run through `jigsaw` by removing the custom `SerialNumberDecoder`. Fixes [#194](https://github.com/DOI-USGS/plio/issues/194) - Updated `create_pvl_header()` to add a newline (`\n`) character to the end of the pvl string. This ensures that the control networks can be written, then read back in using pvl 1.3.0 [#193](https://github.com/USGS-Astrogeology/plio/pull/193) ## [1.5.3]() diff --git a/environment.yml b/environment.yml index 37ea027..233b9cc 100644 --- a/environment.yml +++ b/environment.yml @@ -1,10 +1,8 @@ name: plio channels: - conda-forge - - usgs-astrogeology dependencies: - - libgdal < 3 - - gdal < 3 + - gdal - numpy - pyproj - h5py diff --git a/plio/examples/CTX/ctx.pvl b/plio/examples/CTX/ctx.pvl index b062798..a44e44c 100644 --- a/plio/examples/CTX/ctx.pvl +++ b/plio/examples/CTX/ctx.pvl @@ -67,6 +67,8 @@ Object = IsisCube End_Group End_Object +# Jigged + Object = Label Bytes = 65536 End_Object diff --git a/plio/examples/Projections/Mars_MGS_MOLA_ClrShade_MAP2_90.0N0.0_POLA.tif b/plio/examples/Projections/Mars_MGS_MOLA_ClrShade_MAP2_90.0N0.0_POLA.tif index e47b318148f7c268ebf1aa4e20ee7051695bf24c..eed8e641775cca2fcef4ab8dba81782982a2d73c 100644 GIT binary patch delta 324 zcmX@$(HF$#>8Zuwpt6z8N@XI48xxz_!~i8GIn{|JN_Owm7#R4Pc^H@(7#NOeFfcT= z@G!73FfiQHU|?9^&dd<Pz`#(U&cLvtori&wfq_9mgMndVI}<}J0|P^b#>Oi-Op`m9 zWrR5x7#J8h7#J8C{?{{dY-|r<;F!FfSzeHhftzDPJ0k--12357nEaesU6GN2iw7ji z!oUrtSs6sYG&6%Zm}Z)+&mzzC!G3Zevq-)EOHW7bwI}@N+oU>xYysiYyzIRE^1K>% z=U@fD#N1Q`TLqYqOJYfBE)w50CnvL@I6pH5Ni3i!GdC5aC@3{8wJ0?&IaMJywJ0+s WGcm8G24p@11B2Kmh;5U7nXLf`22r8_ delta 21 ccmeARI^e<K>8ZuQ!N9;U*+Io^qu(cG06)P71^@s6 diff --git a/plio/io/__init__.py b/plio/io/__init__.py index 7c0f093..b67fa43 100644 --- a/plio/io/__init__.py +++ b/plio/io/__init__.py @@ -1,25 +1,13 @@ # Conditional imports for GDAL import importlib import warnings -import sys try: - gdal = importlib.util.find_spec('gdal') - ogr = importlib.util.find_spec('osgeo.ogr') - osr = importlib.util.find_spec('osr') - - gdal = gdal.loader.load_module() - ogr = ogr.loader.load_module() - osr = osr.loader.load_module() - gdal.UseExceptions() + gdal = importlib.import_module('osgeo.gdal') + osr = importlib.import_module('osgeo.osr') + ogr = importlib.import_module('osgeo.ogr') except: - try: - gdal = importlib.util.find_spec('osgeo.gdal') - gdal = gdal.loader.load_module() - except: - gdal = None - ogr = None - osr = None + gdal = osr = ogr = None def conditional_gdal(func): def has_gdal(*args, **kwargs): diff --git a/plio/io/extract_metadata.py b/plio/io/extract_metadata.py index 6d94c2a..ea1e370 100644 --- a/plio/io/extract_metadata.py +++ b/plio/io/extract_metadata.py @@ -1,5 +1,6 @@ -from plio.io import osr, conditional_gdal - + +from plio.io import conditional_gdal, osr + import_options = ['ImportFromWkt', 'ImportFromProj4', 'ImportFromEPSG', 'ImportFromUSGS', 'ImportFromXML'] diff --git a/plio/io/io_db.py b/plio/io/io_db.py index 988ec58..5903484 100644 --- a/plio/io/io_db.py +++ b/plio/io/io_db.py @@ -1,9 +1,10 @@ from sqlalchemy import Column, Integer, String, create_engine, orm -from sqlalchemy.ext import declarative +from sqlalchemy.orm import declarative_base +from sqlalchemy import MetaData from plio.sqlalchemy_json.alchemy import NestedJsonObject -Base = declarative.declarative_base() +Base = declarative_base() def setup_db_session(db): @@ -21,8 +22,8 @@ def setup_db_session(db): A SQLAlchemy session object """ engine = create_engine('sqlite:///{}'.format(db)) - Base.metadata.bind = engine - Base.metadata.create_all() + meta = MetaData() + meta.create_all(engine) return orm.sessionmaker(bind=engine)() diff --git a/plio/io/io_gdal.py b/plio/io/io_gdal.py index fc4222a..749fa40 100644 --- a/plio/io/io_gdal.py +++ b/plio/io/io_gdal.py @@ -7,11 +7,9 @@ import affine import numpy as np import pvl - -from plio.io import extract_metadata, conditional_gdal from plio.geofuncs import geofuncs from plio.utils.utils import find_in_dict -from plio.io import gdal, ogr, osr +from plio.io import gdal, ogr, osr, extract_metadata, conditional_gdal NP2GDAL_CONVERSION = { "byte": 1, diff --git a/plio/io/isis_serial_number.py b/plio/io/isis_serial_number.py index faba3bf..725e9a1 100644 --- a/plio/io/isis_serial_number.py +++ b/plio/io/isis_serial_number.py @@ -1,3 +1,4 @@ +import datetime import warnings import pvl @@ -71,7 +72,7 @@ def generate_serial_number(label): The ISIS compatible serial number """ if not isinstance(label, PVLModule): - label = pvl.load(label, decoder=SerialNumberDecoder()) + label = pvl.load(label) # Get the translation information translation = get_isis_translation(label) @@ -97,82 +98,11 @@ def generate_serial_number(label): serial_entry = search_translation[serial_entry] elif '*' in search_translation.keys() and search_translation['*'] != '*': serial_entry = search_translation['*'] + if isinstance(serial_entry, datetime.datetime): + # PVL returns datetime objects now. Convert these to string and strip trailing zeros on microseconds. + serial_entry = serial_entry.strftime('%Y-%m-%dT%H:%M:%S.%f').rstrip('0') serial_number.append(serial_entry) except: pass - + return '/'.join(serial_number) - - -class SerialNumberDecoder(pvl.decoder.PVLDecoder): - """ - A PVL Decoder class to handle cube label parsing for the purpose of creating a valid ISIS - serial number. Inherits from the PVLDecoder in planetarypy's pvl module. - """ - - def decode_simple_value(self, value: str): - """Returns a Python object based on *value*, assuming - that *value* can be decoded as a PVL Simple Value:: - - <Simple-Value> ::= (<Numeric> | <String>) - - Modified from https://pvl.readthedocs.io/en/stable/_modules/pvl/decoder.html#PVLDecoder.decode_simple_value - Modification entails stripping datetime from list of functions. - """ - for d in ( - self.decode_quoted_string, - self.decode_non_decimal, - self.decode_decimal, - ): - try: - return d(value) - except ValueError: - pass - - if value.casefold() == self.grammar.none_keyword.casefold(): - return None - - if value.casefold() == self.grammar.true_keyword.casefold(): - return True - - if value.casefold() == self.grammar.false_keyword.casefold(): - return False - - return self.decode_unquoted_string(value) - - def decode_unquoted_string(self, value: str) -> str: - """Returns a Python ``str`` if *value* can be decoded - as an unquoted string, based on this decoder's grammar. - Raises a ValueError otherwise. - - Modified from: https://pvl.readthedocs.io/en/stable/_modules/pvl/decoder.html#PVLDecoder.decode_unquoted_string - Modification entails removal of decode_datetime call - """ - for coll in ( - ("a comment", chain.from_iterable(self.grammar.comments)), - ("some whitespace", self.grammar.whitespace), - ("a special character", self.grammar.reserved_characters), - ): - for item in coll[1]: - if item in value: - raise ValueError( - "Expected a Simple Value, but encountered " - f'{coll[0]} in "{self}": "{item}".' - ) - - agg_keywords = self.grammar.aggregation_keywords.items() - for kw in chain.from_iterable(agg_keywords): - if kw.casefold() == value.casefold(): - raise ValueError( - "Expected a Simple Value, but encountered " - f'an aggregation keyword: "{value}".' - ) - - for es in self.grammar.end_statements: - if es.casefold() == value.casefold(): - raise ValueError( - "Expected a Simple Value, but encountered " - f'an End-Statement: "{value}".' - ) - - return str(value) diff --git a/plio/io/tests/test_io_bae.py b/plio/io/tests/test_io_bae.py index 4031703..11bffd9 100644 --- a/plio/io/tests/test_io_bae.py +++ b/plio/io/tests/test_io_bae.py @@ -3,7 +3,7 @@ import os import numpy as np import pandas as pd -from pandas.util.testing import assert_frame_equal +from pandas.testing import assert_frame_equal from plio.io.io_bae import socetset_keywords_to_dict, read_gpf, save_gpf, read_ipf, save_ipf from plio.examples import get_path diff --git a/plio/io/tests/test_io_gdal.py b/plio/io/tests/test_io_gdal.py index 0423ca3..a8bc23d 100644 --- a/plio/io/tests/test_io_gdal.py +++ b/plio/io/tests/test_io_gdal.py @@ -39,7 +39,7 @@ class TestMercator(unittest.TestCase): """ def test_scale(self): - self.assertEqual(self.dataset.scale, ('Meter', 1.0)) + self.assertEqual(self.dataset.scale, ('metre', 1.0)) def test_xy_extent(self): xy_extent = self.dataset.xy_extent @@ -231,13 +231,17 @@ class TestWriter(unittest.TestCase): DATUM["Moon_2000", SPHEROID["Moon_2000_IAU_IAG",1737400,0]], PRIMEM["Reference_Meridian",0], - UNIT["Degree",0.017453292519943295]], - PROJECTION["Mercator_2SP"], + UNIT["degree",0.0174532925199433, + AUTHORITY["EPSG","9122"]]], + PROJECTION["Mercator_1SP"], PARAMETER["central_meridian",180], + PARAMETER["scale_factor",1], PARAMETER["false_easting",0], PARAMETER["false_northing",0], - PARAMETER["standard_parallel_1",0], - UNIT["Meter",1]]""" + UNIT["metre",1, + AUTHORITY["EPSG","9001"]], + AXIS["Easting",EAST], + AXIS["Northing",NORTH]]""" dataset = io_gdal.GeoDataset('test.tif') test_srs = dataset.spatial_reference.__str__() self.assertEqual(test_srs.split(), expected_srs.split()) diff --git a/plio/io/tests/test_metadata.py b/plio/io/tests/test_metadata.py index 6eef483..08d41a6 100644 --- a/plio/io/tests/test_metadata.py +++ b/plio/io/tests/test_metadata.py @@ -1,7 +1,7 @@ import pytest from plio.io import extract_metadata as em -from plio.io import gdal +from plio.io import gdal @pytest.fixture def wkt_moon(): @@ -52,7 +52,7 @@ def test_export_to_proj4(srs_mars): Check that proj4 is not supporting Moon2000_Mercator """ proj4 = srs_mars.ExportToProj4() - for element in '+proj=merc +lon_0=180 +lat_ts=0 +x_0=0 +y_0=0 +a=1737400 +b=1737400 +units=m +no_defs'.split(): + for element in '+proj=merc +lon_0=180 +lat_ts=0 +x_0=0 +y_0=0 +R=1737400 +units=m +no_defs'.split(): assert element in proj4 @pytest.mark.skipif(gdal is None, reason="GDAL not installed") -- GitLab