Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9dc899b
First pass at adding dem error/velocity estimation per link
scottstanie Jan 19, 2026
8a68903
clarify comments and add failing test
scottstanie Jan 20, 2026
d950d54
More work setting up DEM error estimation as a default feature
scottstanie Feb 17, 2026
930abb7
more attempts to bound search and make demo work
scottstanie Feb 17, 2026
4d8ab19
restore model after flattening
scottstanie Feb 17, 2026
d2214a9
Force lsqr float64
scottstanie Feb 18, 2026
e1894cc
Refactor to better test link param integration, refactor baseline war…
scottstanie Feb 19, 2026
3900992
Add DEM error theory page, tutorial notebook, and fix pre-commit issues
scottstanie Feb 19, 2026
2510cb8
Rename incidence angle to look angle, remove unnecessary properties
scottstanie Feb 19, 2026
42486ad
Fix DEM error sensitivity: use sin(theta) instead of cos(theta)
claude Feb 20, 2026
702f0ef
Replace scipy.optimize.brute with vectorized grid search
claude Feb 20, 2026
7370e0b
redo baseline CSV read for flexibility
Feb 20, 2026
b48ca72
Allow other check, add tests
Feb 20, 2026
85c84b2
Merge branch 'claude/review-dem-error-formula-31Ted' into add-dem-error
Feb 20, 2026
ff1dd36
First round of performance improve
scottstanie Feb 20, 2026
3bc8211
clarify logging
scottstanie Feb 20, 2026
a8b29fa
Fix multiprocessing memory explosion on macOS
scottstanie Feb 23, 2026
69efdf9
fix loading for other formats
scottstanie Mar 12, 2026
5adae52
Skip link param raster writing when outputs already exist
scottstanie Mar 13, 2026
e0531bc
Add --date-fmt CLI flag for time-of-day in SLC filenames
scottstanie Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ dmypy.json
# Cython debug symbols
cython_debug/

# Notebook data (symlinked datasets, not committed)
docs/notebooks/data/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
Expand Down
135 changes: 135 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Python Coding & Editing Guidelines

> **Living document – PRs welcome!**
> Last updated: 2026‑01‑15

## Table of Contents

1. Philosophy
1. Code Style
1. Docstrings & Comments
1. Tools
1. Documentation

---

## Philosophy

- **Readability, reproducibility, performance – in that order.**
- Prefer explicit over implicit; avoid hidden state and global flags.
- Measure before you optimize (`time.perf_counter`, `line_profiler`).
- Each module holds a **single responsibility**; keep public APIs minimal.
- Before merging pull requests, ensure that
- All tests pass
- All precommit checks pass
- Any new functionality has a new test with it
- Any bug fixes have a new test which fails on the main branch but passes on your PR

## Code Style

- Annotate all public functions (PEP 484).
- In general, write code that will raise an exception early if something isn't expected.
- Raise Exceptions/Errors for user-facing problems. Only use asserts to help fix mypy errors, or to show developer expectations.
- Use Pydantic models over `dataclasses.dataclass`.
- Aim for zero "dead" code: do not leave commented code in unless it is part of a very descriptive comment that illustrates something specific.
- Follow the "parse, don't validate" addage: Parse unknown inputs at the serialization boundaries, not scattered everywhere in the code.
- If you need to add an ignore, ignore a specific check like # type: ignore[specific] . Use sparingly.
- Don't write error handing code or smooth over exceptions/errors unless they are expected as part of control flow.
- Prefer `Protocol` over `ABC`s when only an interface is needed.
- Use `from loguru import logger` for logging instead of `print` statements (`logger.info`).

## Docstrings & Comments

- Style: NumPyDoc.
- Start with a one‑sentence summary in the imperative mood.
- Sections: Parameters, Returns, Raises, Examples, References.
- Use backticks for code or referring to variables (e.g. `xarray.DataArray`).
- Do not use emojis, or non-unicode characters in comments/print statements.
- Cite peer‑reviewed papers with DOI links when relevant.
- Write code that explains itself rather than needs comments.
- For the inline you do add, explain *why*, not what. For example, *don't* write:

```python
# open the file
f = open(filename)
```

- The comments should be things which are not obvious to a reader with typical background knowledge. Aim to write code that explains itself.


## Tools

- You can run `pre-commit run -a` to run all pre-commit hooks and check for style violations
- ruff is uses for most code maintenance, black for formatting, mypy for type checking, pytest for testing



## Documentation

- mkdocs + Jupyter. Hosted on ReadTheDocs.
- Auto API from type hints.
- Provide tutorial notebooks covering common workflows.
- Include examples in docstrings.
- Add high-level guides for key functionality.

---

## Codebase Architecture

SPURT implements Extended Minimum Cost Flow (EMCF) for 3D InSAR phase unwrapping. The algorithm decomposes 3D unwrapping into two sequential 2D MCF problems.

### Module Structure (`src/spurt/`)

| Module | Purpose |
|--------|---------|
| `graph/` | Graph representations (Delaunay, Hop3, Regular2D) for spatial/temporal domains |
| `mcf/` | Minimum Cost Flow solver (OR-Tools based) and utilities |
| `links/` | Per-link model estimation (DEM errors, velocities) via grid search |
| `workflows/emcf/` | Main EMCF algorithm orchestration, tiling, merging |
| `io/` | Input/output interfaces for SLC stacks and 3D data |
| `utils/` | Logging, CPU utilities, tiling helpers |

### Algorithm Flow

**Stage 1: Temporal Unwrapping** (`EMCFSolver.unwrap_gradients_in_time`)
- For each spatial edge (pixel-to-pixel link), unwrap phase gradients across interferograms
- Uses temporal graph `G_t` (typically Hop3 or Delaunay in time-baseline space)
- Outputs temporally-unwrapped spatial gradients

**Stage 2: Spatial Unwrapping** (`EMCFSolver.unwrap_gradients_in_space`)
- For each interferogram, unwrap spatial gradients using MCF
- Uses spatial graph `G_s` (typically Delaunay triangulation)
- Outputs unwrapped interferograms

### Key Files

- `mcf/_ortools.py` - Core MCF solver using OR-Tools (`ORMCFSolver`)
- `mcf/utils.py` - `phase_diff()`, `flood_fill()`, cost functions
- `links/_grid_search.py` - `GridSearchLinearModel` for velocity/DEM error estimation
- `links/_common.py` - Temporal coherence objective function
- `workflows/emcf/_solver.py` - `EMCFSolver` main algorithm class
- `graph/_delaunay.py` - Delaunay triangulation and regular 2D grid graphs
- `graph/_hop3.py` - Hop-3 temporal graph for narrow-baseline time-series

### Data Structures

**Edges/Links**: `(nedges, 2)` integer arrays indexing into points, always ordered `links[i, 0] < links[i, 1]`

**Dual Graph**: Maps primal edges to cycles they participate in:
- `dual_edges[i]` = `[cycle1_idx, cycle2_idx]` (1-indexed, 0 = boundary)
- `dual_edge_dir[i]` = `[+1/-1, +1/-1]` orientation within each cycle

**Design Matrix for Link Models**: `amat` of shape `(nifgs, ndim)` where:
- Column 0: temporal sensitivity (rad per velocity unit, e.g., mm/yr)
- Column 1: baseline sensitivity (rad per DEM error unit, e.g., meters)

### DEM Error / Velocity Estimation

The `links/` module provides per-link model estimation via `GridSearchLinearModel`:
1. Build design matrix `amat` from temporal baselines and perpendicular baselines
2. Define search ranges as `slice(start, stop, step)` for each parameter
3. For each edge, grid search + Nelder-Mead finds parameters maximizing temporal coherence
4. Model prediction: `model_phase = amat @ [velocity, dem_error]`
5. Use `phase_diff(data0, data1, model=model_phase)` to "flatten" edges before MCF

Integration point: `EMCFSolver.unwrap_gradients_in_time()` in `_solver.py` (lines 268-299)
1 change: 1 addition & 0 deletions docs/background-theory.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

1. [Notation](./theory/notation.md)
1. [Residue computation](./theory/residues.md)
1. [DEM error and velocity estimation](./theory/dem-error-velocity.md)
Loading