forked from insarlab/MintPy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
s1ab_range_bias.py
262 lines (211 loc) · 10.2 KB
/
s1ab_range_bias.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
############################################################
# Program is part of MintPy #
# Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi #
# Author: Zhang Yunjun, Apr 2022 #
############################################################
import os
import numpy as np
from matplotlib import colors, pyplot as plt, ticker
from mintpy.objects import timeseries
from mintpy.utils import plot as pp, readfile, s1_utils, writefile
####################################################################################
def estimate_s1ab_range_bias(ts_file, mask_file=None, safe_list_file=None):
"""Estimate the S1A/B range bias based on the time series file.
Parameters: ts_file - str, path of the time series range offset file
mask_file - str, path of the mask file, e.g., maskResInv.h5 file.
safe_list_file - str, path of the SAFE_files.txt file
Returns: bias_list - list of float32, median bias per subswath in meters
bias_est - 2D np.ndarray in size of (length, width) in float32, bias in meters
mask_list - list of 2D np.ndarray in size of (length, width) in bool
"""
mintpy_dir = os.path.dirname(ts_file)
print(f'Estimating S1A/B range bias from file: {ts_file}')
# read time series
ts_data = readfile.read(ts_file)[0]
length, width = ts_data.shape[-2:]
# read mask
mask_file = mask_file if mask_file else os.path.join(mintpy_dir, 'maskResInv.h5')
if mask_file and os.path.isfile(mask_file):
mask = readfile.read(mask_file)[0]
else:
mask = np.ones((length, width), dtype=np.bool_)
# estimate bias - 2D map
bias_est_poi = s1_utils.estimate_s1ab_bias(
mintpy_dir,
ts_data[:, mask],
safe_list_file=safe_list_file)[0]
if bias_est_poi is None:
print('Exit without estimating S1A/B range bias from the input time series.')
return None, None, None
bias_est = np.ones((length, width), dtype=np.float32) * np.nan
bias_est[mask] = bias_est_poi
# estimate bias - median
geom_file = os.path.join(mintpy_dir, 'inputs', 'geometryRadar.h5')
flag = readfile.read(geom_file, datasetName='height')[0] != 0
mask_list = s1_utils.get_subswath_masks(flag, cut_overlap_in_half=False)[:3]
bias_list = [np.nanmedian(bias_est[x]) for x in mask_list]
print(f'IW1 : {bias_list[0]:.3f} m')
print(f'IW2 : {bias_list[1]:.3f} m')
print(f'IW3 : {bias_list[2]:.3f} m')
return bias_list, bias_est, mask_list
def plot_s1ab_range_bias_est(bias_list, bias_est, mask_list, out_dir=None,
save_fig=False, disp_fig=True):
"""Plot the S1A/B range bias estimation results.
Parameters: bias_list - list of float, mean S1A/B range bias in meters
bias_est - 2D np.ndarray in float32, pixelwised S1A/B range bias in meters
mask_list - list of 2D np.ndarray in bool, mask array for IW1/2/3
out_dir - str, output directory for the plotted figure.
"""
vmin, vmax = 7, 14
font_size = 12
out_dir = out_dir if out_dir else os.getcwd()
## figure 1 - map
fig_size = pp.auto_figure_size(ds_shape=bias_est.shape, disp_cbar=True, print_msg=True)
fig, ax = plt.subplots(figsize=fig_size)
cmap = colors.LinearSegmentedColormap.from_list('magma_t', plt.get_cmap('magma')(np.linspace(0.3, 1.0, 100)))
im = ax.imshow(bias_est*100., cmap=cmap, vmin=vmin, vmax=vmax, interpolation='nearest')
# axis format
ax.tick_params(which='both', direction='out', bottom=True, top=False, left=True, right=False)
ax.set_xlabel('Range [pixel]', fontsize=font_size)
ax.set_ylabel('Azimuth [pixel]', fontsize=font_size)
cbar = fig.colorbar(im, ax=ax)
cbar.set_label('S1A/B range bias [cm]', fontsize=font_size)
# output
if save_fig:
out_fig = os.path.join(out_dir, 's1ab_range_bias_map.pdf')
print('save figure to file', out_fig)
plt.savefig(out_fig, bbox_inches='tight', transparent=True, dpi=300)
## figure 2 - histogram
clist = [cmap((x*100. - vmin) / (vmax - vmin)) for x in bias_list]
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=[6, 2])
for bias, mask, c in zip(bias_list, mask_list, clist):
ax.hist(bias_est[mask].flatten()*100, bins=70, range=(vmin, vmax), density=False, alpha=0.7, color=c)
ax.axvline(bias*100, color='k')
# plot median value
ax.tick_params(which='both', direction='out', bottom=True, top=True, left=True, right=True)
ax.xaxis.set_minor_locator(ticker.AutoMinorLocator())
ax.set_xlim(vmin, vmax)
ax.set_xlabel('S1A/B range bias [cm]')
ax.set_ylabel('# of pixels')
fig.tight_layout()
# output
if save_fig:
out_fig = os.path.join(out_dir, 's1ab_range_bias_hist.pdf')
print('save figure to file', out_fig)
plt.savefig(out_fig, bbox_inches='tight', transparent=True, dpi=300)
if disp_fig:
print('showing ....')
plt.show()
else:
plt.close()
return
def write_s1ab_bias_file(bias_file, bias_list, geom_file, force=False):
"""Write estimated S1A/B range bias to HDF5 file.
Parameters: bias_file - str, path to the S1A/B range bias file
bias_list - list of float, constant S1A/B range bias per IW1/2/3
geom_file - str, path to the geometry file
force - bool, overwrite existing bias file.
Returns: bias_file - str, path to the S1A/B range bias file
"""
# run or skip
if os.path.isfile(bias_file) and not force:
print(f'S1Bias file exists in: {bias_file}, skip re-writing.')
return bias_file
# get the list of masks for IW1/2/3
flag = readfile.read(geom_file, datasetName='height')[0] != 0
mask_list = s1_utils.get_subswath_masks(flag, cut_overlap_in_half=False)[:3]
bias_mat = np.zeros(flag.shape, dtype=np.float32) * np.nan
for bias, mask in zip(bias_list, mask_list):
bias_mat[mask] = bias
# write file
atr = readfile.read_attribute(geom_file)
atr['FILE_TYPE'] = 'offset'
atr['UNIT'] = 'm'
print(f'writing S1A/B range bias to file: {bias_file}')
writefile.write(bias_mat, out_file=bias_file, metadata=atr)
return bias_file
def correct_s1ab_range_bias(ts_file, bias_file, ts_cor_file=None, safe_list_file=None):
"""Correct input time series for the S1A/B range bias.
Parameters: ts_file - str, path to the range offset time series file
bias_file - str, path to the S1A/B range bias file
ts_cor_file - str, path to the corrected range offset time series file
safe_list_file - str, path to the SAFE_files.txt file
Returns: ts_cor_file - str, path to the corrected range offset time series file
"""
if not os.path.isfile(bias_file):
msg = f'No bias file found in: {bias_file}!'
msg += '\nRe-run with "--action compute" to generate it.'
raise FileNotFoundError(msg)
date_list = timeseries(ts_file).get_date_list()
num_date = len(date_list)
# date info for Sentinel-1B
mintpy_dir = os.path.dirname(os.path.dirname(bias_file))
s1b_date_list_file = s1_utils.get_s1ab_date_list_file(mintpy_dir, safe_list_file)[1]
s1b_date_list = np.loadtxt(s1b_date_list_file, dtype=str).tolist()
s1b_flag = np.array([x in s1b_date_list for x in date_list], dtype=np.bool_)
# read data
ts_data = readfile.read(ts_file)[0].reshape(num_date, -1)
bias = readfile.read(bias_file)[0].flatten()
# correct bias
mask = ts_data == 0
ts_data[s1b_flag] -= np.tile(bias.reshape(1, -1), (np.sum(s1b_flag), 1))
ts_data[mask] = 0 # Do not change zero value in the input TS file
ts_data[:, np.isnan(bias)] = np.nan # set to nan for pixels with nan in bias file
# write file
if not ts_cor_file:
ts_cor_file = f'{os.path.splitext(ts_file)[0]}_S1Bias.h5'
atr = readfile.read_attribute(ts_file)
length = int(atr['LENGTH'])
width = int(atr['WIDTH'])
writefile.write(ts_data.reshape(num_date, length, width),
out_file=ts_cor_file,
metadata=atr,
ref_file=ts_file)
return ts_cor_file
####################################################################################
def run_s1ab_range_bias(inps):
# matplotlib backend setting
if not inps.disp_fig:
plt.switch_backend('Agg')
# default bias file path
inps.bias_file = os.path.join(os.path.dirname(inps.geom_file), 'S1Bias.h5')
# calculate the S1A/B range bias
if inps.action == 'compute':
if inps.bias_method == 'hardwired':
# option 1 - use the hardwired value from section VII-A in Yunjun et al. (2022)
bias_list = [0.087, 0.106, 0.123] # m
print('Used hardwired S1A/B range bias values from Yunjun et al. (2022):')
print(f'IW1 : {bias_list[0]:.3f} m')
print(f'IW2 : {bias_list[1]:.3f} m')
print(f'IW3 : {bias_list[2]:.3f} m')
else:
# option 2 - estimate from the time series of its dataset itself
# estimate optimal (median) value for each subswath from SenDT156
bias_list, bias_est, mask_list = estimate_s1ab_range_bias(
ts_file=inps.ts_file,
mask_file=inps.mask_file,
safe_list_file=inps.safe_list_file)
# plot the estimation result
if bias_list:
plot_s1ab_range_bias_est(
bias_list,
bias_est,
mask_list,
out_dir=os.path.dirname(inps.ts_file),
save_fig=inps.save_fig,
disp_fig=inps.disp_fig)
# write S1Bias.h5 file
if bias_list:
write_s1ab_bias_file(
bias_file=inps.bias_file,
bias_list=bias_list,
geom_file=inps.geom_file,
force=inps.force)
# correct time series range offset file
elif inps.action == 'correct':
correct_s1ab_range_bias(
ts_file=inps.ts_file,
bias_file=inps.bias_file,
ts_cor_file=inps.ts_cor_file,
safe_list_file=inps.safe_list_file)
return