Coverage for sarvey/objects.py: 88%

363 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"""Objects module for SARvey.""" 

31import os 

32from os.path import join, dirname, exists, basename 

33from typing import Optional 

34import h5py 

35import matplotlib.pyplot as plt 

36import numpy as np 

37from pyproj import Proj, CRS 

38from pyproj.aoi import AreaOfInterest 

39from pyproj.database import query_utm_crs_info 

40from logging import Logger 

41 

42from miaplpy.objects.slcStack import slcStack 

43from mintpy.utils import readfile 

44from mintpy.utils.plot import auto_flip_direction 

45 

46from sarvey.ifg_network import IfgNetwork 

47 

48 

49class AmplitudeImage: 

50 """AmplitudeImage.""" 

51 

52 def __init__(self, *, file_path: str): 

53 """Init. 

54 

55 Parameters 

56 ---------- 

57 file_path: str 

58 path to filename 

59 """ 

60 self.width = None 

61 self.length = None 

62 self.file_path = file_path 

63 self.background_map = None 

64 self.orbit_direction = None 

65 

66 def prepare(self, *, slc_stack_obj: slcStack, img: np.ndarray, logger: Logger): 

67 """Read the SLC stack, compute the mean amplitude image and store it into a file. 

68 

69 Parameters 

70 ---------- 

71 slc_stack_obj: slcStack 

72 object of class slcStack from MiaplPy 

73 img: np.ndarray 

74 amplitude image, e.g. the mean over time 

75 logger: Logger 

76 Logging handler 

77 """ 

78 self.orbit_direction = slc_stack_obj.metadata["ORBIT_DIRECTION"] 

79 self.length = slc_stack_obj.length 

80 self.width = slc_stack_obj.width 

81 

82 self.background_map = img 

83 

84 logger.info(msg="write data to {}...".format(self.file_path)) 

85 

86 if exists(self.file_path): 

87 os.remove(self.file_path) 

88 

89 with h5py.File(self.file_path, 'w') as f: 

90 f.create_dataset('background_map', data=self.background_map) 

91 f.attrs["ORBIT_DIRECTION"] = self.orbit_direction 

92 f.attrs["LENGTH"] = self.length 

93 f.attrs["WIDTH"] = self.width 

94 

95 def open(self): 

96 """Open.""" 

97 # print("read from {}".format(self.file_path)) 

98 

99 with h5py.File(self.file_path, 'r') as f: 

100 self.background_map = f["background_map"][:] 

101 self.orbit_direction = f.attrs["ORBIT_DIRECTION"] 

102 self.length = f.attrs["LENGTH"] 

103 self.width = f.attrs["WIDTH"] 

104 

105 def plot(self, *, ax: plt.Axes = None, logger: Logger): 

106 """Plot the mean amplitude image as a background map. 

107 

108 Parameters 

109 ---------- 

110 ax: plt.Axes 

111 axes for plotting (default: None, a new figure will be created). 

112 logger: Logger 

113 Logging handler. 

114 

115 Return 

116 ------ 

117 ax: plt.Axes 

118 axes object. 

119 """ 

120 if self.background_map is None: 

121 try: 

122 self.open() 

123 except OSError as e: 

124 logger.error(msg="Could not open file: {}".format(e)) 

125 fig = plt.figure(figsize=(15, 5)) 

126 ax = fig.add_subplot() 

127 logger.error(msg="Orbit direction not available.") 

128 return ax 

129 

130 if ax is None: 

131 fig = plt.figure(figsize=(15, 5)) 

132 ax = fig.add_subplot() 

133 ax.imshow(self.background_map, cmap=plt.cm.get_cmap("gray")) 

134 meta = {"ORBIT_DIRECTION": self.orbit_direction} 

135 auto_flip_direction(meta, ax=ax, print_msg=False) 

136 

137 ax.set_xlabel("Range") 

138 ax.set_ylabel("Azimuth") 

139 

140 return ax 

141 

142 

143class CoordinatesUTM: 

144 """Coordinates in UTM for all pixels in the radar image.""" 

145 

146 def __init__(self, *, file_path: str, logger: Logger): 

147 """Init. 

148 

149 Parameters 

150 ---------- 

151 file_path: str 

152 path to filename 

153 logger: Logger 

154 Logging handler. 

155 """ 

156 self.file_path = file_path 

157 self.coord_utm = None 

158 self.logger = logger 

159 

160 def prepare(self, *, input_path: str): 

161 """Read the slc stack, computes the mean amplitude image and stores it into a file. 

162 

163 Parameters 

164 ---------- 

165 input_path: str 

166 path to slcStack.h5 file. 

167 """ 

168 log = self.logger 

169 lat = readfile.read(input_path, datasetName='latitude')[0] 

170 lon = readfile.read(input_path, datasetName='longitude')[0] 

171 

172 log.info(msg="Transform coordinates from latitude and longitude (WGS84) to North and East (UTM).") 

173 # noinspection PyTypeChecker 

174 utm_crs_list = query_utm_crs_info( 

175 datum_name="WGS 84", 

176 area_of_interest=AreaOfInterest( 

177 west_lon_degree=np.nanmin(lon.ravel()), 

178 south_lat_degree=np.nanmin(lat.ravel()), 

179 east_lon_degree=np.nanmax(lon.ravel()), 

180 north_lat_degree=np.nanmax(lat.ravel())), 

181 contains=True) 

182 utm_crs = CRS.from_epsg(utm_crs_list[0].code) 

183 lola2utm = Proj(utm_crs) 

184 self.coord_utm = np.array(lola2utm(lon, lat)) 

185 

186 log.info(msg="write data to {}...".format(self.file_path)) 

187 

188 if exists(self.file_path): 

189 os.remove(self.file_path) 

190 

191 with h5py.File(self.file_path, 'w') as f: 

192 f.create_dataset('coord_utm', data=self.coord_utm) 

193 

194 def open(self): 

195 """Open.""" 

196 with h5py.File(self.file_path, 'r') as f: 

197 self.coord_utm = f["coord_utm"][:] 

198 

199 

200class BaseStack: 

201 """Class for 3D image-like data stacks.""" 

202 

203 def __init__(self, *, file: str = None, logger: Logger): 

204 """Init. 

205 

206 Parameters 

207 ---------- 

208 file: str 

209 path to filename 

210 logger: Logger 

211 Logging handler. 

212 """ 

213 self.file = file 

214 self.logger = logger 

215 self.metadata = None 

216 self.num_time = None 

217 self.length = None 

218 self.width = None 

219 self.f = None 

220 

221 def close(self, *, print_msg: bool = True): 

222 """Close.""" 

223 try: 

224 self.f.close() 

225 if print_msg: 

226 self.logger.info(msg='close file: {}'.format(basename(self.file))) 

227 except Exception as e: 

228 self.logger.exception(msg=e) 

229 pass 

230 return None 

231 

232 def getShape(self, *, dataset_name: str): 

233 """Open file and read shape of dataset.""" 

234 with h5py.File(self.file, 'r') as f: 

235 dshape = f[dataset_name].shape 

236 return dshape 

237 

238 def read(self, *, dataset_name: str, box: Optional[tuple] = None, print_msg: bool = True): 

239 """Read dataset from slc file. 

240 

241 Parameters 

242 ---------- 

243 dataset_name: str 

244 name of dataset 

245 box: tuple 

246 tuple of 4 int, indicating x0,y0,x1,y1 of range, or 

247 tuple of 6 int, indicating x0,y0,z0,x1,y1,z1 of range 

248 print_msg: bool 

249 print message. 

250 

251 Returns 

252 ------- 

253 data: np.ndarray 

254 2D or 3D dataset 

255 """ 

256 if print_msg: 

257 self.logger.info(msg='reading box {} from file: {} ...'.format(box, self.file)) 

258 

259 with h5py.File(self.file, 'r') as f: 

260 self.metadata = dict(f.attrs) 

261 

262 ds = f[dataset_name] 

263 if len(ds.shape) == 3: 

264 self.length, self.width, self.num_time = ds.shape 

265 else: 

266 self.length, self.width = ds.shape 

267 

268 # Get Index in space/2_3 dimension 

269 if box is None: 

270 box = [0, 0, self.width, self.length] 

271 

272 if len(ds.shape) == 3: 

273 if len(box) == 4: 

274 data = ds[box[1]:box[3], box[0]:box[2], :] 

275 if len(box) == 6: 

276 data = ds[box[1]:box[4], box[0]:box[3], box[2]:box[5]] 

277 else: 

278 if len(box) == 6: 

279 raise IndexError("Cannot read 3D box from 2D data.") 

280 data = ds[box[1]:box[3], box[0]:box[2]] 

281 

282 for key, value in self.metadata.items(): 

283 try: 

284 self.metadata[key] = value.decode('utf8') 

285 except Exception: 

286 self.metadata[key] = value 

287 return data 

288 

289 def prepareDataset(self, dataset_name: str, dshape: tuple, dtype: object, 

290 metadata: Optional[dict], mode: str = "w", chunks: [tuple, bool] = True): 

291 """PrepareDataset. Creates a dataset in file with specified size without writing any data. 

292 

293 Parameters 

294 ---------- 

295 dataset_name: str 

296 name of dataset. 

297 dshape: tuple 

298 shape of dataset. 

299 dtype: object 

300 data type of dataset. 

301 metadata: dict 

302 metadata of dataset (e.g. WAVELENGTH, ORBIT_DIRECTION, etc.). Usually the same as in slcStack.h5. 

303 mode: str 

304 open mode ('w' for writing new file or 'a' for appending to existing file). 

305 chunks: tuple 

306 chunk size ('True'/'False' or tuple specifying the dimension of the chunks) 

307 """ 

308 with h5py.File(self.file, mode) as f: 

309 self.logger.info(msg="Prepare dataset: {d:<25} of {t:<25} in size of {s}".format( 

310 d=dataset_name, 

311 t=str(dtype), 

312 s=dshape)) 

313 

314 f.create_dataset(dataset_name, 

315 shape=dshape, 

316 dtype=dtype, 

317 chunks=chunks) 

318 

319 # write attributes 

320 metadata = dict(metadata) 

321 for key in metadata.keys(): 

322 f.attrs[key] = metadata[key] 

323 

324 return 

325 

326 def writeToFileBlock(self, *, data: np.ndarray, dataset_name: str, block: Optional[tuple] = None, mode: str = 'a', 

327 print_msg: bool = True): 

328 """Write data to existing HDF5 dataset in disk block by block. 

329 

330 Parameters 

331 ---------- 

332 data: np.ndarray 

333 1/2/3D matrix. 

334 dataset_name: str 

335 dataset name. 

336 block: list 

337 the list can contain 2, 4 or 6 integers indicating: [zStart, zEnd, yStart, yEnd, xStart, xEnd]. 

338 mode: str 

339 open mode ('w' for writing new file or 'a' for appending to existing file). 

340 print_msg: bool 

341 print message. 

342 

343 Returns 

344 -------- 

345 file: str 

346 path to file 

347 """ 

348 if block is None: 

349 # data shape 

350 if isinstance(data, list): 

351 shape = (len(data),) 

352 else: 

353 shape = data.shape 

354 

355 if len(shape) == 1: 

356 block = [0, shape[0]] 

357 elif len(shape) == 2: 

358 block = [0, shape[0], 

359 0, shape[1]] 

360 elif len(shape) == 3: 

361 block = [0, shape[0], 

362 0, shape[1], 

363 0, shape[2]] 

364 

365 with h5py.File(self.file, mode) as f: 

366 

367 if print_msg: 

368 self.logger.info(msg="writing dataset /{:<25} block: {}".format(dataset_name, block)) 

369 if len(block) == 6: 

370 f[dataset_name][block[0]:block[1], 

371 block[2]:block[3], 

372 block[4]:block[5]] = data 

373 

374 elif len(block) == 4: 

375 f[dataset_name][block[0]:block[1], 

376 block[2]:block[3]] = data 

377 

378 elif len(block) == 2: 

379 f[dataset_name][block[0]:block[1]] = data 

380 

381 return self.file 

382 

383 def writeToFile(self, *, data: np.ndarray, dataset_name: str, metadata: Optional[dict] = None, mode: str = 'a', 

384 chunks: [tuple, bool] = True): 

385 """Write the whole dataset to the file (not block-by-block). 

386 

387 Parameters 

388 ---------- 

389 data: np.ndarray 

390 3D data array. 

391 dataset_name: str 

392 name of dataset. 

393 metadata: dict 

394 metadata of dataset (e.g. WAVELENGTH, ORBIT_DIRECTION, etc.). Usually the same as in slcStack.h5. 

395 mode: str 

396 mode for opening the h5 file (e.g. write: 'w' or append: 'a') 

397 chunks: tuple 

398 chunk size ('True'/'False' or tuple specifying the dimension of the chunks) 

399 """ 

400 # 3D dataset 

401 self.logger.info(msg='create HDF5 file: {} with w mode'.format(self.file)) 

402 self.f = h5py.File(self.file, mode) 

403 if dataset_name not in self.f: 

404 self.logger.info(msg='create dataset /{n} of {t:<10} in size of {s}.'.format(n=dataset_name, 

405 t=str(data.dtype), 

406 s=data.shape)) 

407 self.f.create_dataset(dataset_name, data=data, chunks=chunks) 

408 else: 

409 self.logger.info(msg='overwrite dataset /{n} of {t:<10} in size of {s}.'.format(n=dataset_name, 

410 t=str(data.dtype), 

411 s=data.shape)) 

412 self.f[dataset_name] = data 

413 

414 # Attributes 

415 if metadata is not None: 

416 metadata = dict(metadata) 

417 for key, value in metadata.items(): 

418 self.f.attrs[key] = str(value) 

419 

420 self.f.close() 

421 self.logger.info(msg='finished writing to {}'.format(self.file)) 

422 return 

423 

424 

425class Points: 

426 """Points class for storing information about the selected scatterers.""" 

427 

428 file_path: str 

429 point_id: np.array 

430 coord_xy: np.array 

431 num_points: int 

432 phase: np.array 

433 wavelength: float 

434 length: int 

435 width: int 

436 times: None 

437 

438 # etc. 

439 

440 def __init__(self, *, file_path: str, logger: Logger): 

441 """Init. 

442 

443 Parameters 

444 ---------- 

445 file_path: str 

446 ath to filename 

447 logger: Logger 

448 Logging handler. 

449 """ 

450 self.ifg_net_obj = IfgNetwork() # use parent class here which doesn't know and care about 'star' or 'sb' 

451 self.coord_utm = None 

452 self.coord_lalo = None 

453 self.height = None 

454 self.slant_range = None 

455 self.loc_inc = None 

456 self.file_path = file_path 

457 self.logger = logger 

458 

459 def prepare(self, *, point_id: np.ndarray, coord_xy: np.ndarray, input_path: str): 

460 """Assign point_id and radar coordinates to the object. 

461 

462 Store the point_id and radar coordinates of the scatterers in the object (not file) and read further 

463 attributes from external files (ifg_network.h5, slcStack.h5, geometryRadar.h5, coordinates_utm.h5). 

464 

465 Parameters 

466 ---------- 

467 point_id: np.ndarray 

468 point_id of the scatterers. 

469 coord_xy: np.ndarray 

470 radar coordinates of the scatterers. 

471 input_path: str 

472 path to input files (slcStack.h5, geometryRadar.h5). 

473 """ 

474 self.point_id = point_id 

475 self.coord_xy = coord_xy 

476 self.num_points = self.coord_xy.shape[0] 

477 self.phase = None 

478 self.openExternalData(input_path=input_path) 

479 

480 def writeToFile(self): 

481 """Write data to .h5 file (num_points, coord_xy, point_id, phase).""" 

482 self.logger.info(msg="write data to {}...".format(self.file_path)) 

483 

484 if exists(self.file_path): 

485 os.remove(self.file_path) 

486 

487 with h5py.File(self.file_path, 'w') as f: 

488 f.attrs["num_points"] = self.num_points 

489 f.create_dataset('coord_xy', data=self.coord_xy) 

490 f.create_dataset('point_id', data=self.point_id) 

491 f.create_dataset('phase', data=self.phase) 

492 

493 def open(self, input_path: str, other_file_path: str = None): 

494 """Read data from file. 

495 

496 Read stored information from already existing .h5 file. This can be the file of the object itself. If the 

497 data should be read from another file, the path to this file can be given as 'other_file_path'. Thereby, a new 

498 Points object can be created with the data of another Points object. 

499 

500 Parameters 

501 ---------- 

502 input_path: str 

503 path to input files (slcStack.h5, geometryRadar.h5). 

504 other_file_path: str 

505 path to other .h5 file (default: None). 

506 """ 

507 # 1) read own data: coord_xy, phase, point_id, num_points, reference_point_idx 

508 if other_file_path is not None: 

509 path = other_file_path 

510 else: 

511 path = self.file_path 

512 self.logger.info(msg="read from {}".format(path)) 

513 

514 with h5py.File(path, 'r') as f: 

515 self.num_points = f.attrs["num_points"] 

516 self.coord_xy = f["coord_xy"][:] 

517 self.point_id = f["point_id"][:] 

518 self.phase = f["phase"][:] 

519 

520 self.openExternalData(input_path=input_path) 

521 

522 def openExternalData(self, *, input_path: str): 

523 """Load data which is stored in slcStack.h5, geometryRadar.h5, ifg_network.h5 and coordinates_utm.h5.""" 

524 # 1) read IfgNetwork 

525 self.ifg_net_obj.open(path=join(dirname(self.file_path), "ifg_network.h5")) 

526 

527 # 2) read metadata from slcStack 

528 slc_stack_obj = slcStack(join(input_path, "slcStack.h5")) 

529 slc_stack_obj.open(print_msg=False) 

530 self.wavelength = np.float64(slc_stack_obj.metadata["WAVELENGTH"]) 

531 self.length = slc_stack_obj.length # y-coordinate axis (azimut) 

532 self.width = slc_stack_obj.width # x-coordinate axis (range) 

533 

534 # 3) read from geometry file 

535 mask = self.createMask() 

536 

537 geom_path = join(input_path, "geometryRadar.h5") 

538 

539 # load geometry data 

540 loc_inc, meta = readfile.read(geom_path, datasetName='incidenceAngle') 

541 loc_inc *= np.pi / 180 # in [rad] 

542 slant_range = readfile.read(geom_path, datasetName='slantRangeDistance')[0] 

543 height = readfile.read(geom_path, datasetName='height')[0] 

544 lat = readfile.read(geom_path, datasetName='latitude')[0] 

545 lon = readfile.read(geom_path, datasetName='longitude')[0] 

546 

547 self.loc_inc = loc_inc[mask].ravel() 

548 self.slant_range = slant_range[mask].ravel() 

549 self.height = height[mask].ravel() 

550 self.coord_lalo = np.array([lat[mask].ravel(), lon[mask].ravel()]).transpose() 

551 

552 # 4) read UTM coordinates 

553 coord_utm_obj = CoordinatesUTM(file_path=join(dirname(self.file_path), "coordinates_utm.h5"), 

554 logger=self.logger) 

555 coord_utm_obj.open() 

556 self.coord_utm = coord_utm_obj.coord_utm[:, mask].transpose() 

557 

558 def createMask(self): 

559 """Create a mask. 

560 

561 Create a mask in the size of the radar image which is used to read the geometry and SLC data for the selected 

562 scatterers. 

563 """ 

564 mask = np.zeros((self.length, self.width), dtype=np.bool_) 

565 tmp = [tuple([c[0], c[1]]) for c in self.coord_xy] 

566 for i in tmp: 

567 mask[i] = True 

568 return mask 

569 

570 def addPointsFromObj(self, *, new_point_id: np.ndarray, new_coord_xy: np.ndarray, new_phase: np.ndarray, 

571 new_num_points: int, input_path: str): 

572 """Add new points and their attributes to the existing data. 

573 

574 Parameters 

575 ---------- 

576 new_point_id: np.ndarray 

577 point_id of the new scatterers. 

578 new_coord_xy: np.ndarray 

579 radar coordinates of the new scatterers. 

580 new_phase: np.ndarray 

581 phase of the new scatterers. 

582 new_num_points: int 

583 number of new points. 

584 input_path: str 

585 path to input files (slcStack.h5, geometryRadar.h5). 

586 """ 

587 self.point_id = np.append(self.point_id, new_point_id) 

588 self.coord_xy = np.append(self.coord_xy, new_coord_xy, axis=0) 

589 self.phase = np.append(self.phase, new_phase, axis=0) 

590 self.num_points += new_num_points 

591 

592 # all data must be ordered, so that all external data can be loaded correctly 

593 sort_idx = np.argsort(self.point_id) 

594 self.point_id = self.point_id[sort_idx] 

595 self.coord_xy = self.coord_xy[sort_idx, :] 

596 self.phase = self.phase[sort_idx, :] 

597 # refresh by reopening all external data 

598 self.openExternalData(input_path=input_path) 

599 

600 def removePoints(self, mask: np.ndarray = None, *, keep_id: [np.ndarray, list], input_path: str): 

601 """Remove all entries from specified points. 

602 

603 The possible options exist for removing the points: 

604 a) Keep all points which are set to True in a 'mask' with size (num_points x 1). Or 

605 b) Keep all points whose ID is listed in keep_id. The rest of the points will be removed. 

606 

607 Parameters 

608 ---------- 

609 mask: np.ndarray 

610 mask to select points to be kept, rest will be removed (default: None). 

611 keep_id: np.ndarray 

612 list of point_id to keep. 

613 input_path: str 

614 path to input files (slcStack.h5, geometryRadar.h5). 

615 """ 

616 if mask is None: 

617 mask = np.ones((self.num_points,), dtype=np.bool_) 

618 for p in self.point_id: 

619 if p not in keep_id: 

620 mask[self.point_id == p] = False 

621 self.point_id = self.point_id[mask] 

622 self.coord_xy = self.coord_xy[mask, :] 

623 self.phase = self.phase[mask, :] 

624 self.num_points = mask[mask].shape[0] 

625 # refresh by reopening all external data 

626 self.openExternalData(input_path=input_path) 

627 

628 

629class Network: 

630 """Spatial network of PS candidates.""" 

631 

632 def __init__(self, *, file_path: str, logger: Logger): 

633 """Init. 

634 

635 Parameters 

636 ---------- 

637 file_path: str 

638 absolute path to working directory for creating/loading 'psNetwork.h5' 

639 logger: Logger 

640 Logging handler. 

641 """ 

642 self.num_arcs = None 

643 self.gamma = None 

644 self.arcs = None 

645 self.slant_range = None 

646 self.loc_inc = None 

647 self.phase = None 

648 self.vel = None 

649 self.demerr = None 

650 self.ifg_net_obj = None 

651 self.width = None 

652 self.length = None 

653 self.wavelength = None 

654 self.file_path = file_path 

655 self.logger = logger 

656 

657 def writeToFile(self): 

658 """Write all existing data to psNetwork.h5 file.""" 

659 self.logger.info(msg="write data to {}...".format(self.file_path)) 

660 

661 if exists(self.file_path): 

662 os.remove(self.file_path) 

663 

664 with h5py.File(self.file_path, 'w') as f: 

665 f.attrs["num_arcs"] = self.num_arcs 

666 f.create_dataset('arcs', data=self.arcs) 

667 f.create_dataset('phase', data=self.phase) 

668 f.create_dataset('loc_inc', data=self.loc_inc) 

669 f.create_dataset('slant_range', data=self.slant_range) 

670 

671 def open(self, *, input_path: str): 

672 """Read stored information from existing .h5 file.""" 

673 with h5py.File(self.file_path, 'r') as f: 

674 self.num_arcs = f.attrs["num_arcs"] 

675 self.arcs = f["arcs"][:] 

676 self.phase = f["phase"][:] 

677 self.loc_inc = f["loc_inc"][:] 

678 self.slant_range = f["slant_range"][:] 

679 self.openExternalData(input_path=input_path) 

680 

681 def openExternalData(self, *, input_path: str): 

682 """Read data from slcStack.h5 and IfgNetwork.h5 files.""" 

683 slc_stack_obj = slcStack(join(input_path, "slcStack.h5")) 

684 slc_stack_obj.open(print_msg=False) 

685 self.wavelength = np.float64(slc_stack_obj.metadata["WAVELENGTH"]) 

686 self.length = slc_stack_obj.length # y-coordinate axis (azimut) 

687 self.width = slc_stack_obj.width # x-coordinate axis (range) 

688 

689 # 3) read IfgNetwork 

690 self.ifg_net_obj = IfgNetwork() 

691 self.ifg_net_obj.open(path=join(dirname(self.file_path), "ifg_network.h5")) 

692 

693 def computeArcObservations(self, *, point_obj: Points, arcs: np.ndarray): 

694 """Compute the phase observations for each arc. 

695 

696 Compute double difference phase observations, i.e. the phase differences for each arc in the network from the 

697 phase of the two scatterers connected by the arc. 

698 

699 Parameters 

700 ---------- 

701 point_obj: Points 

702 object of class Points. 

703 arcs: np.ndarray 

704 Array with the indices of the points connected by an arc. 

705 """ 

706 self.arcs = arcs 

707 self.num_arcs = self.arcs.shape[0] 

708 self.logger.info(msg="no. arcs:\t{}".format(self.num_arcs)) 

709 

710 self.phase = np.zeros((self.num_arcs, point_obj.ifg_net_obj.num_ifgs)) 

711 self.loc_inc = np.zeros((self.num_arcs,)) 

712 self.slant_range = np.zeros((self.num_arcs,)) 

713 for idx, arc in enumerate(self.arcs): 

714 self.phase[idx, :] = np.angle( 

715 np.exp(1j * point_obj.phase[arc[0], :]) * np.conjugate(np.exp(1j * point_obj.phase[arc[1], :]))) 

716 self.loc_inc[idx] = np.mean([point_obj.loc_inc[arc[0]], point_obj.loc_inc[arc[1]]]) 

717 self.slant_range[idx] = np.mean([point_obj.slant_range[arc[0]], point_obj.slant_range[arc[1]]]) 

718 

719 self.logger.info(msg="ifg arc observations created.") 

720 

721 def removeArcs(self, *, mask: np.ndarray): 

722 """Remove arcs from the list of arcs in the network. 

723 

724 Parameter 

725 --------- 

726 mask: np.ndarray 

727 mask to select arcs to be kept, rest will be removed. 

728 """ 

729 self.demerr = self.demerr[mask] 

730 self.vel = self.vel[mask] 

731 self.phase = self.phase[mask, :] 

732 self.loc_inc = self.loc_inc[mask] 

733 self.slant_range = self.slant_range[mask] 

734 self.arcs = np.array(self.arcs) 

735 self.arcs = self.arcs[mask, :] 

736 self.gamma = self.gamma[mask] 

737 self.num_arcs = mask[mask].shape[0] 

738 

739 

740class NetworkParameter(Network): 

741 """Spatial Network with the estimated parameters of each arc in the network.""" 

742 

743 def __init__(self, *, file_path: str, logger: Logger): 

744 """Init.""" 

745 super().__init__(file_path=file_path, logger=logger) 

746 self.gamma = None 

747 self.vel = None 

748 self.demerr = None 

749 self.slant_range = None 

750 self.loc_inc = None 

751 self.phase = None 

752 self.arcs = None 

753 self.num_arcs = None 

754 self.logger = logger 

755 

756 def prepare(self, *, net_obj: Network, demerr: np.ndarray, vel: np.ndarray, gamma: np.ndarray): 

757 """Prepare. 

758 

759 Parameter 

760 ----------- 

761 net_obj: Network 

762 object of class Network. 

763 demerr: np.ndarray 

764 estimated DEM error for each arc in the network. 

765 vel: np.ndarray 

766 estimated velocity for each arc in the network. 

767 gamma: np.ndarray 

768 estimated temporal coherence for each arc in the network. 

769 """ 

770 self.num_arcs = net_obj.num_arcs 

771 self.arcs = net_obj.arcs 

772 self.phase = net_obj.phase 

773 self.loc_inc = net_obj.loc_inc 

774 self.slant_range = net_obj.slant_range 

775 self.demerr = demerr 

776 self.vel = vel 

777 self.gamma = gamma 

778 

779 def writeToFile(self): 

780 """Write DEM error, velocity and temporal coherence to file.""" 

781 super().writeToFile() 

782 

783 with h5py.File(self.file_path, 'r+') as f: # append existing file 

784 f.create_dataset('demerr', data=self.demerr) 

785 f.create_dataset('vel', data=self.vel) 

786 f.create_dataset('gamma', data=self.gamma) 

787 

788 def open(self, *, input_path: str): 

789 """Read data from file.""" 

790 super().open(input_path=input_path) 

791 

792 with h5py.File(self.file_path, 'r') as f: 

793 self.demerr = f["demerr"][:] 

794 self.vel = f["vel"][:] 

795 self.gamma = f["gamma"][:]