Skip to content

Commit 40ae1c7

Browse files
hfattahiLiangJYu
andauthored
Doppler shift (#34)
* range delay caused by Doppler shift * update docs * address multiple styling comments * move the new functions next to bistatic_dely * address codacy, remove space * consolidate azimuth carrier methods make carrier method class member * make azimuth carrier a class * docstring nits * fix syntax error; add missing defs * fix undefined orbit Co-authored-by: Liang Yu <[email protected]>
1 parent 605dc0d commit 40ae1c7

File tree

2 files changed

+139
-56
lines changed

2 files changed

+139
-56
lines changed

src/s1reader/s1_burst_slc.py

Lines changed: 137 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,57 +10,6 @@
1010

1111

1212
# Other functionalities
13-
def compute_az_carrier(burst, orbit, offset, position):
14-
'''
15-
Estimate azimuth carrier and store in numpy arrary
16-
17-
Parameters
18-
----------
19-
burst: Sentinel1BurstSlc
20-
Sentinel1 burst object
21-
orbit: isce3.core.Orbit
22-
Sentinel1 orbit ephemerides
23-
offset: float
24-
Offset between reference and secondary burst
25-
position: tuple
26-
Tuple of locations along y and x directions
27-
28-
Returns
29-
-------
30-
carr: np.ndarray
31-
Azimuth carrier
32-
'''
33-
34-
# Get burst sensing mid relative to orbit reference epoch
35-
fmt = "%Y-%m-%dT%H:%M:%S.%f"
36-
orbit_ref_epoch = datetime.datetime.strptime(orbit.reference_epoch.__str__()[:-3], fmt)
37-
38-
t_mid = burst.sensing_mid - orbit_ref_epoch
39-
_, v = orbit.interpolate(t_mid.total_seconds())
40-
vs = np.linalg.norm(v)
41-
ks = 2 * vs * burst.azimuth_steer_rate / burst.wavelength
42-
43-
y, x = position
44-
45-
n_lines, _ = burst.shape
46-
eta = (y - (n_lines // 2) + offset) * burst.azimuth_time_interval
47-
rng = burst.starting_range + x * burst.range_pixel_spacing
48-
49-
f_etac = np.array(
50-
burst.doppler.poly1d.eval(rng.flatten().tolist())).reshape(rng.shape)
51-
ka = np.array(
52-
burst.azimuth_fm_rate.eval(rng.flatten().tolist())).reshape(rng.shape)
53-
54-
eta_ref = (burst.doppler.poly1d.eval(
55-
burst.starting_range) / burst.azimuth_fm_rate.eval(
56-
burst.starting_range)) - (f_etac / ka)
57-
kt = ks / (1.0 - ks / ka)
58-
59-
carr = np.pi * kt * ((eta - eta_ref) ** 2)
60-
61-
return carr
62-
63-
6413
def polyfit(xin, yin, zin, azimuth_order, range_order,
6514
sig=None, snr=None, cond=1.0e-12,
6615
max_order=True):
@@ -155,6 +104,19 @@ class represents a polynomial function of range
155104
poly = isce3.core.Poly2d(coeffs, xmin, ymin, xnorm, ynorm)
156105
return poly
157106

107+
@dataclass
108+
class AzimuthCarrierComponents:
109+
kt: np.ndarray
110+
eta: float
111+
eta_ref: float
112+
113+
@property
114+
def antenna_steering_doppler(self):
115+
return self.kt * (self.eta - self.eta_ref)
116+
117+
@property
118+
def carrier(self):
119+
return np.pi * self.kt * ((self.eta - self.eta_ref) ** 2)
158120

159121
@dataclass(frozen=True)
160122
class Doppler:
@@ -198,6 +160,7 @@ class Sentinel1BurstSlc:
198160
range_window_coefficient: float
199161
rank: int # The number of PRI between transmitted pulse and return echo.
200162
prf_raw_data: float # Pulse repetition frequency (PRF) of the raw data [Hz]
163+
range_chirp_rate: float # Range chirp rate [Hz]
201164

202165
def as_isce3_radargrid(self):
203166
'''Init and return isce3.product.RadarGridParameters.
@@ -339,14 +302,14 @@ class represents a polynomial function of range
339302
x_mesh, y_mesh = np.meshgrid(x, y)
340303

341304
# Estimate azimuth carrier
342-
az_carrier = compute_az_carrier(self, self.orbit,
305+
az_carr_comp = self.az_carrier_components(
343306
offset=offset,
344307
position=(y_mesh, x_mesh))
345308

346309
# Fit azimuth carrier polynomial with x/y or range/azimuth
347310
if index_as_coord:
348311
az_carrier_poly = polyfit(x_mesh.flatten()+1, y_mesh.flatten()+1,
349-
az_carrier.flatten(), az_order,
312+
az_carr_comp.carrier.flatten(), az_order,
350313
rg_order)
351314
else:
352315
# Convert x/y to range/azimuth
@@ -356,7 +319,7 @@ class represents a polynomial function of range
356319

357320
# Estimate azimuth carrier polynomials
358321
az_carrier_poly = polyfit(rg_mesh.flatten(), az_mesh.flatten(),
359-
az_carrier.flatten(), az_order,
322+
az_carr_comp.carrier.flatten(), az_order,
360323
rg_order)
361324

362325
return az_carrier_poly
@@ -478,6 +441,126 @@ def bistatic_delay(self, xstep=1, ystep=1):
478441

479442
return isce3.core.LUT2d(x, y, bistatic_correction)
480443

444+
def geometrical_and_steering_doppler(self, xstep=500, ystep=50):
445+
"""
446+
Compute total Doppler which is the sum of two components:
447+
(1) the geometrical Doppler induced by the relative movement
448+
of the sensor and target
449+
(2) the TOPS specicifc Doppler caused by the electric steering
450+
of the beam along the azimuth direction resulting in Doppler varying
451+
with azimuth time.
452+
Parameters
453+
----------
454+
xstep: int
455+
Spacing along x direction [pixels]
456+
ystep: int
457+
Spacing along y direction [pixels]
458+
459+
Returns
460+
-------
461+
x : int
462+
The index of samples in range direction as an 1D array
463+
y : int
464+
The index of samples in azimuth direction as an 1D array
465+
total_doppler : float
466+
Total Doppler which is the sum of the geometrical Doppler and
467+
beam steering induced Doppler [Hz] as a 2D array
468+
"""
469+
470+
x = np.arange(0, self.width, xstep, dtype=int)
471+
y = np.arange(0, self.length, ystep, dtype=int)
472+
x_mesh, y_mesh = np.meshgrid(x, y)
473+
az_carr_comp = self.az_carrier_components(
474+
offset=0.0,
475+
position=(y_mesh, x_mesh))
476+
477+
slant_range = self.starting_range + x * self.range_pixel_spacing
478+
geometrical_doppler = self.doppler.poly1d.eval(slant_range)
479+
480+
total_doppler = az_carr_comp.antenna_steering_doppler + geometrical_doppler
481+
482+
return x, y, total_doppler
483+
484+
def doppler_induced_range_shift(self, xstep=500, ystep=50):
485+
"""
486+
Computes the range delay caused by the Doppler shift as described
487+
by Gisinger et al 2021
488+
489+
Parameters
490+
----------
491+
xstep: int
492+
Spacing along x direction [pixels]
493+
ystep: int
494+
Spacing along y direction [pixels]
495+
496+
Returns
497+
-------
498+
isce3.core.LUT2d:
499+
LUT2D object of range delay correction [seconds] as a function
500+
of the x and y indices.
501+
502+
"""
503+
504+
x, y, doppler_shift = self.geometrical_and_steering_doppler(
505+
xstep=xstep, ystep=ystep)
506+
tau_corr = doppler_shift / self.range_chirp_rate
507+
508+
return isce3.core.LUT2d(x, y, tau_corr)
509+
510+
def az_carrier_components(self, offset, position):
511+
'''
512+
Estimate azimuth carrier and store in numpy arrary. Also return
513+
contributing components.
514+
515+
Parameters
516+
----------
517+
offset: float
518+
Offset between reference and secondary burst
519+
position: tuple
520+
Tuple of locations along y and x directions
521+
522+
Returns
523+
-------
524+
eta: float
525+
zero-Doppler azimuth time centered in the middle of the burst
526+
eta_ref: float
527+
refernce time
528+
kt: np.ndarray
529+
Doppler centroid rate in the focused TOPS SLC data [Hz/s]
530+
carr: np.ndarray
531+
Azimuth carrier
532+
533+
Reference
534+
---------
535+
https://sentinels.copernicus.eu/documents/247904/0/Sentinel-1-TOPS-SLC_Deramping/b041f20f-e820-46b7-a3ed-af36b8eb7fa0
536+
'''
537+
# Get self.sensing mid relative to orbit reference epoch
538+
fmt = "%Y-%m-%dT%H:%M:%S.%f"
539+
orbit_ref_epoch = datetime.datetime.strptime(self.orbit.reference_epoch.__str__()[:-3], fmt)
540+
541+
t_mid = self.sensing_mid - orbit_ref_epoch
542+
_, v = self.orbit.interpolate(t_mid.total_seconds())
543+
vs = np.linalg.norm(v)
544+
ks = 2 * vs * self.azimuth_steer_rate / self.wavelength
545+
546+
y, x = position
547+
548+
n_lines, _ = self.shape
549+
eta = (y - (n_lines // 2) + offset) * self.azimuth_time_interval
550+
rng = self.starting_range + x * self.range_pixel_spacing
551+
552+
f_etac = np.array(
553+
self.doppler.poly1d.eval(rng.flatten().tolist())).reshape(rng.shape)
554+
ka = np.array(
555+
self.azimuth_fm_rate.eval(rng.flatten().tolist())).reshape(rng.shape)
556+
557+
eta_ref = (self.doppler.poly1d.eval(
558+
self.starting_range) / self.azimuth_fm_rate.eval(
559+
self.starting_range)) - (f_etac / ka)
560+
kt = ks / (1.0 - ks / ka)
561+
562+
563+
return AzimuthCarrierComponents(kt, eta, eta_ref)
481564

482565
@property
483566
def sensing_mid(self):

src/s1reader/s1_reader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ def burst_from_xml(annotation_path: str, orbit_path: str, tiff_path: str,
299299
downlink_element = tree.find('generalAnnotation/downlinkInformationList/downlinkInformation')
300300
prf_raw_data = float(downlink_element.find('prf').text)
301301
rank = int(downlink_element.find('downlinkValues/rank').text)
302+
range_chirp_ramp_rate = float(downlink_element.find('downlinkValues/txPulseRampRate').text)
302303

303304
n_lines = int(tree.find('swathTiming/linesPerBurst').text)
304305
n_samples = int(tree.find('swathTiming/samplesPerBurst').text)
@@ -399,8 +400,7 @@ def burst_from_xml(annotation_path: str, orbit_path: str, tiff_path: str,
399400
tiff_path, i, first_valid_sample,
400401
last_sample, first_valid_line, last_line,
401402
range_window_type, range_window_coeff,
402-
rank, prf_raw_data)
403-
403+
rank, prf_raw_data, range_chirp_ramp_rate)
404404
return bursts
405405

406406
def _is_zip_annotation_xml(path: str, id_str: str) -> bool:

0 commit comments

Comments
 (0)