Skip to content

Commit 605dc0d

Browse files
hfattahiyunjunzLiangJYu
authored
Bistatic delay (#32)
+ s1_burst_slc.py: add `bistatic_delay()` following Gisinger et al. (2021, TGRS). Further clarification is needed to confirm whether there is any discrepancy between the IPF implementation and what is described in the ETAD ATBD, on the mid_range definition specifically. + s1_reader.burst_from_xml(): add a new argument `iw2_annotation_path` to compute `iw2_mid_range` and pass to Sentinel1BurstSlc class + s1_reader._burst_from_zip()/_burst_from_safe_dir(): find annotation file for IW2 and pass to burst_from_xml(). Co-authored-by: Zhang Yunjun <[email protected]> Co-authored-by: Liang Yu <[email protected]>
1 parent 7761736 commit 605dc0d

File tree

2 files changed

+95
-15
lines changed

2 files changed

+95
-15
lines changed

src/s1reader/s1_burst_slc.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ def compute_az_carrier(burst, orbit, offset, position):
4848

4949
f_etac = np.array(
5050
burst.doppler.poly1d.eval(rng.flatten().tolist())).reshape(rng.shape)
51-
ka = np.array(burst.azimuth_fm_rate.eval(rng.flatten().tolist())).reshape(
52-
rng.shape)
51+
ka = np.array(
52+
burst.azimuth_fm_rate.eval(rng.flatten().tolist())).reshape(rng.shape)
5353

5454
eta_ref = (burst.doppler.poly1d.eval(
5555
burst.starting_range) / burst.azimuth_fm_rate.eval(
@@ -172,6 +172,7 @@ class Sentinel1BurstSlc:
172172
azimuth_time_interval: float
173173
slant_range_time: float
174174
starting_range: float
175+
iw2_mid_range: float
175176
range_sampling_rate: float
176177
range_pixel_spacing: float
177178
shape: tuple()
@@ -423,6 +424,61 @@ def as_dict(self):
423424
self_as_dict[key] = val
424425
return self_as_dict
425426

427+
def bistatic_delay(self, xstep=1, ystep=1):
428+
'''Computes the bistatic delay correction in azimuth direction
429+
due to the movement of the platform between pulse transmission and echo reception
430+
as described in equation (21) in Gisinger et al. (2021, TGRS).
431+
432+
References
433+
-------
434+
Gisinger, C., Schubert, A., Breit, H., Garthwaite, M., Balss, U., Willberg, M., et al.
435+
(2021). In-Depth Verification of Sentinel-1 and TerraSAR-X Geolocation Accuracy Using
436+
the Australian Corner Reflector Array. IEEE Trans. Geosci. Remote Sens., 59(2), 1154-
437+
1181. doi:10.1109/TGRS.2019.2961248
438+
ETAD-DLR-DD-0008, Algorithm Technical Baseline Document. Available: https://sentinels.
439+
copernicus.eu/documents/247904/4629150/Sentinel-1-ETAD-Algorithm-Technical-Baseline-
440+
Document.pdf
441+
442+
Parameters
443+
-------
444+
xstep : int
445+
spacing along x direction (range direction) in units of pixels
446+
447+
ystep : int
448+
spacing along y direction (azimuth direction) in units of pixels
449+
450+
Returns
451+
-------
452+
LUT2D object of bistatic delay correction in seconds as a function
453+
of the range and zimuth indices. This correction needs to be added
454+
to the SLC tagged azimuth time to get the corrected azimuth times.
455+
'''
456+
457+
pri = 1.0 / self.prf_raw_data
458+
tau0 = self.rank * pri
459+
tau_mid = self.iw2_mid_range * 2.0 / isce3.core.speed_of_light
460+
461+
nx = np.ceil(self.width / xstep).astype(int)
462+
ny = np.ceil(self.length / ystep).astype(int)
463+
x = np.arange(0, nx*xstep, xstep, dtype=int)
464+
y = np.arange(0, ny*ystep, ystep, dtype=int)
465+
466+
slant_range = self.starting_range + x * self.range_pixel_spacing
467+
tau = slant_range * 2.0 / isce3.core.speed_of_light
468+
469+
# the first term (tau_mid/2) is the bulk bistatic delay which was
470+
# removed from the orginial azimuth time by the ESA IPF. Based on
471+
# Gisinger et al. (2021) and ETAD ATBD, ESA IPF has used the mid of
472+
# the second subswath to compute the bulk bistatic delay. However
473+
# currently we have not been able to verify this from ESA documents.
474+
# This implementation follows the Gisinger et al. (2021) for now, we
475+
# can revise when we hear back from ESA folks.
476+
bistatic_correction_vec = tau_mid / 2 + tau / 2 - tau0
477+
bistatic_correction = np.tile(bistatic_correction_vec.reshape(1,-1), (ny,1))
478+
479+
return isce3.core.LUT2d(x, y, bistatic_correction)
480+
481+
426482
@property
427483
def sensing_mid(self):
428484
'''Returns sensing mid as datetime.datetime object.

src/s1reader/s1_reader.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -249,18 +249,20 @@ def get_burst_centers_and_boundaries(tree):
249249
return center_pts, boundary_pts
250250

251251
def burst_from_xml(annotation_path: str, orbit_path: str, tiff_path: str,
252-
open_method=open):
253-
'''Parse bursts in Sentinel 1 annotation XML.
252+
iw2_annotation_path: str, open_method=open):
253+
'''Parse bursts in Sentinel-1 annotation XML.
254254
255255
Parameters:
256256
-----------
257257
annotation_path : str
258-
Path to Sentinel 1 annotation XML file of specific subswath and
258+
Path to Sentinel-1 annotation XML file of specific subswath and
259259
polarization.
260260
orbit_path : str
261261
Path the orbit file.
262262
tiff_path : str
263-
Path to tiff file holding Sentinel 1 SLCs.
263+
Path to tiff file holding Sentinel-1 SLCs.
264+
iw2_annotation_path : str
265+
Path to Sentinel-1 annotation XML file of IW2 subswath.
264266
open_method : function
265267
Function used to open annotation file.
266268
@@ -324,6 +326,14 @@ def burst_from_xml(annotation_path: str, orbit_path: str, tiff_path: str,
324326
starting_range = slant_range_time * isce3.core.speed_of_light / 2
325327
range_pxl_spacing = isce3.core.speed_of_light / (2 * range_sampling_rate)
326328

329+
# calculate the range at mid swath (mid of SM swath, mid of IW2 or mid of EW3)
330+
with open_method(iw2_annotation_path, 'r') as iw2_f:
331+
iw2_tree = ET.parse(iw2_f)
332+
iw2_slant_range_time = float(iw2_tree.find('imageAnnotation/imageInformation/slantRangeTime').text)
333+
iw2_n_samples = int(iw2_tree.find('swathTiming/samplesPerBurst').text)
334+
iw2_starting_range = iw2_slant_range_time * isce3.core.speed_of_light / 2
335+
iw2_mid_range = iw2_starting_range + 0.5 * iw2_n_samples * range_pxl_spacing
336+
327337
# find orbit state vectors in 'Data_Block/List_of_OSVs'
328338
orbit_tree = ET.parse(orbit_path)
329339
osv_list = orbit_tree.find('Data_Block/List_of_OSVs')
@@ -380,7 +390,7 @@ def burst_from_xml(annotation_path: str, orbit_path: str, tiff_path: str,
380390

381391
bursts[i] = Sentinel1BurstSlc(sensing_start, radar_freq, wavelength,
382392
azimuth_steer_rate, azimuth_time_interval,
383-
slant_range_time, starting_range,
393+
slant_range_time, starting_range, iw2_mid_range,
384394
range_sampling_rate, range_pxl_spacing,
385395
(n_lines, n_samples), az_fm_rate, doppler,
386396
rng_processing_bandwidth, pol, burst_id,
@@ -416,12 +426,12 @@ def _is_zip_annotation_xml(path: str, id_str: str) -> bool:
416426
return False
417427

418428
def load_bursts(path: str, orbit_path: str, swath_num: int, pol: str = 'vv'):
419-
'''Find bursts in a Sentinel 1 zip file or a SAFE structured directory.
429+
'''Find bursts in a Sentinel-1 zip file or a SAFE structured directory.
420430
421431
Parameters:
422432
-----------
423433
path : str
424-
Path to Sentinel 1 zip file or SAFE directory
434+
Path to Sentinel-1 zip file or SAFE directory
425435
orbit_path : str
426436
Path the orbit file.
427437
swath_num : int
@@ -456,7 +466,7 @@ def load_bursts(path: str, orbit_path: str, swath_num: int, pol: str = 'vv'):
456466
raise ValueError(f'{path} is unsupported')
457467

458468
def _burst_from_zip(zip_path: str, id_str: str, orbit_path: str):
459-
'''Find bursts in a Sentinel 1 zip file.
469+
'''Find bursts in a Sentinel-1 zip file.
460470
461471
Parameters:
462472
-----------
@@ -475,22 +485,29 @@ def _burst_from_zip(zip_path: str, id_str: str, orbit_path: str):
475485
with zipfile.ZipFile(zip_path, 'r') as z_file:
476486
z_file_list = z_file.namelist()
477487

478-
# find annotation file
488+
# find annotation file - subswath of interest
479489
f_annotation = [f for f in z_file_list if _is_zip_annotation_xml(f, id_str)]
480490
if not f_annotation:
481491
raise ValueError(f"burst {id_str} not in SAFE: {zip_path}")
482492
f_annotation = f_annotation[0]
483493

494+
# find annotation file - IW2
495+
iw2_id_str = f'iw2-{id_str[4:]}'
496+
iw2_f_annotation = [f for f in z_file_list if _is_zip_annotation_xml(f, iw2_id_str)]
497+
if not iw2_f_annotation:
498+
raise ValueError(f"burst {iw2_id_str} not in SAFE: {zip_path}")
499+
iw2_f_annotation = iw2_f_annotation[0]
500+
484501
# find tiff file
485502
f_tiff = [f for f in z_file_list
486503
if 'measurement' in f and id_str in f and 'tiff' in f]
487504
f_tiff = f'/vsizip/{zip_path}/{f_tiff[0]}' if f_tiff else ''
488505

489-
bursts = burst_from_xml(f_annotation, orbit_path, f_tiff, z_file.open)
506+
bursts = burst_from_xml(f_annotation, orbit_path, f_tiff, iw2_f_annotation, z_file.open)
490507
return bursts
491508

492509
def _burst_from_safe_dir(safe_dir_path: str, id_str: str, orbit_path: str):
493-
'''Find bursts in a Sentinel 1 SAFE structured directory.
510+
'''Find bursts in a Sentinel-1 SAFE structured directory.
494511
495512
Parameters:
496513
-----------
@@ -507,13 +524,20 @@ def _burst_from_safe_dir(safe_dir_path: str, id_str: str, orbit_path: str):
507524
List of Sentinel1BurstSlc objects found in annotation XML.
508525
'''
509526

510-
# find annotation file
527+
# find annotation file - subswath of interest
511528
annotation_list = os.listdir(f'{safe_dir_path}/annotation')
512529
f_annotation = [f for f in annotation_list if id_str in f]
513530
if not f_annotation:
514531
raise ValueError(f"burst {id_str} not in SAFE: {safe_dir_path}")
515532
f_annotation = f'{safe_dir_path}/annotation/{f_annotation[0]}'
516533

534+
# find annotation file - IW2
535+
iw2_id_str = f'iw2-{id_str[4:]}'
536+
iw2_f_annotation = [f for f in annotation_list if iw2_id_str in f]
537+
if not iw2_f_annotation:
538+
raise ValueError(f"burst {iw2_id_str} not in SAFE: {safe_dir_path}")
539+
iw2_f_annotation = f'{safe_dir_path}/annotation/{iw2_f_annotation[0]}'
540+
517541
# find tiff file if measurement directory found
518542
if os.path.isdir(f'{safe_dir_path}/measurement'):
519543
measurement_list = os.listdir(f'{safe_dir_path}/measurement')
@@ -525,5 +549,5 @@ def _burst_from_safe_dir(safe_dir_path: str, id_str: str, orbit_path: str):
525549
warnings.warn(warning_str)
526550
f_tiff = ''
527551

528-
bursts = burst_from_xml(f_annotation, orbit_path, f_tiff)
552+
bursts = burst_from_xml(f_annotation, orbit_path, f_tiff, iw2_f_annotation)
529553
return bursts

0 commit comments

Comments
 (0)