Skip to content

Commit ebbbf61

Browse files
bhawkinsGitHub Enterprise
authored andcommitted
Ephemeris readers (#732)
* Add NISAR orbit and attitude product readers. * Add docstring, type annotations, and epoch option. * Add orbit reader test case. * Improve attitude docs, change assert to exception * Add attitude reader test * Fix typo * Give better error for malformed orbit data. * Eliminate blank lines between parameters in docs. * Clarify default band in docs * Prefer generator to list. * Start compendium of test data origin stories. * Make attitude more robust to bad input data.
1 parent f4b20e4 commit ebbbf61

File tree

10 files changed

+546
-7
lines changed

10 files changed

+546
-7
lines changed

python/packages/pybind_nisar/products/readers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@
44
from .SLC import SLC
55
from . import Raw
66
from . import antenna
7+
from .attitude import load_attitude_from_xml
8+
from .orbit import load_orbit_from_xml
79

810
# end of file
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import xml.etree.ElementTree as ET
2+
from isce3.core import DateTime, Quaternion, Attitude
3+
4+
5+
def _require(node: ET.Element, pattern: str) -> ET.Element:
6+
"""Like Element.find but error if no match is found.
7+
"""
8+
x = node.find(pattern)
9+
if x is None:
10+
raise IOError(f"Could not find XML element '{pattern}'")
11+
return x
12+
13+
14+
def load_attitude_from_xml(f, epoch: DateTime = None, band="L") -> Attitude:
15+
"""Load attitude from XML file.
16+
17+
Parameters
18+
----------
19+
f : str, Path, or file
20+
File name or object containing XML data.
21+
epoch : isce3.core.DateTime, optional
22+
Desired reference epoch, defaults to time of first state vector.
23+
band : {'L', 'S'}, optional
24+
Which frequency band to read, default to 'L'.
25+
26+
Returns
27+
-------
28+
attitude : isce3.core.Attitude
29+
Attitude object.
30+
31+
Notes
32+
-----
33+
File format is described in NISAR Radar Pointing Product SIS, JPL D-102264.
34+
It contains information that may not be parsed or represented in the
35+
output object.
36+
"""
37+
if band not in ("L", "S"):
38+
raise ValueError(f"Expected band in {{'L', 'S'}} got {band}")
39+
root = ET.parse(f).getroot() # radarPointing
40+
svl = _require(root, f"{band}SAR/radarPointingStateVectorList")
41+
n = int(svl.attrib["count"])
42+
if n <= 0:
43+
raise ValueError(f"Need multiple quaternions, got count={n}")
44+
datetimes, qs = [], []
45+
for node in svl.findall("radarPointingStateVector"):
46+
t = DateTime(_require(node, "utc").text)
47+
q = (float(_require(node, name).text)
48+
for name in ("q0", "q1", "q2", "q3"))
49+
datetimes.append(t)
50+
qs.append(Quaternion(*q))
51+
if len(datetimes) != n:
52+
raise IOError(f"Expected {n} attitude entries, got {len(datetimes)}")
53+
if epoch is None:
54+
epoch = datetimes[0]
55+
# NOTE Assume times are sorted and let Attitude ctor throw error if not.
56+
times = [(t - epoch).total_seconds() for t in datetimes]
57+
return Attitude(times, qs, epoch)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import xml.etree.ElementTree as ET
2+
from isce3.core import DateTime, StateVector, Orbit
3+
4+
5+
def load_orbit_from_xml(f, epoch: DateTime = None) -> Orbit:
6+
"""Load orbit from XML file.
7+
8+
Parameters
9+
----------
10+
f : str, Path, or file
11+
File name or object containing XML data.
12+
epoch : isce3.core.DateTime, optional
13+
Desired reference epoch, defaults to time of first state vector.
14+
15+
Returns
16+
-------
17+
orbit : isce3.core.Orbit
18+
Orbit object
19+
20+
Notes
21+
-----
22+
File format is described in NISAR Orbit Ephemeris Product SIS, JPL D-102253.
23+
It contains information such as covariance estimates and maneuvers that
24+
are not parsed or represented in the output object.
25+
"""
26+
root = ET.parse(f).getroot()
27+
svl = root.find("orbitStateVectorList")
28+
if svl is None:
29+
raise IOError("Could not parse orbit XML file.")
30+
n = int(svl.attrib["count"])
31+
states = []
32+
for node in svl.findall("orbitStateVector"):
33+
t = DateTime(node.find("utc").text)
34+
x = [float(node.find(name).text) for name in "xyz"]
35+
v = [float(node.find(name).text) for name in ("vx", "vy", "vz")]
36+
states.append(StateVector(t, x, v))
37+
if len(states) != n:
38+
raise IOError(f"Expected {n} orbit state vectors, got {len(states)}")
39+
if epoch is None:
40+
epoch = states[0].datetime
41+
return Orbit(states, epoch)

tests/data/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Test Data
2+
3+
This README gives brief descriptions on the generation of various test data
4+
found in this directory.
5+
6+
## Winnipeg
7+
8+
- The original product from which the test data are generated: winnip_31604_12061_004_120717_L090_CX_129_05.h5
9+
This is a UAVSAR NISAR simulated product. Check science/LSAR/identification/productVersion to get the product version
10+
As for NISAR data/ Simulated NISAR data have to be intended as zero-doppler
11+
12+
- All winnipeg-related "reference" products in this folder (e.g. warped_winnipeg.slc) have been generated using ISCE2 v2.3.
13+
The ISCE2 processing uses "winnip_31604_12061_004_120717_L090_CX_129_05.h5" as reference and secondary SLC to create the golden/reference dataset.
14+
15+
## Attitude
16+
17+
Attitude sample data in NISAR format (see JPL D-102264) was provided by the
18+
G&C team (Dave Bates, et al) during NASA Internal Thread Test 04 (NITT-04)
19+
activities in late 2020/early 2021. The hash of the original file delivered
20+
by email is
21+
22+
```
23+
$ md5sum NISAR_ANC_L_PR_FRP_20210114T023357_20200107T200000_20200109T040000.xml
24+
2101bddf088d3b4e8e0e3051931f8284 NISAR_ANC_L_PR_FRP_20210114T023357_20200107T200000_20200109T040000.xml
25+
```
26+
27+
To reduce the size of the test data, this file was trimmed to the first ten
28+
records to produce `attitude.xml`.
29+
30+
## Orbit
31+
32+
Orbit sample data in NISAR format (see JPL D-102253) provided by Paul Ries
33+
via email on 2020-07-06. It was generated from Jason2 data. The hash of the
34+
original file is
35+
36+
```
37+
$ md5sum -b smoothFinal.xml.gz
38+
f415fc38e1feff0bb1453782be3d2b5f *smoothFinal.xml.gz
39+
```
40+
41+
This file was uncompressed and trimmed to the first ten state vectors in
42+
order to reduce the size of the `orbit.xml` file stored here.

tests/data/attitude.xml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?xml version='1.0' encoding='utf-8'?>
2+
<radarPointing>
3+
<productInformation>
4+
<productName>NISAR_ANC_L_PR_FRP_20210114T023357_20200107T200000_20200109T040000</productName>
5+
<mission>NISAR</mission>
6+
<validityStartDateTime>2020-01-07T20:00:00</validityStartDateTime>
7+
<validityEndDateTime>2020-01-09T04:00:00</validityEndDateTime>
8+
<fileClass>Test</fileClass>
9+
<fileType>FRP</fileType>
10+
<creator>NASA JPL</creator>
11+
<creationDateTime>2021-01-14T02:33:57.547520</creationDateTime>
12+
</productInformation>
13+
<LSAR>
14+
<radarPointingStateVectorList count="10">
15+
<radarPointingStateVector index="1">
16+
<utc>2020-01-07T20:00:00</utc>
17+
<roll>-0.6457081417495419</roll>
18+
<pitch>0.001398760112229801</pitch>
19+
<yaw>1.5697422973092137</yaw>
20+
<q0>0.45998636868613685</q0>
21+
<q1>0.8484777808560364</q1>
22+
<q2>0.2604469244480131</q2>
23+
<q3>-0.02579526239696106</q3>
24+
<quality>PREDICT</quality>
25+
</radarPointingStateVector>
26+
<radarPointingStateVector index="2">
27+
<utc>2020-01-07T20:00:01</utc>
28+
<roll>-0.6457082357320937</roll>
29+
<pitch>0.0013966873285031834</pitch>
30+
<yaw>1.56974385895192</yaw>
31+
<q0>0.46031535338895463</q0>
32+
<q1>0.8483862072201784</q1>
33+
<q2>0.26013727118041524</q2>
34+
<q3>-0.026061829862272068</q3>
35+
<quality>PREDICT</quality>
36+
</radarPointingStateVector>
37+
<radarPointingStateVector index="3">
38+
<utc>2020-01-07T20:00:02</utc>
39+
<roll>-0.6457083225750708</roll>
40+
<pitch>0.0013946167258813244</pitch>
41+
<yaw>1.5697454189676887</yaw>
42+
<q0>0.46064424123610703</q0>
43+
<q1>0.848294362825532</q1>
44+
<q2>0.2598275841825737</q2>
45+
<q3>-0.026328378459064716</q3>
46+
<quality>PREDICT</quality>
47+
</radarPointingStateVector>
48+
<radarPointingStateVector index="4">
49+
<utc>2020-01-07T20:00:03</utc>
50+
<roll>-0.6457084155535049</roll>
51+
<pitch>0.001392541161273757</pitch>
52+
<yaw>1.5697469827093893</yaw>
53+
<q0>0.46097303216847363</q0>
54+
<q1>0.8482022476809679</q1>
55+
<q2>0.25951786356037376</q2>
56+
<q3>-0.026594908073106494</q3>
57+
<quality>PREDICT</quality>
58+
</radarPointingStateVector>
59+
<radarPointingStateVector index="5">
60+
<utc>2020-01-07T20:00:04</utc>
61+
<roll>-0.6457084957779009</roll>
62+
<pitch>0.0013904611131749993</pitch>
63+
<yaw>1.569748549857765</yaw>
64+
<q0>0.46130172612701786</q0>
65+
<q1>0.8481098617954342</q1>
66+
<q2>0.2592081094196022</q2>
67+
<q3>-0.026861418590215513</q3>
68+
<quality>PREDICT</quality>
69+
</radarPointingStateVector>
70+
<radarPointingStateVector index="6">
71+
<utc>2020-01-07T20:00:05</utc>
72+
<roll>-0.6457085924112703</roll>
73+
<pitch>0.0013883550254227123</pitch>
74+
<yaw>1.5697501365908249</yaw>
75+
<q0>0.4616303230527267</q0>
76+
<q1>0.8480172051779915</q1>
77+
<q2>0.25889832186595574</q2>
78+
<q3>-0.027127909896180857</q3>
79+
<quality>PREDICT</quality>
80+
</radarPointingStateVector>
81+
<radarPointingStateVector index="7">
82+
<utc>2020-01-07T20:00:06</utc>
83+
<roll>-0.6457086856447454</roll>
84+
<pitch>0.0013862513498051777</pitch>
85+
<yaw>1.5697517215146415</yaw>
86+
<q0>0.46195882288646645</q0>
87+
<q1>0.847924277837868</q1>
88+
<q2>0.25858850100511505</q2>
89+
<q3>-0.02739438187672999</q3>
90+
<quality>PREDICT</quality>
91+
</radarPointingStateVector>
92+
<radarPointingStateVector index="8">
93+
<utc>2020-01-07T20:00:07</utc>
94+
<roll>-0.6457087747620623</roll>
95+
<pitch>0.0013841327533615703</pitch>
96+
<yaw>1.5697533176903113</yaw>
97+
<q0>0.46228722556915264</q0>
98+
<q1>0.847831079784391</q1>
99+
<q2>0.25827864694266167</q2>
100+
<q3>-0.027660834417580382</q3>
101+
<quality>PREDICT</quality>
102+
</radarPointingStateVector>
103+
<radarPointingStateVector index="9">
104+
<utc>2020-01-07T20:00:08</utc>
105+
<roll>-0.6457088614413605</roll>
106+
<pitch>0.0013820105542763858</pitch>
107+
<yaw>1.569754916586475</yaw>
108+
<q0>0.4626155310417434</q0>
109+
<q1>0.8477376110269874</q1>
110+
<q2>0.25796875978409056</q2>
111+
<q3>-0.027927267404464606</q3>
112+
<quality>PREDICT</quality>
113+
</radarPointingStateVector>
114+
<radarPointingStateVector index="10">
115+
<utc>2020-01-07T20:00:09</utc>
116+
<roll>-0.6457089578199849</roll>
117+
<pitch>0.0013798794032847958</pitch>
118+
<yaw>1.5697565222072347</yaw>
119+
<q0>0.46294373924510057</q0>
120+
<q1>0.8476438715752446</q1>
121+
<q2>0.25765883963485936</q2>
122+
<q3>-0.028193680723036463</q3>
123+
<quality>PREDICT</quality>
124+
</radarPointingStateVector>
125+
</radarPointingStateVectorList>
126+
</LSAR>
127+
</radarPointing>

0 commit comments

Comments
 (0)