Metadata-Version: 2.4
Name: xcheck
Version: 0.1.0
Summary: A package for cross-checking radargram layers using dot product similarity.
Author-email: Your Name <your.email@example.com>
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Scientific/Engineering :: Image Processing
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: numpy
Requires-Dist: matplotlib
Requires-Dist: scipy
Requires-Dist: pandas
Requires-Dist: cartopy

# XCheck — Consistency-Based Validation of Englacial Layer Annotations

A Python implementation of the radargram layer-matching framework from:

> Hassan, Muhammad Behroze; Tama, Bayu Adhi; Purushotham, Sanjay;
> Janeja, Vandana P. **XCheck: A Consistency-Based Validation Framework
> for Englacial Layers Annotations.**
> <https://par.nsf.gov/biblio/10673248> ·
> DOI: [10.1109/ICDMW69685.2025.00015](https://doi.org/10.1109/ICDMW69685.2025.00015)

XCheck validates whether annotated englacial layers are *consistent*
between two radargrams that observe the same ice — either two
**consecutive** flight segments (overlap found automatically via GPS time)
or two **intersecting** flight paths (crossing point supplied via a CSV).

---

## Features

- **GPS Overlap Detection**: Automatically finds temporally overlapping segments between two radargrams using log-transformed GPS timestamps.
- **Data Loading & Masking**: Loads `.mat` radargram and ground truth layer files and converts annotated layers into binary depth masks.
- **Geometric Alignment**: Adjusts radargram columns by surface elevation before comparison to correct for topographic variation.
- **Layer Continuity Matching**: Dot-product similarity algorithm that identifies corresponding layers between two radargrams at their crossing or overlap point.
- **Flexible Input**: Accepts a single radargram pair via CLI flags, a batch CSV of pairs, or a pre-computed intersections CSV.
- **Visualization**: Map plots of GPS overlap regions and side-by-side mask views with matched layer annotations.

---

## Installation

```bash
pip install xcheck
```

### NDH_PythonTools (required)

XCheck uses [NDH_PythonTools](https://github.com/nholschuh/NDH_PythonTools)
for `.mat` file loading and radar data processing. Because that repository
is not a standard pip package, it must be installed manually:

```bash
git clone https://github.com/nholschuh/NDH_PythonTools.git
```

Then add its **parent directory** to `PYTHONPATH` before running xcheck:

```bash
# Linux / macOS
export PYTHONPATH="/path/to/parent/of/NDH_PythonTools"

# Windows PowerShell
$env:PYTHONPATH = "C:\path\to\parent\of\NDH_PythonTools"
```

---

## Quick Start

### Layer matching only (no NDH_PythonTools needed)

```python
import numpy as np
from xcheck import match_layers_with_dot_product

m1 = np.zeros((6, 5)); m1[[1, 3, 5], :] = 1
m2 = np.zeros((6, 5)); m2[[1, 3, 5], :] = 1

matches = match_layers_with_dot_product(m1, m2, max_distance=1,
                                        window_size=3, dp_threshold=1)
print(matches)  # [(1, 1), (3, 3), (5, 5)]
```

### Python API (requires NDH_PythonTools + data)

```python
from xcheck import (
    load_and_process_radargram,
    load_log_gps_time,
    find_overlapping_intervals,
    extract_and_adjust_mask_columns_for_location,
    match_layers_with_dot_product,
)

# Load two consecutive radargrams
r1, m1, d1 = load_and_process_radargram("Data_20120429_01_026.mat",
                                         layer_dir="./Nick-layer-data-mat/",
                                         radar_dir="./Nick-raw-radargram-mat/")
r2, m2, d2 = load_and_process_radargram("Data_20120429_01_027.mat",
                                         layer_dir="./Nick-layer-data-mat/",
                                         radar_dir="./Nick-raw-radargram-mat/")

# Find GPS overlap and derive crossing point from midpoint
import numpy as np
log_t1 = load_log_gps_time("./Nick-raw-radargram-mat/Data_20120429_01_026.mat")
log_t2 = load_log_gps_time("./Nick-raw-radargram-mat/Data_20120429_01_027.mat")
overlaps1, overlaps2 = find_overlapping_intervals(log_t1, log_t2)

for iv1, iv2 in zip(overlaps1, overlaps2):
    mid1 = (iv1[0] + iv1[1]) // 2
    mid2 = (iv2[0] + iv2[1]) // 2
    mc1 = extract_and_adjust_mask_columns_for_location(
        r1['Longitude'][mid1], r1['Latitude'][mid1], r1, m1, d1, 20)
    mc2 = extract_and_adjust_mask_columns_for_location(
        r2['Longitude'][mid2], r2['Latitude'][mid2], r2, m2, d2, 20)
    matches = match_layers_with_dot_product(mc1, mc2)
    print(f"Matched layers: {matches}")
```

---

## CLI

### Single pair — consecutive (no crossing CSV needed)

```bash
xcheck --r1 Data_20120429_01_026.mat --r2 Data_20120429_01_027.mat \
       --layer_dir ./Nick-layer-data-mat/ \
       --radar_dir ./Nick-raw-radargram-mat/
```

### Single pair — intersecting (crossing looked up from CSV)

```bash
xcheck --r1 Data_20120330_01_004.mat --r2 Data_20120511_01_054.mat \
       --intersections_csv ./Intersections_2012.csv \
       --layer_dir ./Nick-layer-data-mat/ \
       --radar_dir ./Nick-raw-radargram-mat/
```

### Single pair — intersecting with explicit crossing coordinates

```bash
xcheck --r1 Data_20120330_01_004.mat --r2 Data_20120511_01_054.mat \
       --lat 78.909416 --lon -61.804612 \
       --layer_dir ./Nick-layer-data-mat/ \
       --radar_dir ./Nick-raw-radargram-mat/
```

### Batch mode (all pairs in a CSV)

```bash
xcheck --intersections_csv ./Intersections_2012.csv \
       --layer_dir ./Nick-layer-data-mat/ \
       --radar_dir ./Nick-raw-radargram-mat/
```

### All CLI arguments

| Argument | Default | Description |
|---|---|---|
| `--r1`, `--r2` | — | Single-pair mode: filenames of the two radargrams |
| `--lat`, `--lon` | — | Crossing coordinates for single-pair mode |
| `--csv_path` | — | Batch CSV with columns `Radargram 1`, `Radargram 2`, `Latitude`, `Longitude` |
| `--intersections_csv` | — | Pre-computed intersections CSV (used alone or to look up coordinates) |
| `--layer_dir` | required | Directory of layer `.mat` files |
| `--radar_dir` | required | Directory of raw radargram `.mat` files |
| `--layer_prefix` | `Layer_` | Filename prefix for layer files |
| `--cols_side` | 20 / 3 | Columns extracted each side of crossing (consecutive / non-consecutive) |
| `--max_distance` | 4 / 3 | Max depth-pixel gap between candidate layer rows |
| `--window_size` | 7 / 3 | Dot product window width (must be odd) |
| `--dp_threshold` | 4 / 1 | Minimum dot product score to confirm a match |
| `--plot_overlaps` | off | Plot GPS overlap regions on a map |

Default pairs shown as **consecutive / non-consecutive**.

---

## Default thresholds by radargram type

| Parameter | Consecutive | Non-consecutive |
|---|---|---|
| `cols_side` | 20 (41 columns) | 3 (7 columns) |
| `max_distance` | 4 px | 3 px |
| `window_size` | 7 | 3 |
| `dp_threshold` | 4 | 1 |

These are selected automatically. Override any of them via CLI flags.

---

## Methodological note

The GPS overlap detection compares **`log(GPS_time)`** between two
radargrams with an absolute tolerance of `1e-8`. Because
`d(log t) ≈ dt/t` and GPS times are ~1×10⁹ seconds, this tolerance
corresponds to matching raw timestamps within roughly 10 seconds.
Consecutive flight segments (e.g. `_026.mat` → `_027.mat`) share a
small tail/head of overlapping traces, which is where the crossing point
is derived. Non-consecutive intersecting radargrams do not share GPS
timestamps; their crossing point must be supplied via `--intersections_csv`
or `--lat`/`--lon`.

---

## Project structure

```
src/xcheck/
  xcheck.py      Core algorithms: loading, masking, GPS overlap,
                 column extraction, layer matching, plotting, CLI
  __init__.py    Public API exports
pyproject.toml   Build metadata and dependencies
README.md
```

---

## License

MIT
