diff --git a/python/extensions/pybind_isce3/core/Orbit.cpp b/python/extensions/pybind_isce3/core/Orbit.cpp index f55f9f115..addd25658 100644 --- a/python/extensions/pybind_isce3/core/Orbit.cpp +++ b/python/extensions/pybind_isce3/core/Orbit.cpp @@ -118,5 +118,24 @@ void addbinding(py::class_ & pyOrbit) isce3.core.Orbit Orbit object with data containing start & end times)", py::arg("start"), py::arg("end"), py::arg("npad") = 0) + .def("set_interp_method", [](Orbit& self, const std::string& method) { + if (method == "Hermite") { + self.interpMethod(OrbitInterpMethod::Hermite); + } else if (method == "Legendre") { + self.interpMethod(OrbitInterpMethod::Legendre); + } else { + throw std::invalid_argument("unexpected orbit interpolation method '" + method + "'"); + } + }, R"( + Set interpolation method. + + Parameters + ---------- + method : {'Hermite', 'Legendre'} + The method for interpolating orbit state vectors (cubic + Hermite spline interpolation or eighth-order Legendre + polynomial interpolation). + )", + py::arg("method")) ; } diff --git a/python/packages/isce3/core/__init__.py b/python/packages/isce3/core/__init__.py index 48d4ab1f1..4a894f33d 100644 --- a/python/packages/isce3/core/__init__.py +++ b/python/packages/isce3/core/__init__.py @@ -6,3 +6,4 @@ from .poly2d import fit_bivariate_polynomial from . import rdr_geo_block_generator from .block_param_generator import BlockParam +from .serialization import load_orbit_from_h5_group diff --git a/python/packages/isce3/core/serialization.py b/python/packages/isce3/core/serialization.py new file mode 100644 index 000000000..ed74dd921 --- /dev/null +++ b/python/packages/isce3/core/serialization.py @@ -0,0 +1,69 @@ +from __future__ import annotations +from isce3.core import DateTime, Orbit, StateVector, TimeDelta +import h5py + +def parse_iso_date(iso_date: str) -> DateTime: + """ + Parse ISO date string to DateTime + + This is a direct translation from C++ code in isce3, + and could probably be made more robust/intuitive. + """ + utc_ref = "" + pos_iso = iso_date.rfind('-') + if pos_iso != -1: + utc_ref = iso_date[pos_iso - 7:] + # remove any trailing whitespace + utc_ref = utc_ref.rstrip(' ') + return DateTime(utc_ref) + +def load_orbit_from_h5_group(group: h5py.Group) -> Orbit: + """ + Load orbit data from a group in an HDF5 file. + + The organization of orbit data in the group is assumed to conform to + NISAR product specifications. + + Parameters + ---------- + group : h5py.Group + The HDF5 group containing the orbit metadata. + + Returns + ------- + orbit : Orbit + The orbit data. + """ + time = group['time'] + pos = group['position'] + vel = group['velocity'] + + if time.ndim != 1 or pos.ndim != 2 or vel.ndim != 2: + raise ValueError("unexpected orbit state vector dims") + + if pos.shape[1] != 3 or vel.shape[1] != 3: + raise ValueError("unexpected orbit position/velocity vector size") + + size = time.shape[0]; + if pos.shape[0] != size or vel.shape[0] != size: + raise ValueError("mismatched orbit state vector component sizes") + + # get reference epoch + unit_attr = time.attrs['units'] + # result may be str or bytes, convert to str if needed + if type(unit_attr) is not str: + unit_attr = unit_attr.decode('utf-8') + epoch = parse_iso_date(unit_attr) + + # convert to state vectors + statevecs = [StateVector(epoch + TimeDelta(time[i]), pos[i], vel[i]) + for i in range(size)] + + # construct Orbit object + result = Orbit(statevecs, epoch) + + # set interpolation method, if specified + if 'interpMethod' in group.keys(): + result.set_interp_method(group['interpMethod'][()]) + + return result diff --git a/python/packages/nisar/products/readers/Base/Base.py b/python/packages/nisar/products/readers/Base/Base.py index 0b7382960..76c6f395c 100644 --- a/python/packages/nisar/products/readers/Base/Base.py +++ b/python/packages/nisar/products/readers/Base/Base.py @@ -140,7 +140,7 @@ def getOrbit(self): ''' with h5py.File(self.filename, 'r', libver='latest', swmr=True) as fid: orbitPath = os.path.join(self.MetadataPath, 'orbit') - return isce3.core.Orbit.load_from_h5(fid[orbitPath]) + return isce3.core.load_orbit_from_h5_group(fid[orbitPath]) @pyre.export def getAttitude(self): diff --git a/python/packages/nisar/products/readers/Raw/Raw.py b/python/packages/nisar/products/readers/Raw/Raw.py index 2dbe9ec58..9bcc904f3 100644 --- a/python/packages/nisar/products/readers/Raw/Raw.py +++ b/python/packages/nisar/products/readers/Raw/Raw.py @@ -162,7 +162,7 @@ def TelemetryPath(self): def getOrbit(self): path = f"{self.TelemetryPath}/orbit" with h5py.File(self.filename, 'r', libver='latest', swmr=True) as f: - orbit = isce3.core.Orbit.load_from_h5(f[path]) + orbit = isce3.core.load_orbit_from_h5_group(f[path]) return orbit def getAttitude(self): diff --git a/tests/python/extensions/pybind/core/orbit.py b/tests/python/extensions/pybind/core/orbit.py index ce0fce844..2a671d045 100644 --- a/tests/python/extensions/pybind/core/orbit.py +++ b/tests/python/extensions/pybind/core/orbit.py @@ -4,12 +4,12 @@ import numpy.testing as npt def load_h5(): - from isce3.ext.isce3.core import Orbit + from isce3.core import load_orbit_from_h5_group from iscetest import data from os import path import h5py f = h5py.File(path.join(data, "envisat.h5"), 'r') - return Orbit.load_from_h5(f["/science/LSAR/SLC/metadata/orbit"]) + return load_orbit_from_h5_group(f["/science/LSAR/SLC/metadata/orbit"]) o = load_h5(); diff --git a/tests/python/extensions/pybind/focus/backproject.py b/tests/python/extensions/pybind/focus/backproject.py index f8ec97f00..853184338 100644 --- a/tests/python/extensions/pybind/focus/backproject.py +++ b/tests/python/extensions/pybind/focus/backproject.py @@ -4,6 +4,7 @@ import numpy as np import numpy.testing as npt import isce3.ext.isce3 as isce +from isce3.core import load_orbit_from_h5_group from iscetest import data as test_data_dir from pathlib import Path import json @@ -20,7 +21,7 @@ def load_h5(filename): signal_data = f["data"][()] # load orbit - orbit = isce.core.Orbit.load_from_h5(f["orbit"]) + orbit = load_orbit_from_h5_group(f["orbit"]) # load Doppler doppler = isce.core.LUT2d.load_from_h5(f["doppler"], "doppler") diff --git a/tests/python/extensions/pybind/geometry/bbox.py b/tests/python/extensions/pybind/geometry/bbox.py index 89a46fefd..6c443b80f 100644 --- a/tests/python/extensions/pybind/geometry/bbox.py +++ b/tests/python/extensions/pybind/geometry/bbox.py @@ -1,6 +1,7 @@ import iscetest import pathlib import isce3.ext.isce3 as isce +from isce3.core import load_orbit_from_h5_group from nisar.products.readers import SLC @@ -11,7 +12,7 @@ def load_orbit(): import h5py with h5py.File(path_slc, "r") as h5: g = h5["/science/LSAR/SLC/metadata/orbit"] - orbit = isce.core.Orbit.load_from_h5(g) + orbit = load_orbit_from_h5_group(g) return orbit