1- import logging
1+ from __future__ import annotations
2+
3+ import contextlib
24from pathlib import Path
35from tempfile import NamedTemporaryFile
4- from typing import Callable , Protocol , Sequence , TypeVar
6+ from typing import Callable , Optional , Protocol , Sequence , TypeVar
57
68import jax .numpy as jnp
79import numpy as np
1012from opera_utils import get_dates
1113from scipy import ndimage
1214
13- from dolphin import DateOrDatetime , io
15+ from dolphin import DateOrDatetime , io , utils
16+ from dolphin ._log import get_log , log_runtime
1417from dolphin ._overviews import ImageType , create_overviews
1518from dolphin ._types import PathOrStr , ReferencePoint
1619from dolphin .utils import flatten , format_dates
17-
18- __all__ = [
19- "invert_stack" ,
20- "get_incidence_matrix" ,
21- "estimate_velocity" ,
22- "create_velocity" ,
23- "create_temporal_average" ,
24- "invert_unw_network" ,
25- "correlation_to_variance" ,
26- "select_reference_point" ,
27- ]
20+ from dolphin .workflows import CallFunc
2821
2922T = TypeVar ("T" )
3023
31- logger = logging .getLogger (__name__ )
24+ logger = get_log (__name__ )
25+
26+ __all__ = ["run" ]
3227
3328
3429class ReferencePointError (ValueError ):
3530 pass
3631
3732
33+ @log_runtime
34+ def run (
35+ unwrapped_paths : Sequence [PathOrStr ],
36+ conncomp_paths : Sequence [PathOrStr ],
37+ corr_paths : Sequence [PathOrStr ],
38+ condition_file : PathOrStr ,
39+ condition : CallFunc ,
40+ output_dir : PathOrStr ,
41+ run_velocity : bool = False ,
42+ velocity_file : Optional [PathOrStr ] = None ,
43+ correlation_threshold : float = 0.2 ,
44+ num_threads : int = 5 ,
45+ reference_point : Optional [ReferencePoint ] = None ,
46+ ) -> list [Path ]:
47+ """Invert the unwrapped interferograms, estimate timeseries and phase velocity.
48+
49+ Parameters
50+ ----------
51+ unwrapped_paths : Sequence[Path]
52+ Sequence unwrapped interferograms to invert.
53+ corr_paths : Sequence[Path]
54+ Sequence interferometric correlation files, one per file in `unwrapped_paths`
55+ conncomp_paths : Sequence[Path]
56+ Sequence connected component files, one per file in `unwrapped_paths`
57+ condition_file: PathOrStr
58+ A file with the same size as each raster, like amplitude dispersion or
59+ temporal coherence
60+ condition: CallFunc
61+ The function to apply to the condition file,
62+ for example numpy.argmin which finds the pixel with lowest value
63+ the options are [min, max]
64+ output_dir : Path
65+ Path to the output directory.
66+ run_velocity : bool
67+ Whether to run velocity estimation on the inverted phase series
68+ velocity_file : Path, Optional
69+ The output velocity file
70+ correlation_threshold : float
71+ Pixels with correlation below this value will be masked out
72+ num_threads : int
73+ The parallel blocks to process at once.
74+ Default is 5.
75+ reference_point : tuple[int, int], optional
76+ Reference point (row, col) used if performing a time series inversion.
77+ If not provided, a point will be selected from a consistent connected
78+ component with low amplitude dispersion or high temporal coherence.
79+
80+ Returns
81+ -------
82+ inverted_phase_paths : list[Path]
83+ list of Paths to inverted interferograms (single reference phase series).
84+
85+ """
86+ condition_func = argmax_index if condition == CallFunc .MAX else argmin_index
87+
88+ Path (output_dir ).mkdir (exist_ok = True , parents = True )
89+
90+ # First we find the reference point for the unwrapped interferograms
91+ if reference_point is None :
92+ reference = select_reference_point (
93+ conncomp_paths ,
94+ condition_file ,
95+ output_dir = Path (output_dir ),
96+ condition_func = condition_func ,
97+ )
98+ else :
99+ reference = reference_point
100+
101+ ifg_date_pairs = [get_dates (f ) for f in unwrapped_paths ]
102+ sar_dates = sorted (set (utils .flatten (ifg_date_pairs )))
103+ # if we did single-reference interferograms, for `n` sar dates, we will only have
104+ # `n-1` interferograms. Any more than n-1 ifgs means we need to invert
105+ needs_inversion = len (unwrapped_paths ) > len (sar_dates ) - 1
106+ # check if we even need to invert, or if it was single reference
107+ inverted_phase_paths : list [Path ] = []
108+ if needs_inversion :
109+ logger .info ("Selecting a reference point for unwrapped interferograms" )
110+
111+ logger .info ("Inverting network of %s unwrapped ifgs" , len (unwrapped_paths ))
112+ inverted_phase_paths = invert_unw_network (
113+ unw_file_list = unwrapped_paths ,
114+ reference = reference ,
115+ output_dir = output_dir ,
116+ num_threads = num_threads ,
117+ )
118+ else :
119+ logger .info (
120+ "Skipping inversion step: only single reference interferograms exist."
121+ )
122+ # Symlink the unwrapped paths to `timeseries/`
123+ for p in unwrapped_paths :
124+ target = Path (output_dir ) / Path (p ).name
125+ with contextlib .suppress (FileExistsError ):
126+ target .symlink_to (p )
127+ inverted_phase_paths .append (target )
128+ # Make extra "0" raster so that the number of rasters matches len(sar_dates)
129+ ref_raster = Path (output_dir ) / (
130+ utils .format_dates (sar_dates [0 ], sar_dates [0 ]) + ".tif"
131+ )
132+ io .write_arr (
133+ arr = None , output_name = ref_raster , like_filename = inverted_phase_paths [0 ]
134+ )
135+ inverted_phase_paths .append (ref_raster )
136+
137+ if run_velocity :
138+ # We can't pass the correlations after an inversion- the numbers don't match
139+ # TODO:
140+ # Is there a better weighting then?
141+ cor_file_list = (
142+ corr_paths if len (corr_paths ) == len (inverted_phase_paths ) else None
143+ )
144+ logger .info ("Estimating phase velocity" )
145+ if velocity_file is None :
146+ velocity_file = Path (output_dir ) / "velocity.tif"
147+ create_velocity (
148+ unw_file_list = inverted_phase_paths ,
149+ output_file = velocity_file ,
150+ reference = reference ,
151+ date_list = sar_dates ,
152+ cor_file_list = cor_file_list ,
153+ cor_threshold = correlation_threshold ,
154+ num_threads = num_threads ,
155+ )
156+
157+ return inverted_phase_paths
158+
159+
38160def argmin_index (arr : ArrayLike ) -> tuple [int , ...]:
39161 """Get the index tuple of the minimum value of the array.
40162
@@ -55,6 +177,26 @@ def argmin_index(arr: ArrayLike) -> tuple[int, ...]:
55177 return np .unravel_index (np .argmin (arr ), np .shape (arr ))
56178
57179
180+ def argmax_index (arr : ArrayLike ) -> tuple [int , ...]:
181+ """Get the index tuple of the maximum value of the array.
182+
183+ If multiple occurrences of the maximum value exist, returns
184+ the index of the first such occurrence in the flattened array.
185+
186+ Parameters
187+ ----------
188+ arr : array_like
189+ The input array.
190+
191+ Returns
192+ -------
193+ tuple of int
194+ The index of the maximum value.
195+
196+ """
197+ return np .unravel_index (np .argmax (arr ), np .shape (arr ))
198+
199+
58200@jit
59201def weighted_lstsq_single (
60202 A : ArrayLike ,
@@ -509,7 +651,7 @@ def invert_unw_network(
509651 The number of looks used to form the input correlation data, used
510652 to convert correlation to phase variance.
511653 Default is 1.
512- num_threads : int, optional
654+ num_threads : int
513655 The parallel blocks to process at once.
514656 Default is 5.
515657 add_overviews : bool, optional
0 commit comments