Coverage for sarvey/viewer.py: 21%

286 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-17 12:36 +0000

1#!/usr/bin/env python 

2 

3# SARvey - A multitemporal InSAR time series tool for the derivation of displacements. 

4# 

5# Copyright (C) 2021-2024 Andreas Piter (IPI Hannover, piter@ipi.uni-hannover.de) 

6# 

7# This software was developed together with FERN.Lab (fernlab@gfz-potsdam.de) in the context 

8# of the SAR4Infra project with funds of the German Federal Ministry for Digital and 

9# Transport and contributions from Landesamt fuer Vermessung und Geoinformation 

10# Schleswig-Holstein and Landesbetrieb Strassenbau und Verkehr Schleswig-Holstein. 

11# 

12# This program is free software: you can redistribute it and/or modify it under 

13# the terms of the GNU General Public License as published by the Free Software 

14# Foundation, either version 3 of the License, or (at your option) any later 

15# version. 

16# 

17# Important: This package uses PyMaxFlow. The core of PyMaxflows library is the C++ 

18# implementation by Vladimir Kolmogorov. It is also licensed under the GPL, but it REQUIRES that you 

19# cite [BOYKOV04] (see LICENSE) in any resulting publication if you use this code for research purposes. 

20# This requirement extends to SARvey. 

21# 

22# This program is distributed in the hope that it will be useful, but WITHOUT 

23# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 

24# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 

25# details. 

26# 

27# You should have received a copy of the GNU Lesser General Public License along 

28# with this program. If not, see <https://www.gnu.org/licenses/>. 

29 

30"""Viewer Module for SARvey.""" 

31import os 

32from typing import Any 

33from logging import Logger 

34import matplotlib.cm as cm 

35import matplotlib.patches as patches 

36import matplotlib.pyplot as plt 

37from matplotlib import colormaps, widgets 

38from matplotlib.backend_bases import MouseButton 

39from matplotlib.colors import Normalize 

40import numpy as np 

41from scipy.spatial import KDTree 

42import datetime 

43 

44from mintpy.objects.colors import ColormapExt 

45from mintpy.utils import readfile 

46from mintpy.utils.plot import auto_flip_direction 

47 

48from sarvey.objects import AmplitudeImage, Points, BaseStack 

49import sarvey.utils as ut 

50 

51 

52def plotIfgs(*, phase: np.ndarray, coord: np.ndarray, spatial_ref_idx: int = None, ttl: str = None, cmap: str = "cmy"): 

53 """Plot one interferogram per subplot. 

54 

55 Parameters 

56 ---------- 

57 phase: np.ndarray 

58 phase per point and ifg, e.g. wrapped or unwrapped phase (dim: no. psPoints x no. ifgs) 

59 coord: np.ndarray 

60 coordinates of the psPoints, e.g. pixel or lat lon (dim: no. psPoints x 2) 

61 spatial_ref_idx: int 

62 index of the spatial reference point (default: None) 

63 ttl: str 

64 title for the figure (default: None) 

65 cmap: str 

66 colormap, use "cmy" for wrapped phase data (default) or "?" for unwrapped or residual phase 

67 """ 

68 if cmap == "cmy": 

69 cmap = ColormapExt('cmy').colormap 

70 else: 

71 cmap = plt.get_cmap(cmap) 

72 

73 num_ifgs = phase.shape[1] 

74 min_val = np.min(phase) 

75 max_val = np.max(phase) 

76 fig, axs = plt.subplots(np.ceil(np.sqrt(num_ifgs + 1)).astype(np.int32), 

77 np.ceil(np.sqrt(num_ifgs + 1)).astype(np.int32)) 

78 sc = None 

79 for i, ax in enumerate(axs.flat): 

80 if i < num_ifgs: 

81 sc = ax.scatter(coord[:, 1], coord[:, 0], c=phase[:, i], 

82 vmin=min_val, vmax=max_val, s=1, cmap=cmap) 

83 ax.axes.set_xticks([]) 

84 ax.axes.set_yticks([]) 

85 if spatial_ref_idx is not None: 

86 ax.plot(coord[spatial_ref_idx, 1], 

87 coord[spatial_ref_idx, 0], 'k*') 

88 elif i == num_ifgs: 

89 plt.colorbar(sc, cax=ax) 

90 else: 

91 ax.set_visible(False) 

92 if ttl is not None: 

93 fig.suptitle(ttl) 

94 

95 

96def plotScatter(*, value: np.ndarray, coord: np.ndarray, bmap_obj: AmplitudeImage = None, ttl: str = None, 

97 unit: str = None, s: float = 5.0, cmap: colormaps = colormaps["jet_r"], symmetric: bool = False, 

98 logger: Logger, **kwargs: Any): 

99 """Plot a scatter map for given value. 

100 

101 Parameters 

102 ---------- 

103 value: np.ndarray 

104 value to be plotted per point giving the colour of the point (dim: no. points x 1) 

105 coord: np.ndarray 

106 coordinates of the points, e.g. radar or lat lon (dim: no. points x 2). If bmapObj is given, 

107 the coordinates must be radar coordinates! 

108 bmap_obj: AmplitudeImage 

109 instance of amplitudeImage for plotting background image (default: None) 

110 ttl: str 

111 title for the figure (default: None) 

112 unit: str 

113 unit as title for the colorbar axis (default: None) 

114 s: float 

115 size of the scatter points (default: 5.0) 

116 cmap: str 

117 colormap (default: "jet_r") 

118 symmetric: bool 

119 plot symmetric colormap extend, i.e. abs(vmin) == abs(vmax) (default: False) 

120 logger: Logger 

121 logging Handler 

122 kwargs: Any 

123 additional keyword arguments for scatter plot 

124 

125 Returns 

126 ------- 

127 fig: plt.Figure 

128 current figure, 

129 ax: plt.Axes 

130 current axis 

131 cb: plt.colorbar 

132 current colorbar 

133 """ 

134 if bmap_obj is not None: 

135 ax = bmap_obj.plot(logger=logger) 

136 fig = plt.gcf() 

137 else: 

138 fig = plt.figure() 

139 ax = fig.add_subplot() 

140 

141 if symmetric: 

142 v_range = np.max(np.abs(value.ravel())) 

143 sc = ax.scatter(coord[:, 1], coord[:, 0], c=value, s=s, cmap=plt.get_cmap(cmap), 

144 vmin=-v_range, vmax=v_range) 

145 else: 

146 sc = ax.scatter(coord[:, 1], coord[:, 0], c=value, s=s, cmap=plt.get_cmap(cmap), **kwargs) 

147 cb = plt.colorbar(sc, ax=ax, pad=0.03, shrink=0.5) 

148 cb.ax.set_title(unit) 

149 ax.set_title(ttl) 

150 plt.tight_layout() 

151 return fig, ax, cb 

152 

153 

154def plotColoredPointNetwork(*, x: np.ndarray, y: np.ndarray, arcs: np.ndarray, val: np.ndarray, ax: plt.Axes = None, 

155 linewidth: float = 2, cmap_name: str = "seismic", clim: tuple = None): 

156 """Plot a network of points with colored arcs. 

157 

158 Parameters 

159 ---------- 

160 x: np.ndarray 

161 x-coordinates of the points (dim: no. points x 1) 

162 y: np.ndarray 

163 y-coordinates of the points (dim: no. points x 1) 

164 arcs: np.ndarray 

165 indices of the points to be connected (dim: no. arcs x 2) 

166 val: np.ndarray 

167 values for the color of the arcs (dim: no. arcs x 1) 

168 ax: plt.Axes 

169 axis for plotting (default: None) 

170 linewidth: float 

171 line width of the arcs (default: 2) 

172 cmap_name: str 

173 name of the colormap (default: "seismic") 

174 clim: tuple 

175 color limits for the colormap (default: None) 

176 

177 Returns 

178 ------- 

179 ax: plt.Axes 

180 current axis 

181 cbar: plt.colorbar 

182 current colorbar 

183 """ 

184 if ax is None: 

185 fig = plt.figure(figsize=[15, 5]) 

186 ax = fig.add_subplot() 

187 else: 

188 fig = ax.get_figure() 

189 ax.scatter(x, y, s=3.5, c=np.ones_like(x)) 

190 

191 if clim is None: 

192 norm = Normalize(vmin=min(val), vmax=max(val)) 

193 else: 

194 norm = Normalize(vmin=clim[0], vmax=clim[1]) 

195 

196 mapper = cm.ScalarMappable(norm=norm, cmap=cm.get_cmap(cmap_name)) 

197 mapper_list = [mapper.to_rgba(v) for v in val] 

198 for m in range(arcs.shape[0]): 

199 x_val = [x[arcs[m, 0]], x[arcs[m, 1]]] 

200 y_val = [y[arcs[m, 0]], y[arcs[m, 1]]] 

201 

202 ax.plot(x_val, y_val, linewidth=linewidth, c=mapper_list[m]) 

203 cbar = fig.colorbar(mapper, ax=ax, pad=0.03, shrink=0.5) 

204 

205 return ax, cbar 

206 

207 

208def plotGridFromBoxList(*, box_list: list, ax: plt.Axes = None, edgecolor: str = "k", linewidth: float = 1): 

209 """Plot a grid into an axis. 

210 

211 Parameters 

212 ---------- 

213 box_list: list 

214 boxes to be plotted. box_list can be created with 'splitImageIntoBoxesRngAz' or 'splitImageIntoBoxes' 

215 ax: plt.Axes 

216 axis for plotting (default: None) 

217 edgecolor: str 

218 edge color for the boxes (default: "k") 

219 linewidth: float 

220 line width for the boxes (default: 1) 

221 

222 Returns 

223 ------- 

224 ax: plt.Axes 

225 current axis 

226 """ 

227 if ax is None: 

228 fig = plt.figure() 

229 ax = fig.add_subplot() 

230 

231 for box in box_list: 

232 rect = patches.Rectangle((box[0], box[1]), box[2] - box[0], box[3] - box[1], linewidth=linewidth, 

233 edgecolor=edgecolor, facecolor="none") 

234 ax.add_patch(rect) 

235 return ax 

236 

237 

238class TimeSeriesViewer: 

239 """TimeSeriesViewer.""" 

240 

241 def __init__(self, *, point_obj: Points, vel_scale: str = "mm", input_path: str, logger: Logger): 

242 """Init.""" 

243 self.sc = None 

244 self.point_obj = point_obj 

245 self.ts_point_marker = None # for ts point marker 

246 self.ts_point_idx = 0 # index of ts_point 

247 self.ts_refpoint_marker = None # for reference point marker 

248 self.logger = logger 

249 self.ts_refpoint_idx = None # index of reference point 

250 self.vel_scale = vel_scale 

251 scale_dict = {"mm": 1000, "cm": 100, "dm": 10, "m": 1} 

252 if self.vel_scale not in scale_dict.keys(): 

253 raise ValueError(f"Invalid argument: '{self.vel_scale}'") 

254 self.scale = scale_dict[self.vel_scale] 

255 self.tree = KDTree(self.point_obj.coord_xy) 

256 if point_obj.ifg_net_obj.dates is not None: 

257 self.times = [datetime.date.fromisoformat(date) for date in point_obj.ifg_net_obj.dates] 

258 else: # backwards compatible, if ifg_net_obj does not contain dates 

259 self.times = point_obj.ifg_net_obj.tbase 

260 

261 vel, demerr, ref_atmo, coherence, omega, v_hat = ut.estimateParameters(obj=self.point_obj, ifg_space=False) 

262 self.vel = vel 

263 self.demerr = demerr 

264 self.ref_atmo = ref_atmo 

265 

266 self.bmap_obj = AmplitudeImage(file_path=os.path.join(os.path.dirname(self.point_obj.file_path), 

267 "background_map.h5")) 

268 self.bmap_obj.open() 

269 self.height = readfile.read(os.path.join(input_path, "geometryRadar.h5"), datasetName='height')[0] 

270 

271 temp_coh_obj = BaseStack( 

272 file=os.path.join(os.path.dirname(self.point_obj.file_path), "temporal_coherence.h5"), 

273 logger=logger) 

274 self.temp_coh_img = temp_coh_obj.read(dataset_name="temp_coh") 

275 

276 self.font_size = 10 

277 plt.rc('font', size=self.font_size) # controls default text size 

278 plt.rc('axes', titlesize=self.font_size) # fontsize of the title 

279 plt.rc('axes', labelsize=self.font_size) # fontsize of the x and y labels 

280 plt.rc('xtick', labelsize=self.font_size) # fontsize of the x tick labels 

281 plt.rc('ytick', labelsize=self.font_size) # fontsize of the y tick labels 

282 plt.rc('legend', fontsize=self.font_size) # fontsize of the legend 

283 

284 self.initFigureMap() 

285 self.initFigureTimeseries() 

286 self.plotMap(val=None) 

287 self.plotPointTimeseries(val=None) # just any point 

288 self.fig1.canvas.mpl_connect('button_press_event', self.onClick) 

289 plt.show() 

290 

291 def initFigureMap(self): 

292 """InitFigureMap.""" 

293 self.fig1 = plt.figure() 

294 self.ax_img = self.fig1.subplots(1, 1) 

295 

296 self.ax_cb = self.fig1.add_axes([0.93, 0.6, 0.015, 0.15]) # (left, bottom, width, height) 

297 self.cb = self.fig1.colorbar(self.sc, 

298 cax=self.ax_cb, 

299 ax=self.ax_img, 

300 pad=0.03, 

301 shrink=0.8, 

302 aspect=10, 

303 orientation='vertical') 

304 

305 # add button to select reference point 

306 self.set_reference_point = False 

307 self.ax_button = self.fig1.add_axes([0.125, 0.9, 0.1, 0.08]) # (left, bottom, width, height) 

308 self.button_mask = widgets.Button(ax=self.ax_button, label='Select\nReference', image=None, color='1') 

309 self.button_mask.on_clicked(self.updateButtonStatus) 

310 

311 # add radiobutton to select parameter 

312 self.ax_radio_par = self.fig1.add_axes([0.225, 0.9, 0.2, 0.08]) # (left, bottom, width, height) 

313 self.rb_par = widgets.RadioButtons(self.ax_radio_par, labels=['Velocity', 'DEM error', 'None'], active=0) 

314 self.rb_par.on_clicked(self.plotMap) 

315 

316 # add radiobutton to select background image 

317 self.ax_radio_backgr = self.fig1.add_axes([0.425, 0.9, 0.2, 0.08]) # (left, bottom, width, height) 

318 self.rb_backgr = widgets.RadioButtons(self.ax_radio_backgr, labels=['Amplitude', 'DEM', 'Coherence', 'None'], 

319 active=0) 

320 self.rb_backgr.on_clicked(self.plotMap) 

321 

322 # add info box with info about velocity and DEM error of selected pixel 

323 self.ax_info_box = self.fig1.add_axes([0.625, 0.9, 0.2, 0.08]) # (left, bottom, width, height) 

324 self.text_obj_time = self.ax_info_box.text(0.1, 0.1, "") 

325 self.ax_info_box.set_xticks([], []) 

326 self.ax_info_box.set_yticks([], []) 

327 

328 # add variable for axis of slider controlling the visualized coherence background image 

329 self.ax_slide_coh = None 

330 self.sl_last_val = 0.0 

331 self.sl_coh = None 

332 

333 def initFigureTimeseries(self): 

334 """InitFigureTimeseries.""" 

335 self.fig2 = plt.figure(figsize=(15, 5)) 

336 self.ax_ts = self.fig2.subplots(1, 1) 

337 

338 # add radiobutton for fitting linear model 

339 self.ax_radio_fit = self.fig2.add_axes([0.125, 0.9, 0.2, 0.08]) # (left, bottom, width, height) 

340 self.rb_fit = widgets.RadioButtons(self.ax_radio_fit, labels=['None', 'Linear fit'], active=0) 

341 

342 # add radiobutton for selecting baseline type 

343 self.ax_radio_baselines = self.fig2.add_axes([0.325, 0.9, 0.2, 0.08]) # (left, bottom, width, height) 

344 self.rb_baselines = widgets.RadioButtons( 

345 self.ax_radio_baselines, 

346 labels=['Temporal baseline', 'Perpendicular baseline'], 

347 active=0 

348 ) 

349 

350 # add check box for removing phase due to parameters 

351 self.ax_cbox_par = self.fig2.add_axes([0.525, 0.9, 0.2, 0.08]) # (left, bottom, width, height) 

352 self.cbox_par = widgets.CheckButtons( 

353 self.ax_cbox_par, 

354 ["Velocity", "DEM error"], 

355 actives=[True, False] 

356 ) 

357 self.rb_fit.on_clicked(self.plotPointTimeseries) 

358 self.rb_baselines.on_clicked(self.plotPointTimeseries) 

359 self.cbox_par.on_clicked(self.plotPointTimeseries) 

360 

361 def plotMap(self, val: object): # val seems to be unused, but its necessary for the function to work. 

362 """Plot velocity map and time series.""" 

363 flag_initial_plot = (0.0, 1.0) == self.ax_img.get_xlim() 

364 ax_img_xlim = None 

365 ax_img_ylim = None 

366 if not flag_initial_plot: 

367 ax_img_xlim = self.ax_img.get_xlim() 

368 ax_img_ylim = self.ax_img.get_ylim() 

369 

370 self.ax_img.cla() 

371 

372 # get selected background from radiobutton 

373 if self.rb_backgr.value_selected == "Amplitude": 

374 self.ax_img = self.bmap_obj.plot(ax=self.ax_img, logger=self.logger) 

375 if self.ax_slide_coh is not None: 

376 self.sl_last_val = self.sl_coh.val 

377 self.ax_slide_coh.remove() 

378 self.ax_slide_coh = None 

379 if self.rb_backgr.value_selected == "DEM": 

380 self.ax_img.imshow(self.height, cmap=ColormapExt('DEM_print').colormap) 

381 meta = {"ORBIT_DIRECTION": self.bmap_obj.orbit_direction} 

382 auto_flip_direction(meta, ax=self.ax_img, print_msg=False) 

383 self.ax_img.set_xlabel("Range") 

384 self.ax_img.set_ylabel("Azimuth") 

385 if self.ax_slide_coh is not None: 

386 self.sl_last_val = self.sl_coh.val 

387 self.ax_slide_coh.remove() 

388 self.ax_slide_coh = None 

389 if self.rb_backgr.value_selected == "Coherence": 

390 if self.ax_slide_coh is None: 

391 # add slider to change value of coherence for background map 

392 self.ax_slide_coh = self.fig1.add_axes([0.425, 0.85, 0.2, 0.03]) # (left, bottom, width, height) 

393 self.sl_coh = widgets.Slider(self.ax_slide_coh, 

394 label='Coherence', 

395 valmin=0.0, 

396 valmax=1.0, 

397 valinit=self.sl_last_val, 

398 valfmt="%.1f") 

399 

400 self.ax_img.imshow(self.temp_coh_img, 

401 cmap=plt.get_cmap("gray"), 

402 vmin=np.round(self.sl_coh.val, decimals=1), 

403 vmax=1) 

404 meta = {"ORBIT_DIRECTION": self.bmap_obj.orbit_direction} 

405 auto_flip_direction(meta, ax=self.ax_img, print_msg=False) 

406 self.ax_img.set_xlabel("Range") 

407 self.ax_img.set_ylabel("Azimuth") 

408 if self.rb_backgr.value_selected == "None": 

409 self.ax_img.imshow(np.ones_like(self.height, dtype=np.int8), cmap=plt.cm.get_cmap("gray"), vmin=0, vmax=1) 

410 meta = {"ORBIT_DIRECTION": self.bmap_obj.orbit_direction} 

411 auto_flip_direction(meta, ax=self.ax_img, print_msg=False) 

412 self.ax_img.set_xlabel("Range") 

413 self.ax_img.set_ylabel("Azimuth") 

414 if self.ax_slide_coh is not None: 

415 self.sl_last_val = self.sl_coh.val 

416 self.ax_slide_coh.remove() 

417 self.ax_slide_coh = None 

418 

419 par = None 

420 v_range = None 

421 cb_ttl = "" 

422 if self.rb_par.value_selected == "Velocity": # show velocity 

423 v_range = np.max(np.abs(self.vel * self.scale)) 

424 par = self.vel * self.scale 

425 cb_ttl = f"[{self.vel_scale}/\nyear]" 

426 elif self.rb_par.value_selected == "DEM error": # show demerr 

427 v_range = np.max(np.abs(self.demerr)) 

428 par = self.demerr 

429 cb_ttl = "[m]" 

430 

431 if self.rb_par.value_selected != "None": 

432 self.sc = self.ax_img.scatter(self.point_obj.coord_xy[:, 1], 

433 self.point_obj.coord_xy[:, 0], 

434 c=par, 

435 s=5, 

436 cmap=colormaps["jet_r"], 

437 vmin=-v_range, 

438 vmax=v_range) 

439 

440 self.cb.ax.set_title(cb_ttl, fontsize=self.font_size) 

441 self.cb = self.fig1.colorbar(self.sc, cax=self.ax_cb, ax=self.ax_img, pad=0.03, shrink=0.8, aspect=10, 

442 orientation='vertical') 

443 

444 # add back location of selected sarvey point and current reference 

445 if self.ts_refpoint_idx is not None: # initial value is None 

446 y, x = self.point_obj.coord_xy[self.ts_refpoint_idx, :] 

447 self.ts_refpoint_marker = self.ax_img.scatter(x, y, marker='^', facecolors='none', edgecolors='k') 

448 

449 y, x = self.point_obj.coord_xy[self.ts_point_idx, :] 

450 self.ts_point_marker = self.ax_img.scatter(x, y, facecolors='none', edgecolors='k') 

451 

452 if not flag_initial_plot: 

453 self.ax_img.set_xlim(ax_img_xlim) 

454 self.ax_img.set_ylim(ax_img_ylim) 

455 

456 plt.draw() 

457 

458 def updateButtonStatus(self, val: object): # val seems to be unused, but its necessary for the function to work. 

459 """Set to true.""" 

460 if self.set_reference_point: 

461 self.set_reference_point = False 

462 self.button_mask.color = '1' 

463 else: 

464 self.set_reference_point = True 

465 self.button_mask.color = '0.5' 

466 

467 def onClick(self, event): 

468 """Event function to get y/x from button press.""" 

469 if event.inaxes is None: 

470 return 

471 

472 if not plt.fignum_exists(self.fig2.number): 

473 self.initFigureTimeseries() 

474 plt.show() 

475 

476 if event.button is MouseButton.RIGHT: 

477 if event.inaxes == self.ax_img: 

478 y, x = int(event.ydata + 0.5), int(event.xdata + 0.5) 

479 idx = self.tree.query([y, x])[-1] 

480 y, x = self.point_obj.coord_xy[idx, :] 

481 

482 if self.set_reference_point: # update reference point 

483 self.ts_refpoint_idx = idx 

484 self.updateReference() 

485 self.updateButtonStatus(val=None) 

486 # if self.ts_refpoint_marker is not None: # initial value is None 

487 # self.ts_refpoint_marker.remove() 

488 # self.ts_refpoint_marker = self.ax_img.scatter(x, y, marker='^', facecolors='none', edgecolors='k') 

489 else: 

490 self.ts_point_idx = idx 

491 

492 if self.ts_point_marker is not None: # initial value is None 

493 self.ts_point_marker.remove() 

494 y, x = self.point_obj.coord_xy[self.ts_point_idx, :] 

495 self.ts_point_marker = self.ax_img.scatter(x, y, facecolors='none', edgecolors='k') 

496 self.plotPointTimeseries(val=None) 

497 return 

498 

499 def updateReference(self): 

500 """Change the phase of all points according to the new reference point. 

501 

502 Update the plot of the velocity and time series. 

503 """ 

504 self.logger.info(msg="changed reference to ID: {}".format(self.point_obj.point_id[self.ts_refpoint_idx])) 

505 self.point_obj.phase -= self.point_obj.phase[self.ts_refpoint_idx, :] 

506 vel, demerr, ref_atmo, coherence, omega, v_hat = ut.estimateParameters(obj=self.point_obj, ifg_space=False) 

507 self.vel = vel 

508 self.demerr = demerr 

509 self.ref_atmo = ref_atmo 

510 self.plotMap(val=None) 

511 

512 def plotPointTimeseries(self, val: object): # val seems to be unused, but its necessary for the function to work. 

513 """Plot_point_timeseries.""" 

514 self.ax_ts.cla() 

515 

516 # transform phase time series into meters 

517 resulting_ts = self.point_obj.wavelength / (4 * np.pi) * self.point_obj.phase[self.ts_point_idx, :] 

518 cbox_status = self.cbox_par.get_status() 

519 if not cbox_status[0]: # Displacement 

520 resulting_ts = resulting_ts - self.point_obj.ifg_net_obj.tbase * self.vel[self.ts_point_idx] 

521 if not cbox_status[1]: # DEM error 

522 phase_topo = (self.point_obj.ifg_net_obj.pbase / (self.point_obj.slant_range[self.ts_point_idx] * 

523 np.sin(self.point_obj.loc_inc[self.ts_point_idx])) * 

524 self.demerr[self.ts_point_idx]) 

525 resulting_ts = resulting_ts - phase_topo 

526 

527 self.ax_ts.set_ylabel(f"Displacement [{self.vel_scale}]") 

528 

529 # add trend 

530 if self.rb_fit.value_selected == "Linear fit": 

531 if self.rb_baselines.value_selected == "Temporal baseline": 

532 line = self.point_obj.ifg_net_obj.tbase * self.vel[self.ts_point_idx] + self.ref_atmo[self.ts_point_idx] 

533 self.ax_ts.plot(self.times, line * self.scale, '-k') 

534 elif self.rb_baselines.value_selected == "Perpendicular baseline": 

535 line = (self.point_obj.ifg_net_obj.pbase / (self.point_obj.slant_range[self.ts_point_idx] * 

536 np.sin(self.point_obj.loc_inc[self.ts_point_idx])) * 

537 self.demerr[self.ts_point_idx] + self.ref_atmo[self.ts_point_idx]) 

538 self.ax_ts.plot(self.point_obj.ifg_net_obj.pbase, line * self.scale, '-k') 

539 

540 # set y-lim to [-20, 20] mm except if it exceeds this scale 

541 y_max = max([0.02, resulting_ts.max() + 0.005]) 

542 y_min = min([-0.02, resulting_ts.min() - 0.005]) 

543 

544 self.ax_ts.set_ylim(y_min * self.scale, y_max * self.scale) 

545 if self.rb_baselines.value_selected == "Temporal baseline": 

546 self.ax_ts.plot(self.times, resulting_ts * self.scale, '.') 

547 self.ax_ts.set_xlabel("Time [years]") 

548 if self.rb_baselines.value_selected == "Perpendicular baseline": 

549 self.ax_ts.plot(self.point_obj.ifg_net_obj.pbase, resulting_ts * self.scale, '.') 

550 self.ax_ts.set_xlabel("Perpendicular Baseline [m]") 

551 

552 self.text_obj_time.remove() 

553 point_info = "DEM error: {:.0f} m\nVelocity: {:.0f} {:s}/year".format( 

554 self.demerr[self.ts_point_idx], 

555 self.vel[self.ts_point_idx] * self.scale, 

556 self.vel_scale, 

557 ) 

558 self.text_obj_time = self.ax_info_box.text(0.5, 0.5, point_info, ha='center', va='center') 

559 

560 # update figure 

561 self.fig1.canvas.draw() 

562 self.fig2.canvas.draw()