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 +106 -0
- insardev/Stack_base.py +149 -0
- insardev/Stack_detrend.py +1214 -0
- insardev/Stack_export.py +591 -0
- insardev/Stack_incidence.py +274 -0
- insardev/Stack_lstsq.py +471 -0
- insardev/Stack_multilooking.py +184 -0
- insardev/Stack_phasediff.py +560 -0
- insardev/Stack_ps.py +220 -0
- insardev/Stack_sbas.py +626 -0
- insardev/Stack_stl.py +222 -0
- insardev/Stack_unwrap.py +589 -0
- insardev/Stack_unwrap_snaphu.py +216 -0
- insardev/__init__.py +15 -0
- insardev/dataset.py +454 -0
- insardev/utils.py +136 -0
- insardev-2025.2.20.dev0.dist-info/LICENSE.txt +29 -0
- insardev-2025.2.20.dev0.dist-info/METADATA +151 -0
- insardev-2025.2.20.dev0.dist-info/RECORD +21 -0
- insardev-2025.2.20.dev0.dist-info/WHEEL +5 -0
- insardev-2025.2.20.dev0.dist-info/top_level.txt +1 -0
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
|