Skip to content

Commit

Permalink
Merge pull request #500 from bbuzz31/ReadGUNW_slcid
Browse files Browse the repository at this point in the history
Read SLCS Granules from GUNW
  • Loading branch information
bbuzz31 authored Mar 14, 2023
2 parents b2d98be + 28b33da commit 2f28cb5
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 133 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+ Add assert statement to raise error if the delay cube for each SAR date in a GUNW IFG is not written
+ Verify some constants / equations and remove the comments questioning them
+ Relocate the time resolution of wmodels to one spot
+ Skip test_scenario_3 until a new golden dataset is created

## [0.4.2]

### New/Updated Features
+ `prepFromGUNW` reads the date/time from the SLCs rather than the GUNW filename
+ `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).
+ Removed NCMR removed from supported model list till re-tested
+ `credentials` looks for weather model API credentials RC_file hidden file, and creates it if it does not exists
+ Isolate ISCE3 imports to only those functions that need it.
+ Small bugfixes and updates to docstrings
+ Only orbit file is used (even if multiple specified) to minimize errors and ensure consistency over region
+ GUNW packaging is restructed to store SLC (ref and sec) wet and tropo delays rather than the differential
+ padding made consistent throughout and default arguments reduced (manually update in test_losreader)

## [0.4.1]

Expand Down
2 changes: 1 addition & 1 deletion test/test_GUNW.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_GUNW():
## check the CRS and affine are written correctly
epsg = 4326
transform = (0.1, 0.0, -119.35, 0, -0.1, 35.05)
group = 'science/grids/corrections/external/troposphere'
group = f'science/grids/corrections/external/troposphere/{WM}/reference'
for v in 'troposphereWet troposphereHydrostatic'.split():
ds = rio.open(f'netcdf:{updated_GUNW}:{group}/{v}')
with rio.open(f'netcdf:{updated_GUNW}:{group}/{v}') as ds:
Expand Down
10 changes: 5 additions & 5 deletions test/test_losreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_read_txt_file(svs):
def test_get_sv_1(svs):
true_svs = svs
filename = os.path.join(SCENARIO_DIR, 'S1_orbit_example.EOF')
svs = get_sv(filename, true_svs[0][0])
svs = get_sv(filename, true_svs[0][0], pad=3*60)
assert [np.allclose(
[(x-y).total_seconds() for x, y in zip(svs[0], true_svs[0])],
np.zeros(len(svs[0]))
Expand All @@ -130,7 +130,7 @@ def test_get_sv_1(svs):
def test_get_sv_2(svs):
true_svs = svs
filename = os.path.join(SCENARIO_DIR, 'S1_sv_file.txt')
svs = get_sv(filename, true_svs[0][0])
svs = get_sv(filename, true_svs[0][0], pad=3*60)
assert [np.allclose(
[(x-y).total_seconds() for x, y in zip(svs[0], true_svs[0])],
np.zeros(len(svs[0]))
Expand All @@ -142,19 +142,19 @@ def test_get_sv_3(svs):
true_svs = svs
filename = os.path.join(SCENARIO_DIR, 'incorrect_file.txt')
with pytest.raises(ValueError):
get_sv(filename, true_svs[0][0])
get_sv(filename, true_svs[0][0], pad=3*60)


def test_get_sv_4(svs):
true_svs = svs
filename = os.path.join(SCENARIO_DIR, 'no_exist.txt')
with pytest.raises(FileNotFoundError):
get_sv(filename, true_svs[0][0])
get_sv(filename, true_svs[0][0], pad=3*60)


def test_cut_times(svs):
true_svs = svs
assert all(cut_times(true_svs[0], true_svs[0][0]))
assert all(cut_times(true_svs[0], true_svs[0][0], pad=3600*3))


def test_cut_times_2(svs):
Expand Down
3 changes: 2 additions & 1 deletion test/test_scenario_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@



@pytest.mark.isce3
# @pytest.mark.isce3
@pytest.mark.skip(reason='outdated golden data')
def test_scenario_3():
SCENARIO_DIR = os.path.join(TEST_DIR, "scenario_3")

Expand Down
2 changes: 1 addition & 1 deletion test/test_scenario_3_proj.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import xarray as xr


# @pytest.mark.skip
@pytest.mark.skip(reason='outdated golden data')
def test_scenario_3_proj():
SCENARIO_DIR = os.path.join(TEST_DIR, "scenario_3")
#
Expand Down
62 changes: 38 additions & 24 deletions tools/RAiDER/aria/calcGUNW.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,12 @@ def update_gunw_slc(path_gunw:str, ds_slc):
with h5py.File(path_gunw, 'a') as h5:
for k in TROPO_GROUP.split():
h5 = h5[k]
del h5[TROPO_NAMES[0]]
del h5[TROPO_NAMES[1]]
# in case GUNW has already been updated once before
try:
del h5[TROPO_NAMES[0]]
del h5[TROPO_NAMES[1]]
except KeyError:
pass

for k in 'crs'.split():
if k in h5.keys():
Expand All @@ -101,38 +105,48 @@ def update_gunw_slc(path_gunw:str, ds_slc):
ds_grp.createGroup(ds_slc.attrs['model'].upper())
ds_grp_wm = ds_grp[ds_slc.attrs['model'].upper()]

## create the new dimensions e.g., corrections/troposphere/GMAO/latitudeMeta
for dim in DIM_NAMES:
## dimension may already exist if updating
try:
ds_grp_wm.createDimension(dim, len(ds_slc.coords[dim]))
## necessary for transform
v = ds_grp_wm.createVariable(dim, np.float32, dim)
v[:] = ds_slc[dim]
v.setncatts(ds_slc[dim].attrs)
except:
pass


## create and store new data e.g., corrections/troposphere/GMAO/reference/troposphereWet
for name in TROPO_NAMES:
for rs in 'reference secondary'.split():
for rs in 'reference secondary'.split():
ds_grp_wm.createGroup(rs)
ds_grp_rs = ds_grp_wm[rs]

## create the new dimensions e.g., corrections/troposphere/GMAO/reference/latitudeMeta
for dim in DIM_NAMES:
## dimension may already exist if updating
try:
ds_grp_rs.createDimension(dim, len(ds_slc.coords[dim]))
## necessary for transform
v = ds_grp_rs.createVariable(dim, np.float32, dim)
v[:] = ds_slc[dim]
v.setncatts(ds_slc[dim].attrs)

except RuntimeError:
pass

## add the projection if it doesnt exist
try:
v_proj = ds_grp_rs.createVariable('crs', 'i')
except RuntimeError:
v_proj = ds_grp_rs['crs']
v_proj.setncatts(ds_slc["crs"].attrs)

## update the actual tropo data
for name in TROPO_NAMES:
da = ds_slc[f'{rs}_{name}']
nodata = da.encoding['_FillValue']
chunksize = da.encoding['chunksizes']

ds_grp_wm.createGroup(rs)
ds_grp_rs = ds_grp_wm[rs]
## in case updating
try:
v = ds_grp_rs.createVariable(name, np.float32, DIM_NAMES,
chunksizes=chunksize, fill_value=nodata)
except RuntimeError:
v = ds_grp_rs[name]

v = ds_grp_rs.createVariable(name, np.float32, DIM_NAMES,
chunksizes=chunksize, fill_value=nodata)
v[:] = da.data
v.setncatts(da.attrs)

## add the projection
v_proj = ds_grp_wm.createVariable('crs', 'i')
v_proj.setncatts(ds_slc["crs"].attrs)


logger.info('Updated %s group in: %s', os.path.basename(TROPO_GROUP), path_gunw)
return
Expand Down
141 changes: 91 additions & 50 deletions tools/RAiDER/aria/prepFromGUNW.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import yaml
import shapely.wkt
import RAiDER
from dataclasses import dataclass

from RAiDER.utilFcns import rio_open, writeArrayToRaster
from RAiDER.logger import logger
from RAiDER.models import credentials
Expand All @@ -22,25 +24,25 @@
DCT_POSTING = {'HRRR': 0.05, 'HRES': 0.10, 'GMAO': 0.10, 'ERA5': 0.10, 'ERA5T': 0.10}


class GUNW(object):
def __init__(self, f:str, wm:str, out_dir:str):
self.path_gunw = f
self.wmodel = wm
self.out_dir = out_dir
self.spacing_m = int(DCT_POSTING[self.wmodel] * 1e5)

@dataclass
class GUNW:
path_gunw: str
wm: str
out_dir: str

def __call__(self):
def __post_init__(self):
self.SNWE = self.get_bbox()
self.heights = np.arange(-500, 9500, 500).tolist()
self.dates = self.get_dates()
self.ref_time = self.get_time()
self.look_dir = self.get_look_dir()
# self.heights = [-500, 0]
self.dates, self.mid_time = self.get_datetimes()

self.look_dir = self.get_look_dir()
self.wavelength = self.get_wavelength()
self.name = f'{self.dates[0]}-{self.dates[1]}'
self.OrbitFiles = self.get_orbit_files()
self.name = self.make_fname()
self.OrbitFile = self.get_orbit_file()
self.spacing_m = int(DCT_POSTING[self.wm] * 1e5)

## note implemented
## not implemented
# self.spacing_m = self.calc_spacing_UTM() # probably wrong/unnecessary
# self.lat_file, self.lon_file = self.makeLatLonGrid_native()
# self.path_cube = self.make_cube() # not needed
Expand All @@ -62,17 +64,62 @@ def get_bbox(self):
return [S, N, W, E]


def get_dates(self):
""" Get the ref/sec date from the filename """
def make_fname(self):
""" Match the ref/sec filename (SLC dates may be different around edge cases) """
ref, sec = os.path.basename(self.path_gunw).split('-')[6].split('_')
return f'{ref}-{sec}'


def get_datetimes(self):
""" Get the datetimes and set the satellite for orbit """
ref_sec = self.get_slc_dt()
middates = []
for aq in ref_sec:
st, en = aq
midpt = st + (en-st)/2
middates.append(int(midpt.date().strftime('%Y%m%d')))
midtime = midpt.time().strftime('%H:%M:%S')
return middates, midtime


def get_slc_dt(self):
""" Grab the SLC start date and time from the GUNW """
group = 'science/radarMetaData/inputSLC'
lst_sten = []
for i, key in enumerate('reference secondary'.split()):
ds = xr.open_dataset(self.path_gunw, group=f'{group}/{key}')
slcs = ds['L1InputGranules']
nslcs = slcs.count().item()
# single slc
if nslcs == 1:
slc = slcs.item()
assert slc, f'Missing {key} SLC metadata in GUNW: {self.f}'
st = datetime.strptime(slc.split('_')[5], '%Y%m%dT%H%M%S')
en = datetime.strptime(slc.split('_')[6], '%Y%m%dT%H%M%S')
else:
st, en = datetime(1989, 3, 1), datetime(1989, 3, 1)
for j in range(nslcs):
slc = slcs.data[j]
if slc:
## get the maximum range
st_tmp = datetime.strptime(slc.split('_')[5], '%Y%m%dT%H%M%S')
en_tmp = datetime.strptime(slc.split('_')[6], '%Y%m%dT%H%M%S')

return int(ref), int(sec)
## check the second SLC is within one day of the previous
if st > datetime(1989, 3, 1):
stdiff = np.abs((st_tmp - st).days)
endiff = np.abs((en_tmp - en).days)
assert stdiff < 2 and endiff < 2, 'SLCs granules are too far apart in time. Incorrect metadata'


def get_time(self):
""" Get the center time of the secondary date from the filename """
tt = os.path.basename(self.path_gunw).split('-')[7]
return f'{tt[:2]}:{tt[2:4]}:{tt[4:]}'
st = st_tmp if st_tmp > st else st
en = en_tmp if en_tmp > en else en

assert st>datetime(1989, 3, 1), f'Missing {key} SLC metadata in GUNW: {self.f}'

lst_sten.append([st, en])

return lst_sten


def get_look_dir(self):
Expand All @@ -87,40 +134,35 @@ def get_wavelength(self):
return wavelength


def get_orbit_files(self):
def get_orbit_file(self):
""" Get orbit file for reference (GUNW: first & later date)"""
orbit_dir = os.path.join(self.out_dir, 'orbits')
os.makedirs(orbit_dir, exist_ok=True)

group = 'science/radarMetaData/inputSLC'
paths = []
for i, key in enumerate('reference secondary'.split()):
ds = xr.open_dataset(self.path_gunw, group=f'{group}/{key}')
slcs = ds['L1InputGranules']
nslcs = slcs.count().item()
# single slc
if nslcs == 1:
slc = slcs.item()
assert slc, f'Missing {key} SLC metadata in GUNW: {self.f}'
else:
found = False
for j in range(nslcs):
slc = slcs.data[j]
if slc:
found = True
break
assert found, f'Missing {key} SLC metadata in GUNW: {self.f}'
# just to get the correct satellite
group = 'science/radarMetaData/inputSLC/reference'

ds = xr.open_dataset(self.path_gunw, group=f'{group}')
slcs = ds['L1InputGranules']
nslcs = slcs.count().item()

if nslcs == 1:
slc = slcs.item()
else:
for j in range(nslcs):
slc = slcs.data[j]
if slc:
break

sat = slc.split('_')[0]
dt = datetime.strptime(f'{self.dates[0]}T{self.mid_time}', '%Y%m%dT%H:%M:%S')

sat = slc.split('_')[0]
aq_date = self.dates[i]
dt = datetime.strptime(f'{aq_date}T{self.ref_time}', '%Y%m%dT%H:%M:%S')
## prefer to do each individually to make sure order stays same
path_orb = download_eofs([dt], [sat], save_dir=orbit_dir)
paths.append(path_orb[0])
path_orb = download_eofs([dt], [sat], save_dir=orbit_dir)

return paths
return path_orb


## ------ below are not used
## ------ methods below are not used
def get_version(self):
with xr.open_dataset(self.path_gunw) as ds:
version = ds.attrs['version']
Expand Down Expand Up @@ -236,7 +278,6 @@ def main(args):
credentials.check_api(args.weather_model, args.api_uid, args.api_key)

GUNWObj = GUNW(args.file, args.weather_model, args.output_directory)
GUNWObj()

raider_cfg = {
'weather_model': args.weather_model,
Expand All @@ -247,7 +288,7 @@ def main(args):
'date_group': {'date_list': GUNWObj.dates},
'time_group': {'time': GUNWObj.mid_time, 'interpolate_time': True},
'los_group' : {'ray_trace': True,
'orbit_file': GUNWObj.OrbitFiles,
'orbit_file': GUNWObj.OrbitFile,
'wavelength': GUNWObj.wavelength,
},

Expand Down
7 changes: 3 additions & 4 deletions tools/RAiDER/llreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@

class AOI(object):
'''
This instantiates a generic AOI class object.
This instantiates a generic AOI class object.
Attributes:
_bounding_box - S N W E bounding box
_proj - pyproj-compatible CRS
_type - Type of AOI
_proj - pyproj-compatible CRS
_type - Type of AOI
'''
def __init__(self):
self._output_directory = os.getcwd()
Expand Down Expand Up @@ -74,7 +74,6 @@ def set_output_directory(self, output_directory):
return



class StationFile(AOI):
'''Use a .csv file containing at least Lat, Lon, and optionally Hgt_m columns'''
def __init__(self, station_file, demFile=None):
Expand Down
Loading

0 comments on commit 2f28cb5

Please sign in to comment.