Skip to content

Commit 54ea942

Browse files
Merge pull request #71 from TUDelftGeodesy/70-metadata
Metadata orbit array
2 parents 08abf10 + 941b716 commit 54ea942

3 files changed

Lines changed: 116 additions & 49 deletions

File tree

sarxarray/_io.py

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import xarray as xr
1313

1414
from .conf import (
15+
META_ARRAY_KEYS,
1516
META_FLOAT_KEYS,
1617
META_INT_KEYS,
1718
RE_PATTERNS_DORIS4,
@@ -360,7 +361,7 @@ def read_metadata(
360361
# if a key does not exists, a list will be created
361362
metadata = defaultdict(list)
362363
for file in files:
363-
res = _parse_metadata(file, driver)
364+
res = _parse_metadata(file, driver, ifg_file_name)
364365
for key, value in res.items():
365366
metadata[key].append(value)
366367

@@ -370,7 +371,7 @@ def read_metadata(
370371
return metadata
371372

372373

373-
def _parse_metadata(file, driver, ifg_file_name="ifgs.res"):
374+
def _parse_metadata(file, driver, ifg_file_name):
374375
"""Parse a single metadata file to a dictionary of strings."""
375376
# Select the appropriate patterns based on the driver
376377
if driver == "doris5":
@@ -379,6 +380,11 @@ def _parse_metadata(file, driver, ifg_file_name="ifgs.res"):
379380
elif driver == "doris4":
380381
patterns = RE_PATTERNS_DORIS4
381382
patterns_ifg = None
383+
else:
384+
raise NotImplementedError(
385+
f"Driver '{driver}' is not implemented. "
386+
"Supported drivers are: 'doris4', 'doris5'."
387+
)
382388

383389
# Open the file
384390
with open(file) as f:
@@ -387,11 +393,18 @@ def _parse_metadata(file, driver, ifg_file_name="ifgs.res"):
387393
# Read common metadata patterns
388394
results = {}
389395
for key, pattern in patterns.items():
390-
match = re.search(pattern, content)
391-
if match:
392-
results[key] = match.group(1)
396+
if key in META_ARRAY_KEYS.keys(): # multiple hits allowed
397+
matches = re.findall(pattern, content)
398+
if matches:
399+
results[key] = matches
400+
else:
401+
results[key] = None
393402
else:
394-
results[key] = None
403+
match = re.search(pattern, content)
404+
if match:
405+
results[key] = match.group(1)
406+
else:
407+
results[key] = None
395408

396409
# Doris5 has size information in ifgs.res file
397410
# Try to get the ifg size from ifgs.res next to slave.res, if it exists
@@ -400,12 +413,19 @@ def _parse_metadata(file, driver, ifg_file_name="ifgs.res"):
400413
if file_ifg.exists():
401414
with open(file_ifg) as f_ifg:
402415
content_ifg = f_ifg.read()
403-
for key, pattern in RE_PATTERNS_DORIS5_IFG.items():
404-
match = re.search(pattern, content_ifg)
405-
if match:
406-
results[key] = match.group(1)
407-
else:
408-
results[key] = None
416+
for key, pattern in RE_PATTERNS_DORIS5_IFG.items():
417+
if key in META_ARRAY_KEYS.keys(): # multiple hits allowed
418+
matches = re.findall(pattern, content_ifg)
419+
if matches:
420+
results[key] = matches
421+
else:
422+
results[key] = None
423+
else:
424+
match = re.search(pattern, content_ifg)
425+
if match:
426+
results[key] = match.group(1)
427+
else:
428+
results[key] = None
409429

410430
return results
411431

@@ -446,34 +466,51 @@ def _regulate_metadata(metadata, driver):
446466
"Different types are found in the value list."
447467
)
448468

449-
# Only keep the unique values
450-
if isinstance(metadata[key], list):
451-
metadata[key] = set(value)
469+
if key in META_ARRAY_KEYS.keys(): # need to regulate this one separately
470+
regulated_arrays = []
471+
for arr in metadata[key]:
472+
regulated_array = np.zeros((len(arr), len(arr[0])))
473+
for row in range(len(arr)):
474+
for col in range(len(arr[row])):
475+
regulated_array[row, col] = META_ARRAY_KEYS[key](arr[row][col])
476+
regulated_arrays.append(np.copy(regulated_array))
452477

453-
# Unfold the single value set to strings
454-
if len(metadata[key]) == 1:
455-
metadata[key] = next(iter(metadata[key]))
478+
metadata[key] = [
479+
np.copy(regulated_array) for regulated_array in regulated_arrays
480+
]
481+
if len(metadata[key]) == 1:
482+
metadata[key] = metadata[key][0]
456483

457-
# if float, take the average unless std is larger than 1% of the mean
458-
if key in META_FLOAT_KEYS:
459-
# Convert to float
460-
arr = np.array(value, dtype=np.float64)
461-
if np.std(arr) / np.mean(arr) < 0.01:
462-
metadata[key] = np.mean(arr).item() # Convert to scalar
463-
else:
464-
raise ValueError(
465-
f"Inconsistency found in metadata key: {key}. "
466-
"Standard deviation is larger than 1% of the mean."
467-
)
468-
if key in META_INT_KEYS:
469-
if isinstance(metadata[key], str):
470-
metadata[key] = int(metadata[key])
471-
elif len(metadata[key]) > 1: # set with multiple values
472-
metadata[key] = set([int(v) for v in metadata[key]])
473-
474-
if key in ["number_of_lines", "number_of_pixels"]:
475-
if isinstance(metadata[key], set):
476-
warning_msg = f"Multiple values found in {key}: {metadata[key]}."
477-
logger.warning(warning_msg)
484+
485+
else:
486+
# Only keep the unique values
487+
if isinstance(metadata[key], list):
488+
metadata[key] = set(value)
489+
490+
# Unfold the single value set to strings
491+
if len(metadata[key]) == 1:
492+
metadata[key] = next(iter(metadata[key]))
493+
494+
# if float, take the average unless std is larger than 1% of the mean
495+
if key in META_FLOAT_KEYS:
496+
# Convert to float
497+
arr = np.array(value, dtype=np.float64)
498+
if np.std(arr) / np.mean(arr) < 0.01:
499+
metadata[key] = np.mean(arr).item() # Convert to scalar
500+
else:
501+
raise ValueError(
502+
f"Inconsistency found in metadata key: {key}. "
503+
"Standard deviation is larger than 1% of the mean."
504+
)
505+
if key in META_INT_KEYS:
506+
if isinstance(metadata[key], str):
507+
metadata[key] = int(metadata[key])
508+
elif len(metadata[key]) > 1: # set with multiple values
509+
metadata[key] = set([int(v) for v in metadata[key]])
510+
511+
if key in ["number_of_lines", "number_of_pixels"]:
512+
if isinstance(metadata[key], set):
513+
warning_msg = f"Multiple values found in {key}: {metadata[key]}."
514+
logger.warning(warning_msg)
478515

479516
return metadata

sarxarray/conf.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
r"Pulse_Repetition_Frequency \(computed, Hz\):\s+([\d\.E\+\-]+)"
1818
),
1919
"total_azimuth_bandwidth": r"Total_azimuth_band_width \(Hz\):\s+([\d\.E\+\-]+)",
20-
"range_time_first_pixel": (
20+
"first_range_time": (
2121
r"Range_time_to_first_pixel \(2way\) \(ms\):\s+([\d\.E\+\-]+)"
2222
),
2323
"range_sampling_rate": r"Range_sampling_rate \(computed, MHz\):\s+([\d\.E\+\-]+)",
2424
"total_range_bandwidth": r"Total_range_band_width \(MHz\):\s+([\d\.E\+\-]+)",
2525
"weighting_azimuth": r"Weighting_azimuth:\s+(.+)",
2626
"weighting_range": r"Weighting_range:\s+(.+)",
27-
"first_pixel_azimuth_time": r"First_pixel_azimuth_time \(UTC\):\s+(.+)",
27+
"first_azimuth_time": r"First_pixel_azimuth_time \(UTC\):\s+(.+)",
2828
}
2929
# Regular expressions for reading metadata from DORIS4 files
3030
RE_PATTERNS_DORIS5 = {
@@ -45,11 +45,11 @@
4545
"pulse_repetition_frequency": (
4646
r"Pulse_Repetition_Frequency \(computed, Hz\):\s+([\d\.E\+\-]+)"
4747
),
48-
"first_pixel_azimuth_time": r"First_pixel_azimuth_time \(UTC\):\s+(.+)",
48+
"first_azimuth_time": r"First_pixel_azimuth_time \(UTC\):\s+(.+)",
4949
"azimuth_time_interval": r"Azimuth_time_interval \(s\):\s+([\d\.E\+\-]+)",
5050
"total_azimuth_bandwidth": r"Total_azimuth_band_width \(Hz\):\s+([\d\.E\+\-]+)",
5151
"weighting_azimuth": r"Weighting_azimuth:\s+(.+)",
52-
"range_time_first_pixel": (
52+
"first_range_time": (
5353
r"Range_time_to_first_pixel \(2way\) \(ms\):\s+([\d\.E\+\-]+)"
5454
),
5555
"range_sampling_rate": r"Range_sampling_rate \(computed, MHz\):\s+([\d\.E\+\-]+)",
@@ -59,6 +59,18 @@
5959
"deramp": r"deramp:\s+([\d\.E\+\-]+)",
6060
"reramp": r"reramp:\s+([\d\.E\+\-]+)",
6161
"esd_correct": r"ESD_correct:\s+([\d\.E\+\-]+)",
62+
"orbit_txyz": (
63+
r"(\d+)\s+([-+]?\d+\.\d+(?:\.\d+)?)\s+([-+]?\d+"
64+
r"\.\d+(?:\.\d+)?)\s+([-+]?\d+\.\d+(?:\.\d+)?)"
65+
),
66+
"scene_centre_latitude": (
67+
r"Scene_centre_latitude:"
68+
r"\s+([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)"
69+
),
70+
"scene_centre_longitude": (
71+
r"Scene_centre_longitude:"
72+
r"\s+([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)"
73+
)
6274
}
6375
# Regular expressions for reading metadata from DORIS5 interferogram files
6476
RE_PATTERNS_DORIS5_IFG = {
@@ -71,14 +83,16 @@
7183
"wavelength",
7284
"pulse_repetition_frequency",
7385
"total_azimuth_bandwidth",
74-
"range_time_first_pixel",
86+
"first_range_time",
7587
"range_sampling_rate",
7688
"total_range_bandwidth",
7789
"range_pixel_spacing", # from here DORIS5 only
7890
"azimuth_pixel_spacing",
7991
"radar_frequency",
8092
"pulse_repetition_frequency_raw",
8193
"azimuth_time_interval",
94+
"scene_centre_latitude",
95+
"scene_centre_longitude"
8296
]
8397
# Integer keys in metadata. They are used to regulate the metadata read as strings
8498
META_INT_KEYS = [
@@ -88,8 +102,13 @@
88102
"number_of_pixels",
89103
"esd_correct",
90104
]
105+
# Array keys in metadata and their format. Requires re.findall instead of re.match
106+
# Expects 2D arrays, and a callable variable type as value associated with each key
107+
META_ARRAY_KEYS = {
108+
"orbit_txyz": float # DORIS5 only
109+
}
91110
# Time formats for DORIS metadata
92111
TIME_FORMAT_DORIS4 = "%d-%b-%Y %H:%M:%S.%f"
93112
TIME_FORMAT_DORIS5 = "%Y-%b-%d %H:%M:%S.%f"
94113
# Time stamp key
95-
TIME_STAMP_KEY = "first_pixel_azimuth_time"
114+
TIME_STAMP_KEY = "first_azimuth_time"

tests/test_io.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sarxarray
1111
from sarxarray._io import _calc_chunksize, _unpack_complex
1212
from sarxarray.conf import (
13+
META_ARRAY_KEYS,
1314
META_FLOAT_KEYS,
1415
META_INT_KEYS,
1516
RE_PATTERNS_DORIS4,
@@ -185,17 +186,23 @@ def test_read_metadata_doris4(self, res_files_doris4):
185186
assert isinstance(metadata[key], float)
186187
elif key in META_INT_KEYS:
187188
assert isinstance(metadata[key], int)
188-
assert metadata["first_pixel_azimuth_time"].shape[0] == 3
189+
assert metadata["first_azimuth_time"].shape[0] == 3
189190

190191
def test_read_metadata_doris5(self, res_files_doris5, caplog):
191192
with caplog.at_level(logging.WARNING): # Last file has a wrong number of pixels
193+
# also catches the multiple orbit warning
192194
metadata = sarxarray.read_metadata(res_files_doris5, driver="doris5")
193195
for key in RE_PATTERNS_DORIS5.keys():
194196
assert key in metadata
195197
if key in META_FLOAT_KEYS:
196198
assert isinstance(metadata[key], float)
197199
elif key in META_INT_KEYS:
198200
assert isinstance(metadata[key], int)
201+
elif key in META_ARRAY_KEYS.keys():
202+
assert isinstance(metadata[key][0], np.ndarray)
203+
assert len(metadata[key]) == len(res_files_doris5)
204+
assert len(metadata[key][0].shape) == 2
205+
assert isinstance(metadata[key][0][0][0], META_ARRAY_KEYS[key])
199206
for key in RE_PATTERNS_DORIS5_IFG.keys():
200207
assert key in metadata
201208
if key in META_FLOAT_KEYS:
@@ -205,7 +212,7 @@ def test_read_metadata_doris5(self, res_files_doris5, caplog):
205212
assert len(metadata[key]) == 2 # Two values for ifg sizes
206213
else:
207214
assert isinstance(metadata[key], int)
208-
assert metadata["first_pixel_azimuth_time"].shape[0] == 3
215+
assert metadata["first_azimuth_time"].shape[0] == 3
209216

210217
def test_read_metadata_non_existent_driver(self, res_files_doris4):
211218
with pytest.raises(NotImplementedError):
@@ -219,7 +226,7 @@ def test_read_metadata_doris4_onefile(self, res_files_doris4):
219226
assert isinstance(metadata[key], float)
220227
elif key in META_INT_KEYS:
221228
assert isinstance(metadata[key], int)
222-
assert np.isscalar(metadata["first_pixel_azimuth_time"])
229+
assert np.isscalar(metadata["first_azimuth_time"])
223230

224231
def test_read_metadata_doris5_onefile(self, res_files_doris5, caplog):
225232
metadata = sarxarray.read_metadata(res_files_doris5[0], driver="doris5")
@@ -229,10 +236,14 @@ def test_read_metadata_doris5_onefile(self, res_files_doris5, caplog):
229236
assert isinstance(metadata[key], float)
230237
elif key in META_INT_KEYS:
231238
assert isinstance(metadata[key], int)
239+
elif key in META_ARRAY_KEYS.keys():
240+
assert isinstance(metadata[key], np.ndarray)
241+
assert len(metadata[key].shape) == 2
242+
assert isinstance(metadata[key][0][0], META_ARRAY_KEYS[key])
232243
for key in RE_PATTERNS_DORIS5_IFG.keys():
233244
assert key in metadata
234245
if key in META_FLOAT_KEYS:
235246
assert isinstance(metadata[key], float)
236247
elif key in META_INT_KEYS:
237248
assert isinstance(metadata[key], int)
238-
assert np.isscalar(metadata["first_pixel_azimuth_time"])
249+
assert np.isscalar(metadata["first_azimuth_time"])

0 commit comments

Comments
 (0)