Coverage for sarvey/viewer.py: 21%
286 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-17 12:36 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-17 12:36 +0000
1#!/usr/bin/env python
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/>.
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
44from mintpy.objects.colors import ColormapExt
45from mintpy.utils import readfile
46from mintpy.utils.plot import auto_flip_direction
48from sarvey.objects import AmplitudeImage, Points, BaseStack
49import sarvey.utils as ut
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.
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)
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)
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.
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
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()
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
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.
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)
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))
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])
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]]]
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)
205 return ax, cbar
208def plotGridFromBoxList(*, box_list: list, ax: plt.Axes = None, edgecolor: str = "k", linewidth: float = 1):
209 """Plot a grid into an axis.
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)
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()
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
238class TimeSeriesViewer:
239 """TimeSeriesViewer."""
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
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
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]
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")
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
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()
291 def initFigureMap(self):
292 """InitFigureMap."""
293 self.fig1 = plt.figure()
294 self.ax_img = self.fig1.subplots(1, 1)
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')
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)
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)
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)
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([], [])
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
333 def initFigureTimeseries(self):
334 """InitFigureTimeseries."""
335 self.fig2 = plt.figure(figsize=(15, 5))
336 self.ax_ts = self.fig2.subplots(1, 1)
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)
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 )
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)
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()
370 self.ax_img.cla()
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")
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
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]"
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)
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')
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')
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')
452 if not flag_initial_plot:
453 self.ax_img.set_xlim(ax_img_xlim)
454 self.ax_img.set_ylim(ax_img_ylim)
456 plt.draw()
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'
467 def onClick(self, event):
468 """Event function to get y/x from button press."""
469 if event.inaxes is None:
470 return
472 if not plt.fignum_exists(self.fig2.number):
473 self.initFigureTimeseries()
474 plt.show()
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, :]
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
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
499 def updateReference(self):
500 """Change the phase of all points according to the new reference point.
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)
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()
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
527 self.ax_ts.set_ylabel(f"Displacement [{self.vel_scale}]")
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')
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])
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]")
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')
560 # update figure
561 self.fig1.canvas.draw()
562 self.fig2.canvas.draw()