Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Keep .git: setuptools_scm derives the version from git metadata
build/
dist/
*.egg-info/
__pycache__/
*.pyc
.pytest_cache/
.coverage
.pixi/
pixi.lock
docs/
tests/cassettes/
tests/data/
scripts/
*.zip
*.parquet
.vscode/
.idea/
5 changes: 5 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
FORCE_COLOR: 3

jobs:
dist:
name: Distribution build
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v6
Expand All @@ -44,6 +48,7 @@ jobs:
permissions:
id-token: write
runs-on: ubuntu-latest
timeout-minutes: 15
if: github.event_name == 'release' && github.event.action == 'published'

steps:
Expand Down
93 changes: 89 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
name: Pytest, build docker image, push to GHCR

on: [pull_request, push]
on:
pull_request:
push:
branches: [main]
tags: ["v*"]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

env:
# https://github.com/pytest-dev/pytest/issues/2042#issuecomment-429289164
PY_IGNORE_IMPORTMISMATCH: 1

jobs:
lint:
name: Lint (pre-commit)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- uses: pre-commit/action@v3.0.1

pytest:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
# TODO: pixi has multi env support
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
defaults:
run:
shell: bash -l {0}
Expand All @@ -28,12 +49,76 @@ jobs:
uses: prefix-dev/setup-pixi@v0.9.4
with:
pixi-version: v0.48.2
cache: false
cache: true
cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
- name: Install environment
run: |
pixi install
pixi install -e test

- name: Test
run: |
pixi run -e test pytest --record-mode none
pixi run -e test pytest --record-mode none --cov-report=xml

- name: Upload coverage
uses: actions/upload-artifact@v5
with:
name: coverage-${{ matrix.os }}
path: coverage.xml
if-no-files-found: ignore

docker:
name: Build docker image, push to GHCR
needs: [pytest]
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v5
with:
# setuptools_scm needs the full history to compute the version
fetch-depth: 0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build (and push on main/tags)
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Smoke test image
run: |
IMAGE=$(echo "${{ steps.meta.outputs.tags }}" | head -n1)
if [ "${{ github.event_name }}" = "pull_request" ]; then
docker buildx build --load -t geepers:smoke .
IMAGE=geepers:smoke
fi
docker run --rm "$IMAGE" --help
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,12 @@ Thumbs.db
# pixi environments
.pixi
*.egg-info

# Local research scratch files (keep out of the package)
/*.zip
/midas.py
/hectorp_wrapper.py
scripts/*.parquet

# Generated GitHub Pages staging copy (see scripts/deploy-pages.sh)
scripts/web/
31 changes: 31 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build stage: build the wheel with setuptools_scm (needs git metadata)
FROM python:3.12-slim AS builder

RUN apt-get update && apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
COPY . .
RUN python -m pip install --no-cache-dir build \
&& python -m build --wheel --outdir /wheels

# Runtime stage
FROM python:3.12-slim

LABEL org.opencontainers.image.source="https://github.com/opera-adt/geepers" \
org.opencontainers.image.description="Download GPS data and compare to InSAR" \
org.opencontainers.image.licenses="Apache-2.0"

# rasterio/pyogrio/shapely ship manylinux wheels, so no system GDAL is needed
COPY --from=builder /wheels /wheels
RUN python -m pip install --no-cache-dir /wheels/*.whl matplotlib \
&& rm -rf /wheels

# Run as an unprivileged user; HOME must be writable for the GPS cache dir
RUN useradd --create-home --shell /bin/bash geepers
USER geepers
ENV HOME=/home/geepers
WORKDIR /work

ENTRYPOINT ["geepers"]
CMD ["--help"]
50 changes: 45 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,55 @@ print(df_many.head())
```

## Example: Comparing GPS and InSAR Data
The basic InSAR/GNSS comparison workflow is offered by the `geepers` command line tool.

The InSAR/GNSS comparison workflow is offered by the `geepers` command line tool:

```bash
geepers --los F33039_los_enu.tif --timeseries-files displacement_20160711_*tif --temporal-coherence-files temporal_coherence_*.tif --similarity-files phase_similarity*.tif
geepers \
--los F33039_los_enu.tif \
--timeseries-files displacement_20160711_*tif \
--temporal-coherence-files temporal_coherence_*.tif \
--similarity-files phase_similarity*.tif \
--insar-buffer-meters 100 \
--requirement-mm 3 0.5 \
--wavelength 0.2384 # NISAR L-band; omit for Sentinel-1
```

The results are saved in the current directory in the `GPS` folder by default.

(TODO: Example data prep for this)
Results are saved in the `GPS` folder by default: per-station time-series
and rate comparisons, plus the structure function (pairwise relative RMSE
vs station separation, checked against the requirement curve) and the
per-epoch network misfit. See
[How-To Guides](https://geepers.readthedocs.io/en/latest/how-to-guides/)
and the runnable
[validation notebook](docs/notebooks/gnss_insar_validation.ipynb).

## Analysis toolbox

Beyond data access and the comparison workflow, geepers ships analysis
modules for GNSS velocity fields (see
[Analysis modules](docs/analysis-modules.md) for usage and the
[tour notebook](docs/notebooks/geepers_tour.ipynb) for a runnable demo):

| Module | Purpose |
|---|---|
| `geepers.midas` | Robust MIDAS velocities (Blewitt et al., 2016) |
| `geepers.trend` | Velocities with realistic uncertainties under power-law + white noise (validated against HectorP); fast Whittle method and parallel `estimate_trend_many` for networks |
| `geepers.variability` | Temporal & spatial velocity-stability metrics, spatial structure function |
| `geepers.quality` | Gap percentage, station quality, reference selection |
| `geepers.steps` | Detection of uncatalogued jumps (AIC sliding window) |
| `geepers.cme` | Common-mode error estimation/removal (PCA/ICA) |
| `geepers.gps_imaging` | Robust weighted-median interpolation (Hammond et al., 2016 GPS Imaging port) |
| `geepers.collocation` | Least-squares collocation, ordinary kriging, plate-boundary separation |
| `geepers.euler` | Euler pole estimation and plate-motion prediction |
| `geepers.strain` | Strain-rate/rotation fields from gridded velocities |
| `geepers.validation` | GNSS-vs-InSAR validation: velocity scatter (MAD/RMSE/R²), structure function, semivariogram, per-epoch misfit |
| `geepers.synthetic` | Schema-valid synthetic networks and series for testing |

An interactive MapLibre viewer for UNR gridded time series is hosted at
**[opera-adt.github.io/geepers](https://opera-adt.github.io/geepers/)**
(globe view, velocity/vector overlays, plate boundaries, per-point time
series). See the [viewer docs](scripts/README.md) to run it locally or
build your own dataset.

### Working with Multiple Sources

Expand Down
Loading
Loading