insardev 2025.2.20.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
insardev/Stack.py ADDED
@@ -0,0 +1,106 @@
1
+ # ----------------------------------------------------------------------------
2
+ # PyGMTSAR
3
+ #
4
+ # This file is part of the PyGMTSAR project: https://github.com/mobigroup/gmtsar
5
+ #
6
+ # Copyright (c) 2025, Alexey Pechnikov
7
+ #
8
+ # Licensed under the BSD 3-Clause License (see LICENSE for details)
9
+ # ----------------------------------------------------------------------------
10
+ from .Stack_export import Stack_export
11
+
12
+ class Stack(Stack_export):
13
+
14
+ def __repr__(self):
15
+ return 'Object %s %d bursts %d dates' % (self.__class__.__name__, len(self.ds), len(self.ds[0].date))
16
+
17
+ def to_dataset(self):
18
+ import numpy as np
19
+ import xarray as xr
20
+ data = xr.concat(xr.align(*self.ds, join='outer'), dim='stack_dim').mean('stack_dim')
21
+ return data
22
+
23
+ def __init__(self, basedir, pattern_burst='*_*_?W?', pattern_date = '????????.nc', scale = 2.5e-07):
24
+ """
25
+ Initialize an instance of the Stack class.
26
+ """
27
+ import numpy as np
28
+ import xarray as xr
29
+ import glob
30
+ import os
31
+
32
+ self.basedir = basedir
33
+
34
+ bursts = glob.glob(pattern_burst, root_dir=self.basedir)
35
+ datas = []
36
+ for burst in bursts:
37
+ data = xr.open_mfdataset(
38
+ os.path.join(self.basedir, burst, pattern_date),
39
+ engine=self.netcdf_engine_read,
40
+ format=self.netcdf_format,
41
+ parallel=True,
42
+ concat_dim='date',
43
+ chunks={'date': 1, 'y': self.chunksize, 'x': self.chunksize},
44
+ combine='nested',
45
+ )
46
+ #print (data)
47
+ # zero in np.int16 type means NODATA
48
+ data = self.spatial_ref(
49
+ scale*(data.re.astype(np.float32) + 1j*data.im.astype(np.float32)).where(data.re != 0).rename('data'),
50
+ data
51
+ )\
52
+ .to_dataset(name='data')\
53
+ .assign({v: data[v] for v in data.data_vars if v not in ['im', 're']})\
54
+ .assign_attrs({'burst': burst})
55
+ datas.append(data)
56
+ del data
57
+
58
+ self.ds = datas
59
+
60
+ def baseline_table(self):
61
+ import xarray as xr
62
+ return xr.concat([ds.BPR for ds in self.ds], dim='burst').mean('burst').to_dataframe()[['BPR']]
63
+
64
+ def baseline_pairs(self, days=None, meters=None, invert=False):
65
+ """
66
+ Generates a sorted list of baseline pairs.
67
+ Returns
68
+ -------
69
+ pandas.DataFrame
70
+ A DataFrame containing the sorted list of baseline pairs with reference and repeat dates,
71
+ timelines, and baselines.
72
+
73
+ """
74
+ import numpy as np
75
+ import pandas as pd
76
+
77
+ if days is None:
78
+ # use large number for unlimited time interval in days
79
+ days = 1e6
80
+
81
+ tbl = self.baseline_table()
82
+ data = []
83
+ for line1 in tbl.itertuples():
84
+ counter = 0
85
+ for line2 in tbl.itertuples():
86
+ #print (line1, line2)
87
+ if not (line1.Index < line2.Index and (line2.Index - line1.Index).days < days + 1):
88
+ continue
89
+ if meters is not None and not (abs(line1.BPR - line2.BPR)< meters + 1):
90
+ continue
91
+
92
+ counter += 1
93
+ if not invert:
94
+ data.append({'ref':line1.Index, 'rep': line2.Index,
95
+ 'ref_baseline': np.round(line1.BPR, 2),
96
+ 'rep_baseline': np.round(line2.BPR, 2)})
97
+ else:
98
+ data.append({'ref':line2.Index, 'rep': line1.Index,
99
+ 'ref_baseline': np.round(line2.BPR, 2),
100
+ 'rep_baseline': np.round(line1.BPR, 2)})
101
+
102
+ df = pd.DataFrame(data).sort_values(['ref', 'rep'])
103
+ return df.assign(pair=[f'{ref} {rep}' for ref, rep in zip(df['ref'].dt.date, df['rep'].dt.date)],
104
+ baseline=df.rep_baseline - df.ref_baseline,
105
+ duration=(df['rep'] - df['ref']).dt.days,
106
+ rel=np.datetime64('nat'))
insardev/Stack_base.py ADDED
@@ -0,0 +1,149 @@
1
+ # ----------------------------------------------------------------------------
2
+ # InSAR.dev
3
+ #
4
+ # This file is part of the InSAR.dev project: https://InSAR.dev
5
+ #
6
+ # Copyright (c) 2025, Alexey Pechnikov
7
+ #
8
+ # Licensed under the BSD 3-Clause License (see LICENSE for details)
9
+ # ----------------------------------------------------------------------------
10
+ from insardev_toolkit import tqdm_joblib, tqdm_dask
11
+ from .dataset import dataset
12
+
13
+ class Stack_base(tqdm_joblib, dataset):
14
+
15
+ def get_pairs(self, pairs, dates=False):
16
+ """
17
+ Get pairs as DataFrame and optionally dates array.
18
+
19
+ Parameters
20
+ ----------
21
+ pairs : np.ndarray, optional
22
+ An array of pairs. If None, all pairs are considered. Default is None.
23
+ dates : bool, optional
24
+ Whether to return dates array. Default is False.
25
+ name : str, optional
26
+ The name of the phase filter. Default is 'phasefilt'.
27
+
28
+ Returns
29
+ -------
30
+ pd.DataFrame or tuple
31
+ A DataFrame of pairs. If dates is True, also returns an array of dates.
32
+ """
33
+ import xarray as xr
34
+ import pandas as pd
35
+ import numpy as np
36
+ from glob import glob
37
+
38
+ if isinstance(pairs, pd.DataFrame):
39
+ # workaround for baseline_pairs() output
40
+ pairs = pairs.rename(columns={'ref_date': 'ref', 'rep_date': 'rep'})
41
+ elif isinstance(pairs, (xr.DataArray, xr.Dataset)):
42
+ # pairs = pd.DataFrame({
43
+ # 'ref': pairs.coords['ref'].values,
44
+ # 'rep': pairs.coords['rep'].values
45
+ # })
46
+ refs = pairs.coords['ref'].values
47
+ reps = pairs.coords['rep'].values
48
+ pairs = pd.DataFrame({
49
+ 'ref': refs if isinstance(refs, np.ndarray) else [refs],
50
+ 'rep': reps if isinstance(reps, np.ndarray) else [reps]
51
+ })
52
+ else:
53
+ # Convert numpy array to DataFrame
54
+ # in case of 1d array with 2 items convert to a single pair
55
+ pairs_2d = [pairs] if np.asarray(pairs).shape == (2,) else pairs
56
+ pairs = pd.DataFrame(pairs_2d, columns=['ref', 'rep'])
57
+
58
+ # Convert ref and rep columns to datetime format
59
+ pairs['ref'] = pd.to_datetime(pairs['ref'])
60
+ pairs['rep'] = pd.to_datetime(pairs['rep'])
61
+ pairs['pair'] = [f'{ref} {rep}' for ref, rep in zip(pairs['ref'].dt.date, pairs['rep'].dt.date)]
62
+ # Calculate the duration in days and add it as a new column
63
+ #pairs['duration'] = (pairs['rep'] - pairs['ref']).dt.days
64
+
65
+ if dates:
66
+ # pairs is DataFrame
67
+ dates = np.unique(pairs[['ref', 'rep']].astype(str).values.flatten())
68
+ return (pairs, dates)
69
+ return pairs
70
+
71
+ def get_pairs_matrix(self, pairs):
72
+ """
73
+ Create a matrix based on interferogram dates and pairs.
74
+
75
+ Parameters
76
+ ----------
77
+ pairs : pandas.DataFrame or xarray.DataArray or xarray.Dataset
78
+ DataFrame or DataArray containing interferogram date pairs.
79
+
80
+ Returns
81
+ -------
82
+ numpy.ndarray
83
+ A matrix with one row for every interferogram and one column for every date.
84
+ Each element in the matrix is a float, with 1 indicating the start date,
85
+ -1 indicating the end date, 0 if the date is covered by the corresponding
86
+ interferogram timeline, and NaN otherwise.
87
+
88
+ """
89
+ import numpy as np
90
+ import pandas as pd
91
+
92
+ # also define image capture dates from interferogram date pairs
93
+ pairs, dates = self.get_pairs(pairs, dates=True)
94
+ pairs = pairs[['ref', 'rep']].astype(str).values
95
+
96
+ # here are one row for every interferogram and one column for every date
97
+ matrix = []
98
+ for pair in pairs:
99
+ #mrow = [date>=pair[0] and date<=pair[1] for date in dates]
100
+ mrow = [(-1 if date==pair[0] else (1 if date==pair[1] else (0 if date>pair[0] and date<pair[1] else np.nan))) for date in dates]
101
+ matrix.append(mrow)
102
+ matrix = np.stack(matrix).astype(np.float32)
103
+ return matrix
104
+
105
+ @staticmethod
106
+ def phase_to_positive_range(phase):
107
+ """
108
+ Convert phase from the range [-pi, pi] to [0, 2pi].
109
+
110
+ Parameters
111
+ ----------
112
+ phase : array_like
113
+ Input phase values in the range [-pi, pi].
114
+
115
+ Returns
116
+ -------
117
+ ndarray
118
+ Phase values converted to the range [0, 2pi].
119
+
120
+ Examples
121
+ --------
122
+ >>> phase_to_positive_range(np.array([-np.pi, -np.pi/2, np.pi, 2*-np.pi-1e-6, 2*-np.pi]))
123
+ array([3.14159265, 4.71238898, 3.14159265, 6.28318431, 0. ])
124
+ """
125
+ import numpy as np
126
+ return (phase + 2 * np.pi) % (2 * np.pi)
127
+
128
+ @staticmethod
129
+ def phase_to_symmetric_range(phase):
130
+ """
131
+ Convert phase from the range [0, 2pi] to [-pi, pi].
132
+
133
+ Parameters
134
+ ----------
135
+ phase : array_like
136
+ Input phase values in the range [0, 2pi].
137
+
138
+ Returns
139
+ -------
140
+ ndarray
141
+ Phase values converted to the range [-pi, pi].
142
+
143
+ Examples
144
+ --------
145
+ >>> phase_to_symmetric_range(np.array([0, np.pi, 3*np.pi/2, 2*np.pi]))
146
+ array([ 0. , 3.14159265, -1.57079633, 0. ])
147
+ """
148
+ import numpy as np
149
+ return (phase + np.pi) % (2 * np.pi) - np.pi