Skip to content

rithick007/radarLLM

Repository files navigation

RadarPhi — Marine Radar Target Localization

License: MIT Python TensorFlow

Marine radar small-target range-gate localization on the IPIX Dartmouth 1993 dataset. Single self-contained pipeline: builds the dataset from raw CDF files, trains an HH-polarization Vision Transformer, and evaluates it against a classical STDBA baseline with honest, leakage-controlled time-block splits.


Quick start

pip install -r requirements.txt

python radarphi.py --step build      # CDF → RD maps in dataset_v2/
python radarphi.py --step train      # trains model, saves to results_v2/checkpoints/
python radarphi.py --step evaluate   # metrics + plots → results_v2/
python radarphi.py                   # all three in sequence

python radarphi.py --loocv 19931110_001635   # leave-one-file-out honest test
python test_matfile.py --mat path/to/file.mat  # OOD inference on external .mat

Mac M1: pin numpy<2 and uninstall tensorflow-metal — see macOS M1 TensorFlow Fix below.


Folder layout

radarphi.py           ← main pipeline (build / train / evaluate)
test_matfile.py       ← OOD inference on external .mat files
requirements.txt
Dataset/              ← put the .cdf files here (2.cdf … 15.cdf is fine)
dataset_v2/           ← generated RD maps, labels.csv, meta.json
results_v2/           ← test_metrics.json, plots, checkpoints
tools/                ← analysis & visualisation scripts
reports/              ← PDF reports

Only two things are strictly required: radarphi.py and the IPIX .cdf files. Labels are read from inside each file via Data_collection_date, so filenames can be anything.


Outputs

Path Contents
dataset_v2/labels.csv filename, split, det, primary, g0…g13 per RD map
dataset_v2/meta.json global norm stats, det counts, pos_cell_rate
results_v2/test_metrics.json all evaluation numbers
results_v2/checkpoints/best.weights.h5 best checkpoint
results_v2/roc_curve.png ROC curve
results_v2/training_history.png loss/AUC curves

What the metrics mean

  • per-cell AUC (Transformer head) — target gate vs clutter gate within the same recording. This is the headline scientific metric.
  • map-level AUCmax(loc_tr) over 14 gates; trivially high (~1.0) because 11/12 usable files are target-bearing. Not the headline number.
  • within-1-gate localization (91.7%) — more informative than primary-gate hit (41.5%) because the model sometimes peaks at a documented secondary gate.
  • naive baseline (81.8%) — always predicts the most common target gate. The model must beat this to show it learned something real.

Analysis tools (tools/)

Script Purpose
tools/target_check.py RD map + per-gate amplitude bar chart for any set of files
tools/make_comparison.py Side-by-side Python vs MATLAB Figure 2 comparison
tools/matlab_match.py Full Python replication of mentor's MATLAB cdf.m pipeline
tools/preview_rd_maps.py Jet/parula RD map previews for all 14 CDF files
tools/generate_report.py Generates full PDF target analysis report
python tools/target_check.py                          # default 4-file comparison
python tools/target_check.py --files 135603 001635    # specific files
python tools/make_comparison.py --file Dataset/19931108_213827_starea.cdf
python tools/matlab_match.py --file Dataset/19931110_001635_starea.cdf --n 105
python tools/preview_rd_maps.py --cmap turbo
python tools/generate_report.py

Dataset & labels

IPIX_TARGETS in radarphi.py is the ground-truth label database, keyed by YYYYMMDD_HHMMSS timestamp (read from CDF metadata, not filename). 11 of 14 stare files contain the 1 m floating-sphere target: primary cell 7 for Nov 8–18 files, cell 9 for Nov 7 files. Two files (19931108_213827, 19931118_035737) are excluded pending verification. One file (19931107_145028) and the surveillance file are genuine negatives.



Design & Critical Review

This section documents the known failure modes of v1 and the design decisions made in v2 to fix them.

TL;DR — what was wrong in v1

The headline 98.8% / AUC 0.998 in v1 was not target detection. The model was learning "which recording did this map come from". Two bugs caused this:

  1. Mislabeled files. Files the code marked "no target" actually contain the target in a documented range cell.
  2. Temporal split leakage. Windows were split randomly; at PRF 1000 Hz, windows 0.256 s apart are highly correlated.

1a. Label confound

Positives came from exactly 2 files, negatives from 12 others, all labeled at the file level. The optimizer's easiest path was to fingerprint the two positive recordings — their noise floor, DC offset, sea state — none of which is "the target."

Fix: per-gate labels. Within a target file the target cell(s) = positive; the other ~11 cells = negative. Positives and negatives now come from the same recording, so file-fingerprinting is useless.

1b. Temporal leakage in the split

Windows were shuffled randomly across train/val/test. Window k (train) and k+1 (test) are 0.256 s apart and highly correlated.

Fix: contiguous time-block split per file (first 70% → train, next 15% → val, last 15% → test). Never shuffle temporally adjacent windows across splits.

1c. Range-shift augmentation

The augmentation rolled the RD map but not the label vector, so the model could ignore the augmented positions.

Fix: augment_pair rolls both the RD map and the 14-gate label vector by the same amount. This coupling must be preserved.

Architecture (v2)

Input (512, 14, 4)
  → CNN backbone: 4× [Conv2D → BN → ReLU → MaxPool(Doppler only)]
      Doppler 512→8, Range kept at 14, Channels →128
  → Permute+Reshape → 14 tokens of dim 1024
      ├── det_cnn:  Dense(64,GELU)→Dense(1,sigmoid)  per gate  [no cross-gate]
      └── loc_tr:   Dense(128)→RangePosEmbed→4×EncoderBlock→Dense(1,sigmoid)  [Transformer]

The Transformer head uses self-attention across range tokens — a cell looks like a target relative to its neighbours, which is exactly what amplitude-fluctuation statistics measure.

Three invariants — do not break

  1. Per-gate labels (not per-file). IPIX_TARGETS maps each file to a primary + secondary gate vector.
  2. Contiguous time-block splits. Windows assigned by temporal position, never randomly.
  3. Range-shift augmentation moves the label. augment_pair rolls both map and gvec by the same amount.


Are there really 11 target files? — Evidence

Short answer: Yes. The "only 2 targets" came from a hand-written assumption in v1's label table. Three independent lines of evidence confirm 11 files contain the target.

1. What the CDF metadata actually says

The only target-relevant global attribute is Field_dataset_name — it is just an experiment path index, not a target flag. Your v1 code mapped a few path names to label=0 by hand. That is the sole origin of "2 targets."

CDF file Field_dataset_name shape
19931106_183151_surv /data/Nov6/surv1 (8000,184,4) — surveillance
19931107_135603_starea /data/Nov7/starea1 (131072,2,14,4)
19931107_141630_starea /data/Nov7/starea2 (131072,2,14,4)
19931107_145028_starea /data/Nov7/starea3 (131072,2,14,4)
19931108_213827_starea /data/Nov8/starea0 (131072,2,14,4)
19931108_220902_starea /data/Nov8/starea0 (131072,2,14,4)
19931109_191449_starea /data/Nov9/starea1 (131072,2,14,4)
19931109_202217_starea /data/Nov9/starea2 (131072,2,14,4)
19931110_001635_starea /data/Nov9/starea5 (131072,2,14,4)
19931111_163625_starea /data/Nov11/starea0 (131072,2,14,4)
19931118_023604_stareC /data/Nov17/stareC2 (131072,2,14,4)
19931118_035737_stareC /data/Nov17/stareC4 (131072,2,14,4)
19931118_162155_stareC /data/Nov18/stareC0 (131072,2,14,4)
19931118_162658_stareC /data/Nov18/stareC1 (131072,2,14,4)
19931118_174259_stareC /data/Nov18A/stareC2 (131072,2,14,4)

Note: two different files share /data/Nov8/starea0 — this field was not kept unique, confirming it cannot serve as a target label.

2. Physical proof from raw data (STDBA test)

A floating sphere bobbing on the swell makes the backscattering amplitude fluctuate more in the target cell. Per-gate coefficient of variation: std(|HH|)/mean(|HH|) over all 131,072 pulses.

File Lit. primary cell STDBA argmax within ±1? contrast ratio
135603 9 9 9.0×
141630 9 9 4.7×
220902 7 7 2.1×
191449 7 6 1.3×
202217 7 7 3.3×
001635 7 7 3.3×
163625 7 8 3.3×
023604 7 8 1.1×
162155 7 7 1.6×
162658 7 7 4.5×
174259 7 7 4.3×
Match 11/11

In all 11 files the amplitude-fluctuation peak falls within one cell of the published target cell. For pure clutter the value is flat; it is not — it spikes exactly where the literature says the target is.

Caveats: 191449 (1.3×) and 023604 (1.1×) have weak HH contrast; HV/VH polarisations are stronger for these. Labels are correct but these are the hardest examples.

3. Published literature

Source Files confirmed
McMaster IPIX Dartmouth database (Bakker & Currie, 2001) All canonical files; defines primary cell 7 / cell 9 for Nov 7
Zhou et al., arXiv:2009.04185 — Table I lists TCR per file 135603, 220902, 191449, 202217, 001635, 163625, 023604, 162155, 162658, 174259
Sensors 2022 (PMC9740436) — Table 1 target cells IPIX_01=191449→cell 7; IPIX_02=220902→cell 7; IPIX_03=141630→cell 9
Shui, Li & Xu, IEEE T-AES 50(2), 2014 Primary cell 7 (Nov 8–18), cell 9 (Nov 7)

The decisive citation is Zhou et al., Table I: it explicitly tabulates the files v1 called "clutter" as target recordings with measured TCR values.

Reproduce the STDBA test yourself

python3 - <<'PY'
from scipy.io import netcdf_file; import numpy as np
ds = netcdf_file("Dataset/19931110_001635_starea.cdf", 'r', mmap=False)
adc = ds.variables['adc_data'].data; ds.close()
amp = np.abs(adc[:, 0, :, 0].astype(float) + 1j * adc[:, 0, :, 1].astype(float))
cv = amp.std(0) / (amp.mean(0) + 1e-9)
print("per-gate CV:", np.round(cv, 3), "  peak gate (1-idx):", cv.argmax() + 1)
PY


macOS M1 TensorFlow Fix

A segfault on M1 is almost never your Python code — it is a library mismatch. Do these steps top to bottom.

Step 0 — confirm native arm64 Python

python3 -c "import platform; print(platform.machine())"
# expected: arm64
# if x86_64: install Miniforge arm64

Step 1 — pin NumPy below 2.0 (the #1 cause)

TensorFlow wheels are compiled against NumPy 1.x C ABI. NumPy 2.x breaks it.

pip install "numpy<2"
python3 -c "import numpy; print(numpy.__version__)"  # must start with 1.

Step 2 — remove tensorflow-metal (the #2 cause)

pip uninstall -y tensorflow-metal

The code already forces CPU where needed. Add metal back only after CPU works.

Step 3 — known-good TF build

pip install "numpy<2" scipy pandas scikit-learn matplotlib
pip install tensorflow==2.16.2
python3 -c "import tensorflow as tf; print(tf.__version__)"

Step 4 — narrow down if still crashing

python3 -X faulthandler radarphi.py --step train
python3 radarphi.py --step build   # pure NumPy/SciPy — should never segfault
python3 radarphi.py --step train   # this is the TF part

One-line summary for your mentor: "The segfault was an Apple-Silicon library mismatch (NumPy 2.x ABI + metal GPU plugin), not a model bug. Pinning numpy<2, running on CPU, and removing py_function from the data pipeline fixes it."


Sources

About

Marine-radar small-target range-gate localization on the IPIX 1993 dataset — HH-polarization Vision Transformer with leakage-controlled time-block splits and a classical STDBA baseline.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages