Skip to content

Commit 8398727

Browse files
authored
Merge pull request #490 from dbekaert/dev
Release 0.4.2
2 parents 48c69bd + 755d253 commit 8398727

File tree

17 files changed

+535
-253
lines changed

17 files changed

+535
-253
lines changed

.github/workflows/deploy-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
with:
1515
fetch-depth: 0
1616

17-
- uses: mamba-org/provision-with-micromamba@v14
17+
- uses: mamba-org/provision-with-micromamba@v15
1818
with:
1919
extra-specs: |
2020
python=3.10

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ test/data
2020
*.cpp
2121
test/*/geom/*.dem
2222
test/*/geom/*.nc
23+
test/test_geom/*.nc
24+
test/scenario_2/GMAO_Delay_*.csv
25+
test/GUNW/
2326
.eggs/
2427
dist/
2528
tools/RAiDER.egg-info/

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [PEP 440](https://www.python.org/dev/peps/pep-0440/)
77
and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
+ Add assert statement to raise error if the delay cube for each SAR date in a GUNW IFG is not written
10+
11+
## [0.4.2]
12+
13+
### New/Updated Features
14+
+ `calcDelaysGUNW` allows processing with any supported weather model as listed in [`RAiDER.models.allowed.ALLOWED_MODELS`](https://github.com/dbekaert/RAiDER/blob/dev/tools/RAiDER/models/allowed.py).
15+
+ Removed NCMR removed from supported model list till re-tested
16+
+ `credentials` looks for weather model API credentials RC_file hidden file, and creates it if it does not exists
17+
+ Isolate ISCE3 imports to only those functions that need it.
18+
+ Small bugfixes and updates to docstrings
919

1020
## [0.4.1]
1121

test/conftest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import pytest
2+
3+
4+
def pytest_addoption(parser):
5+
parser.addoption(
6+
"--skip-isce3", action="store_true", default=False, help="skip tests which require ISCE3"
7+
)
8+
9+
10+
def pytest_configure(config):
11+
config.addinivalue_line("markers", "isce3: mark test as requiring ISCE3 to run")
12+
13+
14+
def pytest_collection_modifyitems(config, items):
15+
if config.getoption("--skip-isce3"):
16+
skip_isce3 = pytest.mark.skip(reason="--skip-isce3 option given")
17+
for item in items:
18+
if "isce3" in item.keywords:
19+
item.add_marker(skip_isce3)

test/test_GUNW.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
import numpy as np
77
import rasterio as rio
88
import xarray as xr
9+
import pytest
910

1011
from test import TEST_DIR
1112

1213
WM = 'GMAO'
1314

15+
@pytest.mark.isce3
1416
def test_GUNW():
1517
## eventually to be implemented
1618
# home = os.path.expanduser('~')

test/test_credentials.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import os
2+
from platform import system
3+
from RAiDER.models import credentials
4+
5+
# Test checking/creating ECMWF_RC CAPI file
6+
def test_ecmwfApi_createFile():
7+
import ecmwfapi
8+
9+
#Check extension for hidden files
10+
hidden_ext = '_' if system()=="Windows" else '.'
11+
12+
# Test creation of ~/.ecmwfapirc file
13+
ecmwf_file = os.path.expanduser('./') + hidden_ext + credentials.API_FILENAME['ERAI']
14+
credentials.check_api('ERAI', 'dummy', 'dummy', './', update_flag=True)
15+
assert os.path.exists(ecmwf_file) == True,f'{ecmwf_file} does not exist'
16+
17+
# Get existing ECMWF_API_RC env if exist
18+
default_ecmwf_file = os.getenv("ECMWF_API_RC_FILE")
19+
if default_ecmwf_file is None:
20+
default_ecmwf_file = ecmwfapi.api.DEFAULT_RCFILE_PATH
21+
22+
#Set it to current dir to avoid overwriting ~/.ecmwfapirc file
23+
os.environ["ECMWF_API_RC_FILE"] = ecmwf_file
24+
key, url, uid = ecmwfapi.api.get_apikey_values()
25+
26+
# Return to default_ecmwf_file and remove local API file
27+
os.environ["ECMWF_API_RC_FILE"] = default_ecmwf_file
28+
os.remove(ecmwf_file)
29+
30+
#Check if API is written correctly
31+
assert uid == 'dummy', f'{ecmwf_file}: UID was not written correctly'
32+
assert key == 'dummy', f'{ecmwf_file}: KEY was not written correctly'
33+
34+
35+
# Test checking/creating Copernicus Climate Data Store
36+
# CDS_RC CAPI file
37+
def test_cdsApi_createFile():
38+
import cdsapi
39+
40+
#Check extension for hidden files
41+
hidden_ext = '_' if system()=="Windows" else '.'
42+
43+
# Test creation of .cdsapirc file in current dir
44+
cds_file = os.path.expanduser('./') + hidden_ext + credentials.API_FILENAME['ERA5']
45+
credentials.check_api('ERA5', 'dummy', 'dummy', './', update_flag=True)
46+
assert os.path.exists(cds_file) == True,f'{cds_file} does not exist'
47+
48+
# Check the content
49+
cds_credentials = cdsapi.api.read_config(cds_file)
50+
uid, key = cds_credentials['key'].split(':')
51+
52+
# Remove local API file
53+
os.remove(cds_file)
54+
55+
assert uid == 'dummy', f'{cds_file}: UID was not written correctly'
56+
assert key == 'dummy', f'{cds_file}: KEY was not written correctly'
57+
58+
# Test checking/creating EARTHDATA_RC API file
59+
def test_netrcApi_createFile():
60+
import netrc
61+
62+
#Check extension for hidden files
63+
hidden_ext = '_' if system()=="Windows" else '.'
64+
65+
# Test creation of ~/.cdsapirc file
66+
netrc_file = os.path.expanduser('./') + hidden_ext + credentials.API_FILENAME['GMAO']
67+
credentials.check_api('GMAO', 'dummy', 'dummy', './', update_flag=True)
68+
assert os.path.exists(netrc_file) == True,f'{netrc_file} does not exist'
69+
70+
# Check the content
71+
host = 'urs.earthdata.nasa.gov'
72+
netrc_credentials = netrc.netrc(netrc_file)
73+
uid, _, key = netrc_credentials.authenticators(host)
74+
75+
# Remove local API file
76+
os.remove(netrc_file)
77+
78+
assert uid == 'dummy', f'{netrc_file}: UID was not written correctly'
79+
assert key == 'dummy', f'{netrc_file}: KEY was not written correctly'

test/test_raytracing.py

Lines changed: 165 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,180 @@
11
import datetime
22
import pytest
3+
34
import numpy as np
45

5-
from RAiDER.losreader import Raytracing
6+
from pyproj import CRS
7+
from scipy.interpolate import RegularGridInterpolator as rgi
8+
9+
from RAiDER.delay import _build_cube_ray
10+
from RAiDER.models.weatherModel import (
11+
WeatherModel,
12+
)
13+
14+
_LON0 = 0
15+
_LAT0 = 0
16+
_OMEGA = 0.1 / (180/np.pi)
17+
18+
class MockWeatherModel(WeatherModel):
19+
"""Implement abstract methods for testing."""
20+
21+
def __init__(self):
22+
super().__init__()
23+
24+
self._k1 = 1
25+
self._k2 = 1
26+
self._k3 = 1
27+
28+
self._Name = "MOCK"
29+
self._valid_range = (datetime.datetime(1970, 1, 1), "Present")
30+
self._lag_time = datetime.timedelta(days=15)
31+
32+
def _fetch(self, ll_bounds, time, out):
33+
pass
34+
35+
def load_weather(self, *args, **kwargs):
36+
_N_Z = 32
37+
self._ys = np.arange(-2,3) + _LAT0
38+
self._xs = np.arange(-3,4) + _LON0
39+
self._zs = np.linspace(0, 1e5, _N_Z)
40+
self._t = np.ones((len(self._ys), len(self._xs), _N_Z))
41+
self._e = self._t.copy()
42+
self._e[:,3:,:] = 2
43+
44+
_p = np.arange(31, -1, -1)
45+
self._p = np.broadcast_to(_p, self._t.shape)
46+
47+
self._true_hydro_refr = np.broadcast_to(_p, (self._t.shape))
48+
self._true_wet_ztd = 1e-6 * 2 * np.broadcast_to(np.flip(self._zs), (self._t.shape))
49+
self._true_wet_ztd[:,3:] = 2 * self._true_wet_ztd[:,3:]
50+
51+
self._true_hydro_ztd = np.zeros(self._t.shape)
52+
for layer in range(len(self._zs)):
53+
self._true_hydro_ztd[:,:,layer] = 1e-6 * 0.5 * (self._zs[-1] - self._zs[layer]) * _p[layer]
54+
55+
self._true_wet_refr = 2 * np.ones(self._t.shape)
56+
self._true_wet_refr[:,3:] = 4
57+
58+
def interpWet(self):
59+
_ifWet = rgi((self._ys, self._xs, self._zs), self._true_wet_refr)
60+
return _ifWet
61+
def interpHydro(self):
62+
_ifHydro = rgi((self._ys, self._xs, self._zs), self._true_hydro_refr)
63+
return _ifHydro
64+
65+
66+
@pytest.fixture
67+
def model():
68+
return MockWeatherModel()
69+
70+
71+
@pytest.fixture
72+
def setup_fake_raytracing():
73+
'''This sets up a fake orbit for the weather model'''
74+
75+
import isce3.ext.isce3 as isce
76+
from isce3.core import DateTime, TimeDelta
77+
78+
lat0 = _LAT0 # degrees
79+
lon0 = _LON0
80+
hsat = 700000.
81+
omega = _OMEGA # degrees
82+
Nvec = 30
83+
84+
t0 = DateTime("2017-02-12T01:12:30.0")
85+
86+
elp = isce.core.Ellipsoid(6378137.,.0066943799901)
87+
look = isce.core.LookSide.Left
88+
89+
sat_hgt = elp.a + hsat
90+
sat_lat = np.sin(np.radians(lat0))
91+
clat = np.cos(np.radians(lat0))
92+
93+
svs = []
94+
for k in range(Nvec):
95+
dt = TimeDelta(100 * k)
96+
lon = lon0 + omega * dt.total_seconds()
97+
98+
pos = []
99+
pos.append(sat_hgt * clat * np.cos(np.radians(lon)))
100+
pos.append(sat_hgt * clat * np.sin(np.radians(lon)))
101+
pos.append(sat_hgt * sat_lat)
102+
103+
vel = []
104+
vel.append(-omega * pos[1])
105+
vel.append(omega * pos[0])
106+
vel.append(0.)
107+
108+
epoch = t0 + dt
109+
110+
svs.append(
111+
isce.core.StateVector(epoch,pos, vel)
112+
)
113+
114+
orb = isce.core.Orbit(svs)
115+
116+
return orb, look, elp, sat_hgt
6117

118+
def solve(R, hsat, ellipsoid, side='left'):
119+
# temp = 1.0 + hsat/ellipsoid.a
120+
# temp1 = R/ellipsoid.a
121+
# temp2 = R/(ellipsoid.a + hsat)
122+
t2 = (np.square(hsat) + np.square(R)) - np.square(ellipsoid.a)
123+
# cosang = 0.5 * (temp + (1.0/temp) - temp1 * temp2)
124+
cosang = 0.5 * t2 / (R * hsat)
125+
angdiff = np.arccos(cosang)
126+
if side=='left':
127+
x = _LAT0 + angdiff
128+
else:
129+
x = _LAT0 - angdiff
130+
return x
7131

8-
def test_Raytracing():
9-
lats = np.array([-90, 0, 0, 90])
10-
lons = np.array([-90, 0, 90, 180])
11-
hgts = np.array([-10, 0, 10, 1000])
12132

13-
unit_vecs = np.array([[0,0,-1], [1,0,0], [0,1,0], [0,0,1]])
133+
@pytest.mark.isce3
134+
def test_llhs(setup_fake_raytracing, model):
135+
orb, look_dir, elp, sat_hgt = setup_fake_raytracing
136+
llhs = []
137+
for k in range(20):
138+
tinp = 5 + k * 2
139+
rng = 800000 + 1000 * k
140+
expLon = _LON0 + _OMEGA * tinp
141+
geocentricLat = solve(rng, sat_hgt, elp)
14142

15-
z = Raytracing()
16-
with pytest.raises(RuntimeError):
17-
z.setPoints(lats=None)
143+
xyz = [
144+
elp.a * np.cos(geocentricLat) * np.cos(expLon),
145+
elp.a * np.cos(geocentricLat) * np.sin(expLon),
146+
elp.a * np.sin(geocentricLat)
147+
]
148+
llh = elp.xyz_to_lon_lat(xyz)
149+
llhs.append(llh)
18150

19-
z.setPoints(lats=lats, lons=lons, heights = hgts)
20-
assert z._lats.shape == (4,)
21-
assert z._lats.shape == z._lons.shape
22-
assert np.allclose(z._heights, hgts)
151+
assert len(llhs) == 20
23152

153+
@pytest.mark.skip
154+
@pytest.mark.isce3
155+
def test_build_cube_ray(setup_fake_raytracing, model):
156+
import isce3.ext.isce3 as isce
157+
from RAiDER.losreader import state_to_los
24158

25-
def test_toa():
26-
lats = np.array([0, 0, 0, 0])
27-
lons = np.array([0, 180, 90, -90])
28-
hgts = np.array([0, 0, 0, 0])
159+
orb, look_dir, elp, _ = setup_fake_raytracing
160+
m = model
161+
m.load_weather()
29162

30-
z = Raytracing()
31-
z.setPoints(lats=lats, lons=lons, heights=hgts)
163+
ys = np.arange(-1,1) + _LAT0
164+
xs = np.arange(-1,1) + _LON0
165+
zs = np.arange(0, 1e5, 1e3)
32166

33-
# Mock xyz
34-
z._xyz = np.array([[6378137.0, 0.0, 0.0],
35-
[-6378137.0, 0.0, 0.0],
36-
[0.0, 6378137.0, 0.0],
37-
[0.0, -6378137.0, 0.0]])
38-
z._look_vecs = np.array([[1, 0, 0],
39-
[-1, 0, 0],
40-
[0, 1, 0],
41-
[0, -1, 0]])
42-
toppts = np.array([[6388137.0, 0.0, 0.0],
43-
[-6388137.0, 0.0, 0.0],
44-
[0.0, 6388137.0, 0.0],
45-
[0.0, -6388137.0, 0.0]])
167+
_Y, _X, _Z = np.meshgrid(ys, xs, zs)
46168

47-
topxyz = z.getIntersectionWithHeight(10000.)
169+
out_true = np.zeros(_Y.shape)
170+
t0 = orb.start_time
171+
tm1 = orb.end_time
172+
ts = np.arange(t0, tm1 + orb.time.spacing, orb.time.spacing)
173+
tlist = [orb.reference_epoch + isce.core.TimeDelta(dt) for dt in ts]
174+
ts = np.broadcast_to(tlist, (1, len(tlist))).T
175+
svs = np.hstack([ts, orb.position, orb.velocity])
48176

49-
assert np.allclose(topxyz, toppts)
177+
#TODO: Check that the look vectors are not nans
178+
lv, xyz = state_to_los(svs, np.stack([_Y.ravel(), _X.ravel(), _Z.ravel()], axis=-1),out="ecef")
179+
out = _build_cube_ray(xs, ys, zs, orb, look_dir, CRS(4326), CRS(4326), [m.interpWet(), m.interpHydro()], elp=elp)
180+
assert out.shape == out_true.shape

test/test_scenario_3.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212

1313

14+
@pytest.mark.isce3
1415
def test_scenario_3():
1516
SCENARIO_DIR = os.path.join(TEST_DIR, "scenario_3")
1617

0 commit comments

Comments
 (0)