pyunwrap-insar 0.1.0__tar.gz
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.
- pyunwrap_insar-0.1.0/LICENSE +21 -0
- pyunwrap_insar-0.1.0/PKG-INFO +358 -0
- pyunwrap_insar-0.1.0/README.md +305 -0
- pyunwrap_insar-0.1.0/pyproject.toml +116 -0
- pyunwrap_insar-0.1.0/pyunwrap/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/analytics/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/analytics/explainability.py +331 -0
- pyunwrap_insar-0.1.0/pyunwrap/analytics/phase_stats.py +374 -0
- pyunwrap_insar-0.1.0/pyunwrap/analytics/report_generator.py +404 -0
- pyunwrap_insar-0.1.0/pyunwrap/data/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/data/dataloader.py +243 -0
- pyunwrap_insar-0.1.0/pyunwrap/data/preprocessing.py +417 -0
- pyunwrap_insar-0.1.0/pyunwrap/inference/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/inference/unwrapper.py +650 -0
- pyunwrap_insar-0.1.0/pyunwrap/models/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/models/ambiguity_net.py +367 -0
- pyunwrap_insar-0.1.0/pyunwrap/models/losses.py +257 -0
- pyunwrap_insar-0.1.0/pyunwrap/synthetic/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/synthetic/generator.py +992 -0
- pyunwrap_insar-0.1.0/pyunwrap/training/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/training/trainer.py +754 -0
- pyunwrap_insar-0.1.0/pyunwrap/utils/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/utils/deployment.py +368 -0
- pyunwrap_insar-0.1.0/pyunwrap/visualization/__init__.py +0 -0
- pyunwrap_insar-0.1.0/pyunwrap/visualization/charts.py +179 -0
- pyunwrap_insar-0.1.0/pyunwrap/visualization/maps.py +223 -0
- pyunwrap_insar-0.1.0/pyunwrap/visualization/phase_plots.py +195 -0
- pyunwrap_insar-0.1.0/pyunwrap_insar.egg-info/PKG-INFO +358 -0
- pyunwrap_insar-0.1.0/pyunwrap_insar.egg-info/SOURCES.txt +36 -0
- pyunwrap_insar-0.1.0/pyunwrap_insar.egg-info/dependency_links.txt +1 -0
- pyunwrap_insar-0.1.0/pyunwrap_insar.egg-info/entry_points.txt +2 -0
- pyunwrap_insar-0.1.0/pyunwrap_insar.egg-info/requires.txt +33 -0
- pyunwrap_insar-0.1.0/pyunwrap_insar.egg-info/top_level.txt +1 -0
- pyunwrap_insar-0.1.0/setup.cfg +4 -0
- pyunwrap_insar-0.1.0/tests/test_model.py +211 -0
- pyunwrap_insar-0.1.0/tests/test_physics.py +243 -0
- pyunwrap_insar-0.1.0/tests/test_pipeline.py +303 -0
- pyunwrap_insar-0.1.0/tests/test_synthetic.py +271 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pyunwrap contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyunwrap-insar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-driven InSAR phase unwrapping via a Physics-Informed U-Net (Ambiguity-Net) that predicts the discrete integer ambiguity map, enforcing the rewrapping constraint by construction.
|
|
5
|
+
Author-email: Your Name <you@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/pyunwrap
|
|
8
|
+
Project-URL: Repository, https://github.com/yourusername/pyunwrap
|
|
9
|
+
Project-URL: Issues, https://github.com/yourusername/pyunwrap/issues
|
|
10
|
+
Keywords: InSAR,phase-unwrapping,SAR,remote-sensing,deep-learning,earth-observation,geospatial
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: torch>=2.1
|
|
23
|
+
Requires-Dist: rasterio>=1.3
|
|
24
|
+
Requires-Dist: geopandas>=0.14
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
26
|
+
Requires-Dist: scikit-image>=0.22
|
|
27
|
+
Requires-Dist: scikit-learn>=1.3
|
|
28
|
+
Requires-Dist: shap>=0.44
|
|
29
|
+
Requires-Dist: plotly>=5.18
|
|
30
|
+
Requires-Dist: pyvista>=0.43
|
|
31
|
+
Requires-Dist: jinja2>=3.1
|
|
32
|
+
Requires-Dist: onnxruntime>=1.16
|
|
33
|
+
Requires-Dist: scipy>=1.11
|
|
34
|
+
Requires-Dist: tqdm>=4.66
|
|
35
|
+
Requires-Dist: pandas>=2.1
|
|
36
|
+
Requires-Dist: tensorboard>=2.15
|
|
37
|
+
Requires-Dist: h5py>=3.10
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
41
|
+
Requires-Dist: black>=23.12; extra == "dev"
|
|
42
|
+
Requires-Dist: ruff>=0.1.9; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy>=1.7; extra == "dev"
|
|
44
|
+
Provides-Extra: maps
|
|
45
|
+
Requires-Dist: folium>=0.15; extra == "maps"
|
|
46
|
+
Requires-Dist: leafmap>=0.30; extra == "maps"
|
|
47
|
+
Provides-Extra: deploy
|
|
48
|
+
Requires-Dist: openvino>=2023.2; extra == "deploy"
|
|
49
|
+
Requires-Dist: onnx>=1.16; extra == "deploy"
|
|
50
|
+
Requires-Dist: onnxconverter-common>=1.14; extra == "deploy"
|
|
51
|
+
Requires-Dist: onnxscript>=0.1; extra == "deploy"
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
<div align="center">
|
|
55
|
+
|
|
56
|
+
<picture>
|
|
57
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-horizontal-dark.svg">
|
|
58
|
+
<img src="assets/logo-horizontal.svg" alt="pyunwrap" width="420">
|
|
59
|
+
</picture>
|
|
60
|
+
|
|
61
|
+
## ⚠️ Important Note
|
|
62
|
+
|
|
63
|
+
This project is **actively under development**. While the core functionality
|
|
64
|
+
is production-ready and thoroughly tested, some advanced features are still
|
|
65
|
+
being refined.
|
|
66
|
+
|
|
67
|
+
**Physics-informed deep learning for InSAR phase unwrapping.**
|
|
68
|
+
|
|
69
|
+
[](LICENSE)
|
|
70
|
+
[](pyproject.toml)
|
|
71
|
+
[](tests/)
|
|
72
|
+
[](https://github.com/psf/black)
|
|
73
|
+
[](#project-status)
|
|
74
|
+
|
|
75
|
+
[Why this exists](#why-this-exists) ·
|
|
76
|
+
[How it works](#how-it-works) ·
|
|
77
|
+
[Install](#installation) ·
|
|
78
|
+
[Quickstart](#quickstart) ·
|
|
79
|
+
[Notebooks](#notebooks) ·
|
|
80
|
+
[Architecture](#architecture) ·
|
|
81
|
+
[Citation](#citation)
|
|
82
|
+
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
`pyunwrap` unwraps Interferometric Synthetic Aperture Radar (InSAR) phase — the
|
|
88
|
+
core measurement behind satellite-based ground-deformation monitoring — using a
|
|
89
|
+
**physics-informed U-Net that predicts an integer ambiguity map**, not the
|
|
90
|
+
unwrapped phase itself. The rewrapping identity is enforced by construction,
|
|
91
|
+
so the model is structurally incapable of producing an output that
|
|
92
|
+
contradicts the observed wrapped phase; it can only be wrong about *how many*
|
|
93
|
+
`2π` cycles were missed, never about the physics of wrapping.
|
|
94
|
+
|
|
95
|
+
## Why this exists
|
|
96
|
+
|
|
97
|
+
Every classical phase-unwrapping algorithm — branch-cut methods, minimum-cost
|
|
98
|
+
flow (the approach behind SNAPHU, the field's long-standing reference tool),
|
|
99
|
+
Goldstein's algorithm — rests on one assumption: that the true phase gradient
|
|
100
|
+
between adjacent pixels never exceeds `π` radians (the Nyquist/Itoh
|
|
101
|
+
condition). Two situations break that assumption in practice:
|
|
102
|
+
|
|
103
|
+
- **Low coherence.** Vegetation, water, and temporal decorrelation add
|
|
104
|
+
near-random phase noise. Once the local gradient becomes unreliable,
|
|
105
|
+
branch-cut and MCF methods don't just get that one pixel wrong — they
|
|
106
|
+
propagate the error across the connected region downstream of it.
|
|
107
|
+
- **Steep deformation gradients.** Earthquakes, volcanic inflation, and
|
|
108
|
+
mining subsidence can produce genuine phase gradients that exceed `π`
|
|
109
|
+
radians per pixel near the source. No amount of algorithmic cleverness
|
|
110
|
+
recovers this from the wrapped phase alone without an external prior —
|
|
111
|
+
which is exactly the gap a learned model can fill.
|
|
112
|
+
|
|
113
|
+
`pyunwrap` targets both regimes directly, and does so without inheriting the
|
|
114
|
+
most common failure mode of naive deep-learning approaches to this problem:
|
|
115
|
+
regressing the unwrapped phase directly gives a network no reason to respect
|
|
116
|
+
`wrap(prediction) == observed_phase`. Predicting the integer ambiguity
|
|
117
|
+
instead makes that identity a mathematical guarantee, not a hope.
|
|
118
|
+
|
|
119
|
+
## How it works
|
|
120
|
+
|
|
121
|
+
`AmbiguityNet` outputs a continuous ambiguity prediction, rounds it via a
|
|
122
|
+
straight-through estimator, and reconstructs phase directly:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
φ̂ = ψ + 2π · round(k̂), k̂ ∈ ℤ
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Approach | Failure mode |
|
|
129
|
+
|---|---|
|
|
130
|
+
| Regress `φ` directly | Nothing constrains `wrap(prediction) == ψ`; the network can output any real value |
|
|
131
|
+
| **Predict `k` (this package)** | `wrap(ψ + 2π·round(k̂)) == ψ` holds by construction — the network only has to get the *integer cycle count* right |
|
|
132
|
+
|
|
133
|
+
That single design decision shapes everything downstream: training uses a
|
|
134
|
+
physics-informed loss with a component that's provably near-zero regardless
|
|
135
|
+
of prediction quality (documented explicitly, not hidden — see
|
|
136
|
+
[`pyunwrap/models/losses.py`](pyunwrap/models/losses.py)); tiled inference
|
|
137
|
+
merges the **integer ambiguity map** across overlapping patches, never the
|
|
138
|
+
phase itself, because averaging phase directly across a tile boundary can
|
|
139
|
+
silently produce a value that satisfies no physical interferogram.
|
|
140
|
+
|
|
141
|
+
## Key features
|
|
142
|
+
|
|
143
|
+
**Synthetic data engine** ([`pyunwrap.synthetic`](pyunwrap/synthetic/generator.py))
|
|
144
|
+
— Gaussian subsidence bowls, a from-scratch Okada (1985) rectangular fault
|
|
145
|
+
dislocation model, a Mogi (1958) volcanic point source, DEM-driven
|
|
146
|
+
topographic phase, Kolmogorov-spectrum atmospheric turbulence, orbital
|
|
147
|
+
ramps, coherence-dependent decorrelation noise, and a pseudo-real strategy
|
|
148
|
+
that rewraps real L-band (ALOS-2) unwrapped phase into simulated C-band data
|
|
149
|
+
to help bridge the sim-to-real gap.
|
|
150
|
+
|
|
151
|
+
**`AmbiguityNet`** ([`pyunwrap.models`](pyunwrap/models/ambiguity_net.py))
|
|
152
|
+
— a ResNet-34-encoder U-Net with a dual head: the integer ambiguity map via
|
|
153
|
+
a straight-through-estimator rounding layer, and an auxiliary residue-
|
|
154
|
+
probability map for uncertainty. Trained with a four-component
|
|
155
|
+
physics-informed loss (ambiguity regression, re-wrap consistency,
|
|
156
|
+
coherence-weighted smoothness, ambiguity-map residue penalty).
|
|
157
|
+
|
|
158
|
+
**Curriculum training** ([`pyunwrap.training`](pyunwrap/training/trainer.py))
|
|
159
|
+
— a three-stage curriculum (high-coherence/low-gradient → moderate →
|
|
160
|
+
full difficulty), optional SNAPHU pseudo-ground-truth fine-tuning on real
|
|
161
|
+
data, AdamW with warmup/cosine annealing, and TensorBoard logging.
|
|
162
|
+
|
|
163
|
+
**Production inference** ([`pyunwrap.inference`](pyunwrap/inference/unwrapper.py))
|
|
164
|
+
— tiled processing of arbitrarily large interferograms with edge-aware,
|
|
165
|
+
residue-probability-weighted smart merging of the ambiguity map, Monte
|
|
166
|
+
Carlo Dropout uncertainty, and an ONNX Runtime → OpenVINO backend fallback
|
|
167
|
+
chain for deployment without a PyTorch dependency.
|
|
168
|
+
|
|
169
|
+
**Scientific analytics** ([`pyunwrap.analytics`](pyunwrap/analytics/))
|
|
170
|
+
— Goldstein-style residue detection and clustering, Nyquist
|
|
171
|
+
gradient-violation mapping, error-distribution statistics, Grad-CAM and
|
|
172
|
+
Integrated-Gradients explainability, and uncertainty-calibration
|
|
173
|
+
reliability diagrams.
|
|
174
|
+
|
|
175
|
+
**Visualization & reporting** ([`pyunwrap.visualization`](pyunwrap/visualization/))
|
|
176
|
+
— interactive Plotly 3D phase surfaces and heatmaps, a folium
|
|
177
|
+
swipe-comparison map, and a self-contained, Jinja2-templated HTML report
|
|
178
|
+
tying every stage together.
|
|
179
|
+
|
|
180
|
+
## Architecture
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
InSARSyntheticGenerator ──┐ real GeoTIFFs
|
|
184
|
+
│ (wrapped, coherence, amplitude)
|
|
185
|
+
▼ │
|
|
186
|
+
tiling + normalization ▼
|
|
187
|
+
│ tiled inference
|
|
188
|
+
▼ via PhaseUnwrapper
|
|
189
|
+
InSARTileDataset │
|
|
190
|
+
│ │
|
|
191
|
+
▼ ▼
|
|
192
|
+
Trainer.fit() ──── AmbiguityNet ──── smart ambiguity-map
|
|
193
|
+
(curriculum, (this is the merging (never
|
|
194
|
+
physics loss) same model) the phase)
|
|
195
|
+
│
|
|
196
|
+
▼
|
|
197
|
+
unwrapped phase +
|
|
198
|
+
analytics + report
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
See [`docs/architecture.md`](docs/architecture.md) for the full
|
|
202
|
+
module-by-module reference and a longer explanation of the physics
|
|
203
|
+
invariant every stage is built around.
|
|
204
|
+
|
|
205
|
+
## Installation
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git clone https://github.com/yourusername/pyunwrap.git
|
|
209
|
+
cd pyunwrap
|
|
210
|
+
pip install -e .
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Optional extras, installed as needed:
|
|
214
|
+
|
|
215
|
+
| Extra | Adds | Use case |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `dev` | `pytest`, `pytest-cov`, `black`, `ruff`, `mypy` | Development, testing, linting |
|
|
218
|
+
| `maps` | `folium`, `leafmap` | Interactive map visualization |
|
|
219
|
+
| `deploy` | `openvino`, `onnx`, `onnxconverter-common`, `onnxscript` | ONNX export, OpenVINO inference |
|
|
220
|
+
| `notebooks` | `jupyter`, `ipykernel` | Running the example notebooks |
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
pip install -e ".[dev,maps,deploy,notebooks]" # everything
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Quickstart
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from pyunwrap.synthetic.generator import InSARSyntheticGenerator
|
|
230
|
+
from pyunwrap.models.ambiguity_net import AmbiguityNet
|
|
231
|
+
from pyunwrap.inference.unwrapper import PhaseUnwrapper
|
|
232
|
+
|
|
233
|
+
# Generate a synthetic training sample (Mogi volcanic source deformation).
|
|
234
|
+
gen = InSARSyntheticGenerator(size=256, seed=42)
|
|
235
|
+
sample = gen.generate_sample(deformation_type="mogi")
|
|
236
|
+
|
|
237
|
+
# Run tiled inference on a real interferogram with a trained model.
|
|
238
|
+
model = AmbiguityNet(pretrained=False, k_max=10.0) # or load your own checkpoint
|
|
239
|
+
unwrapper = PhaseUnwrapper(model=model, device="cuda")
|
|
240
|
+
result = unwrapper.unwrap(
|
|
241
|
+
wrapped_phase_path="data/wrapped_phase.tif",
|
|
242
|
+
coherence_path="data/coherence.tif",
|
|
243
|
+
amplitude_path="data/amplitude.tif",
|
|
244
|
+
tile_size=512, overlap=64,
|
|
245
|
+
generate_report=True,
|
|
246
|
+
)
|
|
247
|
+
result.save_geotiff("unwrapped_output.tif")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Training a model end to end:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
pyunwrap-train \
|
|
254
|
+
--train-hdf5 train_tiles.h5 --val-hdf5 val_tiles.h5 \
|
|
255
|
+
--epochs 60 --warmup-epochs 5 \
|
|
256
|
+
--finetune-hdf5 snaphu_pseudo_gt.h5 --finetune-start-epoch 55 \
|
|
257
|
+
--out-dir runs/pyunwrap_v1
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Notebooks
|
|
261
|
+
|
|
262
|
+
Two notebooks in [`notebooks/`](notebooks/) walk through the package
|
|
263
|
+
hands-on, checked in **pre-executed with real outputs** so they're readable
|
|
264
|
+
without running anything:
|
|
265
|
+
|
|
266
|
+
- [`01_training_pipeline.ipynb`](notebooks/01_training_pipeline.ipynb) —
|
|
267
|
+
the complete training chain: synthetic data, tiling, `AmbiguityNet` +
|
|
268
|
+
`Trainer`, training curves, and predicted-vs-ground-truth comparison.
|
|
269
|
+
- [`02_full_pipeline.ipynb`](notebooks/02_full_pipeline.ipynb) — the
|
|
270
|
+
complete end-to-end chain: a deformation-model gallery, tiling and
|
|
271
|
+
augmentation visualized, a full curriculum training run with every loss
|
|
272
|
+
component plotted, tiled inference on real GeoTIFFs, residue/Nyquist/error
|
|
273
|
+
analytics, Grad-CAM and Integrated-Gradients explainability, 3D and
|
|
274
|
+
interactive-map visualization, ONNX deployment with a numerical
|
|
275
|
+
PyTorch-vs-ONNX agreement check, and the final HTML report.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
pip install -e ".[dev,maps,notebooks]"
|
|
279
|
+
jupyter notebook notebooks/
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Testing
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
pytest -m "not slow" # fast unit + integration tests
|
|
286
|
+
pytest # full suite, including the end-to-end
|
|
287
|
+
# synthetic → train → infer → report test
|
|
288
|
+
pytest --cov=pyunwrap --cov-report=term-missing
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The suite includes known-answer physics tests (e.g. residue detection is
|
|
292
|
+
checked against a hand-constructed phase vortex with an exact, known
|
|
293
|
+
topological charge — not just "runs without crashing") and a tile-merging
|
|
294
|
+
regression test that asserts tiled-and-merged inference is *numerically
|
|
295
|
+
identical* to a whole-image pass, directly targeting the class of bug where
|
|
296
|
+
tile boundaries silently corrupt the output. See
|
|
297
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) for the full breakdown and development
|
|
298
|
+
setup.
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
## Project status
|
|
302
|
+
|
|
303
|
+
Early-stage and actively developed. The pipeline — synthetic data
|
|
304
|
+
generation, training, tiled inference, analytics, and reporting — is
|
|
305
|
+
implemented and tested end to end, but **no pretrained weights ship yet**;
|
|
306
|
+
`from_pretrained()` downloads from a Zenodo record you supply, and the
|
|
307
|
+
example notebooks train small demo models from scratch rather than loading
|
|
308
|
+
a benchmarked checkpoint. Monte Carlo Dropout uncertainty is currently a
|
|
309
|
+
no-op (`AmbiguityNet` has no `nn.Dropout` layers yet, only BatchNorm) —
|
|
310
|
+
tracked as a known gap, not hidden. APIs may change between minor versions
|
|
311
|
+
until `1.0`. See [`CHANGELOG.md`](CHANGELOG.md) for what's landed so far.
|
|
312
|
+
|
|
313
|
+
## Relationship to the wider EO stack
|
|
314
|
+
|
|
315
|
+
`pyunwrap` works standalone, but is designed to eventually sit downstream of
|
|
316
|
+
[`pygeofetch`](#) (interferogram acquisition/formation) and alongside
|
|
317
|
+
[`ps-gnn`](#) (persistent scatterer identification) in a broader open-source
|
|
318
|
+
InSAR processing stack.
|
|
319
|
+
|
|
320
|
+
## Citation
|
|
321
|
+
|
|
322
|
+
If `pyunwrap` is useful in your research, please cite it:
|
|
323
|
+
|
|
324
|
+
```bibtex
|
|
325
|
+
@software{pyunwrap2026,
|
|
326
|
+
title = {pyunwrap: Physics-Informed Deep Learning for InSAR Phase Unwrapping},
|
|
327
|
+
author = {{pyunwrap contributors}},
|
|
328
|
+
year = {2026},
|
|
329
|
+
url = {https://github.com/yourusername/pyunwrap},
|
|
330
|
+
note = {Version 0.1.0}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## References
|
|
335
|
+
|
|
336
|
+
- Goldstein, R. M., Zebker, H. A., & Werner, C. L. (1988). Satellite radar
|
|
337
|
+
interferometry: Two-dimensional phase unwrapping. *Radio Science*, 23(4).
|
|
338
|
+
- Itoh, K. (1982). Analysis of the phase unwrapping algorithm. *Applied
|
|
339
|
+
Optics*, 21(14).
|
|
340
|
+
- Chen, C. W., & Zebker, H. A. (2001). Two-dimensional phase unwrapping with
|
|
341
|
+
use of statistical models for cost functions in nonlinear optimization
|
|
342
|
+
(SNAPHU). *JOSA A*, 18(2).
|
|
343
|
+
- Okada, Y. (1985). Surface deformation due to shear and tensile faults in a
|
|
344
|
+
half-space. *Bulletin of the Seismological Society of America*, 75(4).
|
|
345
|
+
- Mogi, K. (1958). Relations between the eruptions of various volcanoes and
|
|
346
|
+
the deformations of the ground surfaces around them. *Bulletin of the
|
|
347
|
+
Earthquake Research Institute*, 36.
|
|
348
|
+
|
|
349
|
+
## Contributing
|
|
350
|
+
|
|
351
|
+
Contributions are welcome — bug reports, documentation, new deformation
|
|
352
|
+
models, or core improvements. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for
|
|
353
|
+
development setup, test/lint conventions, and the design principles worth
|
|
354
|
+
knowing before touching the physics-critical modules.
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-horizontal-dark.svg">
|
|
5
|
+
<img src="assets/logo-horizontal.svg" alt="pyunwrap" width="420">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
## ⚠️ Important Note
|
|
9
|
+
|
|
10
|
+
This project is **actively under development**. While the core functionality
|
|
11
|
+
is production-ready and thoroughly tested, some advanced features are still
|
|
12
|
+
being refined.
|
|
13
|
+
|
|
14
|
+
**Physics-informed deep learning for InSAR phase unwrapping.**
|
|
15
|
+
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](pyproject.toml)
|
|
18
|
+
[](tests/)
|
|
19
|
+
[](https://github.com/psf/black)
|
|
20
|
+
[](#project-status)
|
|
21
|
+
|
|
22
|
+
[Why this exists](#why-this-exists) ·
|
|
23
|
+
[How it works](#how-it-works) ·
|
|
24
|
+
[Install](#installation) ·
|
|
25
|
+
[Quickstart](#quickstart) ·
|
|
26
|
+
[Notebooks](#notebooks) ·
|
|
27
|
+
[Architecture](#architecture) ·
|
|
28
|
+
[Citation](#citation)
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
`pyunwrap` unwraps Interferometric Synthetic Aperture Radar (InSAR) phase — the
|
|
35
|
+
core measurement behind satellite-based ground-deformation monitoring — using a
|
|
36
|
+
**physics-informed U-Net that predicts an integer ambiguity map**, not the
|
|
37
|
+
unwrapped phase itself. The rewrapping identity is enforced by construction,
|
|
38
|
+
so the model is structurally incapable of producing an output that
|
|
39
|
+
contradicts the observed wrapped phase; it can only be wrong about *how many*
|
|
40
|
+
`2π` cycles were missed, never about the physics of wrapping.
|
|
41
|
+
|
|
42
|
+
## Why this exists
|
|
43
|
+
|
|
44
|
+
Every classical phase-unwrapping algorithm — branch-cut methods, minimum-cost
|
|
45
|
+
flow (the approach behind SNAPHU, the field's long-standing reference tool),
|
|
46
|
+
Goldstein's algorithm — rests on one assumption: that the true phase gradient
|
|
47
|
+
between adjacent pixels never exceeds `π` radians (the Nyquist/Itoh
|
|
48
|
+
condition). Two situations break that assumption in practice:
|
|
49
|
+
|
|
50
|
+
- **Low coherence.** Vegetation, water, and temporal decorrelation add
|
|
51
|
+
near-random phase noise. Once the local gradient becomes unreliable,
|
|
52
|
+
branch-cut and MCF methods don't just get that one pixel wrong — they
|
|
53
|
+
propagate the error across the connected region downstream of it.
|
|
54
|
+
- **Steep deformation gradients.** Earthquakes, volcanic inflation, and
|
|
55
|
+
mining subsidence can produce genuine phase gradients that exceed `π`
|
|
56
|
+
radians per pixel near the source. No amount of algorithmic cleverness
|
|
57
|
+
recovers this from the wrapped phase alone without an external prior —
|
|
58
|
+
which is exactly the gap a learned model can fill.
|
|
59
|
+
|
|
60
|
+
`pyunwrap` targets both regimes directly, and does so without inheriting the
|
|
61
|
+
most common failure mode of naive deep-learning approaches to this problem:
|
|
62
|
+
regressing the unwrapped phase directly gives a network no reason to respect
|
|
63
|
+
`wrap(prediction) == observed_phase`. Predicting the integer ambiguity
|
|
64
|
+
instead makes that identity a mathematical guarantee, not a hope.
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
`AmbiguityNet` outputs a continuous ambiguity prediction, rounds it via a
|
|
69
|
+
straight-through estimator, and reconstructs phase directly:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
φ̂ = ψ + 2π · round(k̂), k̂ ∈ ℤ
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Approach | Failure mode |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Regress `φ` directly | Nothing constrains `wrap(prediction) == ψ`; the network can output any real value |
|
|
78
|
+
| **Predict `k` (this package)** | `wrap(ψ + 2π·round(k̂)) == ψ` holds by construction — the network only has to get the *integer cycle count* right |
|
|
79
|
+
|
|
80
|
+
That single design decision shapes everything downstream: training uses a
|
|
81
|
+
physics-informed loss with a component that's provably near-zero regardless
|
|
82
|
+
of prediction quality (documented explicitly, not hidden — see
|
|
83
|
+
[`pyunwrap/models/losses.py`](pyunwrap/models/losses.py)); tiled inference
|
|
84
|
+
merges the **integer ambiguity map** across overlapping patches, never the
|
|
85
|
+
phase itself, because averaging phase directly across a tile boundary can
|
|
86
|
+
silently produce a value that satisfies no physical interferogram.
|
|
87
|
+
|
|
88
|
+
## Key features
|
|
89
|
+
|
|
90
|
+
**Synthetic data engine** ([`pyunwrap.synthetic`](pyunwrap/synthetic/generator.py))
|
|
91
|
+
— Gaussian subsidence bowls, a from-scratch Okada (1985) rectangular fault
|
|
92
|
+
dislocation model, a Mogi (1958) volcanic point source, DEM-driven
|
|
93
|
+
topographic phase, Kolmogorov-spectrum atmospheric turbulence, orbital
|
|
94
|
+
ramps, coherence-dependent decorrelation noise, and a pseudo-real strategy
|
|
95
|
+
that rewraps real L-band (ALOS-2) unwrapped phase into simulated C-band data
|
|
96
|
+
to help bridge the sim-to-real gap.
|
|
97
|
+
|
|
98
|
+
**`AmbiguityNet`** ([`pyunwrap.models`](pyunwrap/models/ambiguity_net.py))
|
|
99
|
+
— a ResNet-34-encoder U-Net with a dual head: the integer ambiguity map via
|
|
100
|
+
a straight-through-estimator rounding layer, and an auxiliary residue-
|
|
101
|
+
probability map for uncertainty. Trained with a four-component
|
|
102
|
+
physics-informed loss (ambiguity regression, re-wrap consistency,
|
|
103
|
+
coherence-weighted smoothness, ambiguity-map residue penalty).
|
|
104
|
+
|
|
105
|
+
**Curriculum training** ([`pyunwrap.training`](pyunwrap/training/trainer.py))
|
|
106
|
+
— a three-stage curriculum (high-coherence/low-gradient → moderate →
|
|
107
|
+
full difficulty), optional SNAPHU pseudo-ground-truth fine-tuning on real
|
|
108
|
+
data, AdamW with warmup/cosine annealing, and TensorBoard logging.
|
|
109
|
+
|
|
110
|
+
**Production inference** ([`pyunwrap.inference`](pyunwrap/inference/unwrapper.py))
|
|
111
|
+
— tiled processing of arbitrarily large interferograms with edge-aware,
|
|
112
|
+
residue-probability-weighted smart merging of the ambiguity map, Monte
|
|
113
|
+
Carlo Dropout uncertainty, and an ONNX Runtime → OpenVINO backend fallback
|
|
114
|
+
chain for deployment without a PyTorch dependency.
|
|
115
|
+
|
|
116
|
+
**Scientific analytics** ([`pyunwrap.analytics`](pyunwrap/analytics/))
|
|
117
|
+
— Goldstein-style residue detection and clustering, Nyquist
|
|
118
|
+
gradient-violation mapping, error-distribution statistics, Grad-CAM and
|
|
119
|
+
Integrated-Gradients explainability, and uncertainty-calibration
|
|
120
|
+
reliability diagrams.
|
|
121
|
+
|
|
122
|
+
**Visualization & reporting** ([`pyunwrap.visualization`](pyunwrap/visualization/))
|
|
123
|
+
— interactive Plotly 3D phase surfaces and heatmaps, a folium
|
|
124
|
+
swipe-comparison map, and a self-contained, Jinja2-templated HTML report
|
|
125
|
+
tying every stage together.
|
|
126
|
+
|
|
127
|
+
## Architecture
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
InSARSyntheticGenerator ──┐ real GeoTIFFs
|
|
131
|
+
│ (wrapped, coherence, amplitude)
|
|
132
|
+
▼ │
|
|
133
|
+
tiling + normalization ▼
|
|
134
|
+
│ tiled inference
|
|
135
|
+
▼ via PhaseUnwrapper
|
|
136
|
+
InSARTileDataset │
|
|
137
|
+
│ │
|
|
138
|
+
▼ ▼
|
|
139
|
+
Trainer.fit() ──── AmbiguityNet ──── smart ambiguity-map
|
|
140
|
+
(curriculum, (this is the merging (never
|
|
141
|
+
physics loss) same model) the phase)
|
|
142
|
+
│
|
|
143
|
+
▼
|
|
144
|
+
unwrapped phase +
|
|
145
|
+
analytics + report
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
See [`docs/architecture.md`](docs/architecture.md) for the full
|
|
149
|
+
module-by-module reference and a longer explanation of the physics
|
|
150
|
+
invariant every stage is built around.
|
|
151
|
+
|
|
152
|
+
## Installation
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
git clone https://github.com/yourusername/pyunwrap.git
|
|
156
|
+
cd pyunwrap
|
|
157
|
+
pip install -e .
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Optional extras, installed as needed:
|
|
161
|
+
|
|
162
|
+
| Extra | Adds | Use case |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `dev` | `pytest`, `pytest-cov`, `black`, `ruff`, `mypy` | Development, testing, linting |
|
|
165
|
+
| `maps` | `folium`, `leafmap` | Interactive map visualization |
|
|
166
|
+
| `deploy` | `openvino`, `onnx`, `onnxconverter-common`, `onnxscript` | ONNX export, OpenVINO inference |
|
|
167
|
+
| `notebooks` | `jupyter`, `ipykernel` | Running the example notebooks |
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pip install -e ".[dev,maps,deploy,notebooks]" # everything
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Quickstart
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from pyunwrap.synthetic.generator import InSARSyntheticGenerator
|
|
177
|
+
from pyunwrap.models.ambiguity_net import AmbiguityNet
|
|
178
|
+
from pyunwrap.inference.unwrapper import PhaseUnwrapper
|
|
179
|
+
|
|
180
|
+
# Generate a synthetic training sample (Mogi volcanic source deformation).
|
|
181
|
+
gen = InSARSyntheticGenerator(size=256, seed=42)
|
|
182
|
+
sample = gen.generate_sample(deformation_type="mogi")
|
|
183
|
+
|
|
184
|
+
# Run tiled inference on a real interferogram with a trained model.
|
|
185
|
+
model = AmbiguityNet(pretrained=False, k_max=10.0) # or load your own checkpoint
|
|
186
|
+
unwrapper = PhaseUnwrapper(model=model, device="cuda")
|
|
187
|
+
result = unwrapper.unwrap(
|
|
188
|
+
wrapped_phase_path="data/wrapped_phase.tif",
|
|
189
|
+
coherence_path="data/coherence.tif",
|
|
190
|
+
amplitude_path="data/amplitude.tif",
|
|
191
|
+
tile_size=512, overlap=64,
|
|
192
|
+
generate_report=True,
|
|
193
|
+
)
|
|
194
|
+
result.save_geotiff("unwrapped_output.tif")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Training a model end to end:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pyunwrap-train \
|
|
201
|
+
--train-hdf5 train_tiles.h5 --val-hdf5 val_tiles.h5 \
|
|
202
|
+
--epochs 60 --warmup-epochs 5 \
|
|
203
|
+
--finetune-hdf5 snaphu_pseudo_gt.h5 --finetune-start-epoch 55 \
|
|
204
|
+
--out-dir runs/pyunwrap_v1
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Notebooks
|
|
208
|
+
|
|
209
|
+
Two notebooks in [`notebooks/`](notebooks/) walk through the package
|
|
210
|
+
hands-on, checked in **pre-executed with real outputs** so they're readable
|
|
211
|
+
without running anything:
|
|
212
|
+
|
|
213
|
+
- [`01_training_pipeline.ipynb`](notebooks/01_training_pipeline.ipynb) —
|
|
214
|
+
the complete training chain: synthetic data, tiling, `AmbiguityNet` +
|
|
215
|
+
`Trainer`, training curves, and predicted-vs-ground-truth comparison.
|
|
216
|
+
- [`02_full_pipeline.ipynb`](notebooks/02_full_pipeline.ipynb) — the
|
|
217
|
+
complete end-to-end chain: a deformation-model gallery, tiling and
|
|
218
|
+
augmentation visualized, a full curriculum training run with every loss
|
|
219
|
+
component plotted, tiled inference on real GeoTIFFs, residue/Nyquist/error
|
|
220
|
+
analytics, Grad-CAM and Integrated-Gradients explainability, 3D and
|
|
221
|
+
interactive-map visualization, ONNX deployment with a numerical
|
|
222
|
+
PyTorch-vs-ONNX agreement check, and the final HTML report.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pip install -e ".[dev,maps,notebooks]"
|
|
226
|
+
jupyter notebook notebooks/
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Testing
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
pytest -m "not slow" # fast unit + integration tests
|
|
233
|
+
pytest # full suite, including the end-to-end
|
|
234
|
+
# synthetic → train → infer → report test
|
|
235
|
+
pytest --cov=pyunwrap --cov-report=term-missing
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The suite includes known-answer physics tests (e.g. residue detection is
|
|
239
|
+
checked against a hand-constructed phase vortex with an exact, known
|
|
240
|
+
topological charge — not just "runs without crashing") and a tile-merging
|
|
241
|
+
regression test that asserts tiled-and-merged inference is *numerically
|
|
242
|
+
identical* to a whole-image pass, directly targeting the class of bug where
|
|
243
|
+
tile boundaries silently corrupt the output. See
|
|
244
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) for the full breakdown and development
|
|
245
|
+
setup.
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
## Project status
|
|
249
|
+
|
|
250
|
+
Early-stage and actively developed. The pipeline — synthetic data
|
|
251
|
+
generation, training, tiled inference, analytics, and reporting — is
|
|
252
|
+
implemented and tested end to end, but **no pretrained weights ship yet**;
|
|
253
|
+
`from_pretrained()` downloads from a Zenodo record you supply, and the
|
|
254
|
+
example notebooks train small demo models from scratch rather than loading
|
|
255
|
+
a benchmarked checkpoint. Monte Carlo Dropout uncertainty is currently a
|
|
256
|
+
no-op (`AmbiguityNet` has no `nn.Dropout` layers yet, only BatchNorm) —
|
|
257
|
+
tracked as a known gap, not hidden. APIs may change between minor versions
|
|
258
|
+
until `1.0`. See [`CHANGELOG.md`](CHANGELOG.md) for what's landed so far.
|
|
259
|
+
|
|
260
|
+
## Relationship to the wider EO stack
|
|
261
|
+
|
|
262
|
+
`pyunwrap` works standalone, but is designed to eventually sit downstream of
|
|
263
|
+
[`pygeofetch`](#) (interferogram acquisition/formation) and alongside
|
|
264
|
+
[`ps-gnn`](#) (persistent scatterer identification) in a broader open-source
|
|
265
|
+
InSAR processing stack.
|
|
266
|
+
|
|
267
|
+
## Citation
|
|
268
|
+
|
|
269
|
+
If `pyunwrap` is useful in your research, please cite it:
|
|
270
|
+
|
|
271
|
+
```bibtex
|
|
272
|
+
@software{pyunwrap2026,
|
|
273
|
+
title = {pyunwrap: Physics-Informed Deep Learning for InSAR Phase Unwrapping},
|
|
274
|
+
author = {{pyunwrap contributors}},
|
|
275
|
+
year = {2026},
|
|
276
|
+
url = {https://github.com/yourusername/pyunwrap},
|
|
277
|
+
note = {Version 0.1.0}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## References
|
|
282
|
+
|
|
283
|
+
- Goldstein, R. M., Zebker, H. A., & Werner, C. L. (1988). Satellite radar
|
|
284
|
+
interferometry: Two-dimensional phase unwrapping. *Radio Science*, 23(4).
|
|
285
|
+
- Itoh, K. (1982). Analysis of the phase unwrapping algorithm. *Applied
|
|
286
|
+
Optics*, 21(14).
|
|
287
|
+
- Chen, C. W., & Zebker, H. A. (2001). Two-dimensional phase unwrapping with
|
|
288
|
+
use of statistical models for cost functions in nonlinear optimization
|
|
289
|
+
(SNAPHU). *JOSA A*, 18(2).
|
|
290
|
+
- Okada, Y. (1985). Surface deformation due to shear and tensile faults in a
|
|
291
|
+
half-space. *Bulletin of the Seismological Society of America*, 75(4).
|
|
292
|
+
- Mogi, K. (1958). Relations between the eruptions of various volcanoes and
|
|
293
|
+
the deformations of the ground surfaces around them. *Bulletin of the
|
|
294
|
+
Earthquake Research Institute*, 36.
|
|
295
|
+
|
|
296
|
+
## Contributing
|
|
297
|
+
|
|
298
|
+
Contributions are welcome — bug reports, documentation, new deformation
|
|
299
|
+
models, or core improvements. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for
|
|
300
|
+
development setup, test/lint conventions, and the design principles worth
|
|
301
|
+
knowing before touching the physics-critical modules.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
[MIT](LICENSE)
|