Coverage for sarvey/ifg_network.py: 70%
171 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"""IfgNetwork module for SARvey."""
31import datetime
32import h5py
33import os
34import matplotlib.pyplot as plt
35import numpy as np
36from typing import Union
37import warnings
38from logging import Logger
39from scipy.spatial import Delaunay
42class IfgNetwork:
43 """Abstract class/interface for different types of interferogram networks."""
45 ifg_list: Union[list, np.ndarray] = None
47 def __init__(self):
48 """Init."""
49 self.pbase = None
50 self.tbase = None
51 self.num_images = None
52 self.ifg_list = list() # is later converted to np.array
53 self.pbase_ifg = None
54 self.tbase_ifg = None
55 self.num_ifgs = None
56 self.dates = list()
58 def plot(self):
59 """Plot the network of interferograms."""
60 fig = plt.figure(figsize=(15, 5))
61 axs = fig.subplots(1, 3)
62 dt = [datetime.date.fromisoformat(d) for d in self.dates]
63 axs[0].plot(dt, self.pbase, 'ko')
64 for idx in self.ifg_list:
65 xx = np.array([dt[idx[0]], dt[idx[1]]])
66 yy = np.array([self.pbase[idx[0]], self.pbase[idx[1]]])
67 axs[0].plot(xx, yy, 'k-')
68 axs[0].set_ylabel('perpendicular baseline [m]')
69 axs[0].set_xlabel('temporal baseline [years]')
70 axs[0].set_title('Network of interferograms')
71 fig.autofmt_xdate()
73 axs[1].hist(self.tbase_ifg * 365.25, bins=100)
74 axs[1].set_ylabel('Absolute frequency')
75 axs[1].set_xlabel('temporal baseline [days]')
77 axs[2].hist(self.pbase_ifg, bins=100)
78 axs[2].set_ylabel('Absolute frequency')
79 axs[2].set_xlabel('perpendicular baseline [m]')
80 return fig
82 def getDesignMatrix(self):
83 """Compute the design matrix for the smallbaseline network."""
84 a = np.zeros((self.num_ifgs, self.num_images))
85 for i in range(len(self.ifg_list)):
86 a[i, self.ifg_list[i][0]] = 1
87 a[i, self.ifg_list[i][1]] = -1
88 return a
90 def open(self, *, path: str):
91 """Read stored information from already existing.h5 file.
93 Parameter
94 -----------
95 path: str
96 path to existing file to read from.
97 """
98 with h5py.File(path, 'r') as f:
99 self.num_images = f.attrs["num_images"]
100 self.num_ifgs = f.attrs["num_ifgs"]
102 self.tbase_ifg = f['tbase_ifg'][:]
103 self.pbase_ifg = f['pbase_ifg'][:]
104 self.tbase = f['tbase'][:]
105 self.pbase = f['pbase'][:]
106 self.ifg_list = f['ifg_list'][:]
107 try:
108 self.dates = f['dates'][:]
109 self.dates = [date.decode("utf-8") for date in self.dates]
110 except KeyError as ke:
111 self.dates = None
112 print(f"IfgNetwork is in old dataformat. Cannot read 'dates'! {ke}")
114 f.close()
116 def writeToFile(self, *, path: str, logger: Logger):
117 """Write all existing data to .h5 file.
119 Parameters
120 ----------
121 path: str
122 path to filename
123 logger: Logger
124 Logging handler.
125 """
126 logger.info(msg="write IfgNetwork to {}".format(path))
128 if os.path.exists(path):
129 os.remove(path)
131 dates = np.array(self.dates, dtype=np.string_)
133 with h5py.File(path, 'w') as f:
134 f.attrs["num_images"] = self.num_images
135 f.attrs["num_ifgs"] = self.num_ifgs
137 f.create_dataset('tbase_ifg', data=self.tbase_ifg)
138 f.create_dataset('pbase_ifg', data=self.pbase_ifg)
139 f.create_dataset('tbase', data=self.tbase)
140 f.create_dataset('pbase', data=self.pbase)
141 f.create_dataset('ifg_list', data=self.ifg_list)
142 f.create_dataset('dates', data=dates)
145class StarNetwork(IfgNetwork):
146 """Star network of interferograms (single-reference)."""
148 def configure(self, *, pbase: np.ndarray, tbase: np.ndarray, ref_idx: int, dates: list):
149 """Create list of interferograms containing the indices of the images and computes baselines.
151 Parameter
152 ---------
153 pbase: np.ndarray
154 Perpendicular baselines of the SAR acquisitions.
155 tbase: np.ndarray
156 Temporal baselines of the SAR acquisitions.
157 ref_idx: int
158 Index of the reference image.
159 dates: list
160 Dates of the acquisitions.
161 """
162 self.pbase = pbase
163 self.tbase = tbase / 365.25
164 self.num_images = pbase.shape[0]
165 self.dates = dates
167 for i in range(self.num_images):
168 if i == ref_idx:
169 continue
170 self.ifg_list.append((ref_idx, i))
172 self.pbase_ifg = np.delete(self.pbase - self.pbase[ref_idx], ref_idx)
173 self.tbase_ifg = np.delete(self.tbase - self.tbase[ref_idx], ref_idx)
174 self.num_ifgs = self.num_images - 1
177class SmallTemporalBaselinesNetwork(IfgNetwork):
178 """Small temporal baselines network of interferograms without restrictions on the perpendicular baselines."""
180 def configure(self, *, pbase: np.ndarray, tbase: np.ndarray, num_link: int = None, dates: list):
181 """Create list of interferograms containing the indices of the images and computes baselines.
183 Parameter
184 -----------
185 pbase: np.ndarray
186 Perpendicular baselines of the SAR acquisitions.
187 tbase: np.ndarray
188 Temporal baselines of the SAR acquisitions.
189 num_link: int
190 Number of consecutive links in time connecting acquisitions.
191 dates: list
192 Dates of the acquisitions.
193 """
194 self.pbase = pbase
195 self.tbase = tbase / 365.25
196 self.num_images = pbase.shape[0]
197 self.dates = dates
199 for i in range(self.num_images):
200 for j in range(num_link):
201 if i + j + 1 >= self.num_images:
202 continue
203 self.ifg_list.append((i, i + j + 1))
205 self.ifg_list = [(i, j) for i, j in self.ifg_list if i != j] # remove connections to itself, e.g. (0, 0)
207 self.pbase_ifg = np.array([self.pbase[idx[1]] - self.pbase[idx[0]] for idx in self.ifg_list])
208 self.tbase_ifg = np.array([self.tbase[idx[1]] - self.tbase[idx[0]] for idx in self.ifg_list])
209 self.num_ifgs = self.pbase_ifg.shape[0]
212class SmallBaselineNetwork(IfgNetwork):
213 """Small baseline network of interferograms restricting both temporal and spatial baselines."""
215 def configure(self, *, pbase: np.ndarray, tbase: np.ndarray, num_link: int, max_tbase: int, dates: list):
216 """Create list of interferograms containing the indices of the images and computes baselines.
218 Parameter
219 -----------
220 pbase: np.ndarray
221 perpendicular baselines of the SAR acquisitions.
222 tbase: np.ndarray
223 temporal baselines of the SAR acquisitions.
224 max_tbase: int
225 maximum temporal baseline in [days] (default: None).
226 num_link: int
227 number of links within the range of maximum temporal baseline.
228 dates: list
229 Dates of the acquisitions.
230 """
231 self.pbase = pbase
232 self.tbase = tbase / 365.25
233 self.num_images = pbase.shape[0]
234 self.dates = dates
235 flag_restrict_to_max_tbase = False
237 # in this section use tbase in [days] (function argument, not self.)
238 for i in range(self.num_images - 1):
240 if i + 1 < self.num_images - 1:
241 # always use one connection to nearest neighbour in time
242 self.ifg_list.append((i, i + 1))
243 else:
244 self.ifg_list.append((i, i + 1))
245 break
246 # compute index corresponding to max_tbase for current time
247 diff = np.abs(tbase - (tbase[i] + max_tbase))
248 max_idx = np.where(diff == diff.min())[0][0]
249 self.ifg_list.append((i, max_idx))
251 if max_idx == i: # no further images between i and max_idx
252 flag_restrict_to_max_tbase = True
253 continue
255 # spread the rest of the links over the remaining time steps in between
256 links = np.floor(np.arange(i, max_idx, (max_idx - i) / (num_link - 1)))[1:].astype(int)
257 for link in links:
258 self.ifg_list.append((i, link))
259 self.ifg_list = np.unique(self.ifg_list, axis=0)
261 if flag_restrict_to_max_tbase:
262 warnings.warn(f"Cannot restrict ifgs to maximum temporal baseline of {max_tbase} days.")
264 self.ifg_list = [(i, j) for i, j in self.ifg_list if i != j] # remove connections to itself, e.g. (0, 0)
266 self.pbase_ifg = np.array([self.pbase[idx[1]] - self.pbase[idx[0]] for idx in self.ifg_list])
267 self.tbase_ifg = np.array([self.tbase[idx[1]] - self.tbase[idx[0]] for idx in self.ifg_list])
268 self.num_ifgs = self.pbase_ifg.shape[0]
271class DelaunayNetwork(IfgNetwork):
272 """Delaunay network of interferograms which restricts both the temporal and perpendicular baselines."""
274 def configure(self, *, pbase: np.ndarray, tbase: np.ndarray, dates: list):
275 """Create list of interferograms containing the indices of the images and computes baselines.
277 Parameter
278 -----------
279 pbase: np.ndarray
280 perpendicular baselines of the SAR acquisitions, array
281 tbase: np.ndarray
282 temporal baselines of the SAR acquisitions, array
283 dates: list
284 Dates of the acquisitions, list.
285 """
286 self.pbase = pbase
287 self.tbase = tbase / 365.25
288 self.num_images = pbase.shape[0]
289 self.dates = dates
290 scale = 0.25
292 network = Delaunay(points=np.stack([self.pbase, self.tbase * 365.25 * scale]).T)
293 for p1, p2, p3 in network.simplices:
294 self.ifg_list.append((p1, p2))
295 self.ifg_list.append((p1, p3))
296 self.ifg_list.append((p2, p3))
297 self.ifg_list = np.unique(self.ifg_list, axis=0)
299 self.pbase_ifg = np.array([self.pbase[idx[1]] - self.pbase[idx[0]] for idx in self.ifg_list])
300 self.tbase_ifg = np.array([self.tbase[idx[1]] - self.tbase[idx[0]] for idx in self.ifg_list])
301 self.num_ifgs = self.pbase_ifg.shape[0]
304class SmallBaselineYearlyNetwork(IfgNetwork):
305 """Small baseline network of interferograms with yearly connections."""
307 def configure(self, *, pbase: np.ndarray, tbase: np.ndarray, num_link: int = None, dates: list):
308 """Create list of interferograms containing the indices of the images and computes baselines.
310 Parameter
311 -----------
312 pbase: np.ndarray
313 perpendicular baselines of the SAR acquisitions, array
314 tbase: np.ndarray
315 temporal baselines of the SAR acquisitions, array
316 num_link: int
317 Number of consecutive links in time connecting acquisitions.
318 dates: list
319 Dates of the acquisitions, list.
320 """
321 self.pbase = pbase
322 self.tbase = tbase / 365.25
323 self.num_images = pbase.shape[0]
324 self.dates = dates
326 # add small temporal baselines
327 for i in range(self.num_images):
328 for j in range(num_link):
329 if i + j + 1 >= self.num_images:
330 continue
331 self.ifg_list.append((i, i + j + 1))
333 # add yearly ifgs
334 for i in range(self.num_images):
335 # find index of image at roughly one year distance
336 diff = np.abs(tbase - (tbase[i] + 365.25))
337 year_idx = np.where(diff == diff.min())[0][0]
338 print(year_idx)
339 if year_idx != self.num_images - 1: # avoid connections to the last image
340 self.ifg_list.append((i, year_idx))
341 print("found!")
343 self.ifg_list = np.unique(self.ifg_list, axis=0)
344 self.ifg_list = [(i, j) for i, j in self.ifg_list if i != j] # remove connections to itself, e.g. (0, 0)
346 self.pbase_ifg = np.array([self.pbase[idx[1]] - self.pbase[idx[0]] for idx in self.ifg_list])
347 self.tbase_ifg = np.array([self.tbase[idx[1]] - self.tbase[idx[0]] for idx in self.ifg_list])
348 self.num_ifgs = self.pbase_ifg.shape[0]