MagPlotter is a magnetometer data processing and spatial field characterization platform. It converts raw CSV sensor logs into structured engineering analysis, visualizations, field maps, and per-run reports.
Designed for experimental and research workflows where raw magnetic field measurements must be cleaned, analyzed, mapped, and summarized in a reproducible format.
Time-Series Analysis
- Cleans and validates raw magnetometer CSV logs
- Computes field magnitude |B| and per-axis statistics
- Calculates heading using circular statistics
- Generates mean, variance, drift, and noise metrics
- Produces standardized engineering plots
Spatial Field Mapping (automatic when X/Y coordinate columns are detected)
- Detects spatial coordinate columns automatically (flexible naming)
- Interpolates scattered measurements onto a uniform grid (scipy)
- Generates 2D heatmap, contour map, vector field, and 3D surface plots
- Computes 21 engineering metrics: peak field, uniformity, gradient, magnetic center, symmetry, radial falloff, and more
- Exports interactive HTML maps (Plotly) for zoom/pan/hover inspection
git clone https://github.com/evankinsel/magplotter
cd magplotter
pip install -r requirements.txtcp your_data.csv incoming/
# Process all CSVs in incoming/ once python main.py # Watch incoming/ and auto-process new files as they arrive python main.py --watch # Process a specific project folder python main.py /path/to/project
output/
└── your_data/
├── field_strength_plot.png
├── heading_plot.png
├── axes_plot.png
├── field_heatmap.png # spatial runs only
├── field_contours.png # spatial runs only
├── vector_field.png # spatial runs with Bx/By
├── field_surface.png # spatial runs only
├── interactive_heatmap.html # spatial runs only (requires plotly)
├── interactive_surface.html # spatial runs only (requires plotly)
├── field_metrics.json # spatial runs only
├── cleaned_data.csv
├── summary.json
└── report.txt
MagPlotter also works as a library — the CLI above is one client of the same backend. Call
run_pipeline and inspect the returned PipelineResult; no other import is needed.
from magplotter import run_pipeline result = run_pipeline(input_csv="example.csv", output_directory="output") if result.success: print(result.summary_statistics, result.generated_figures) else: for err in result.errors: print(err.title, err.explanation)
See magplotter/README.md for the full API reference (progress callbacks,
config overrides, error types) and examples/run_pipeline_example.py
for a runnable example.
time,mx,my,mz 0.00,12.24,28.08,8.30 0.05,12.16,28.14,8.42
Column aliases accepted: t, timestamp, x/bx/mag_x for mx, etc.
X,Y,B 0,0,100.2 10,0,95.8 20,0,89.3
X_mm,Y_mm,Bx,By,Bz 0,0,10.1,5.2,2.3 10,0,9.6,4.9,2.1
X,Y,Z,Bmag 0,0,0,100.2 10,0,0,95.8
Spatial column aliases (case-insensitive):
| Axis | Recognized names |
|---|---|
| X | x, X, pos_x, position_x, x_mm, x_cm, x_m, col_x, x_pos, posx |
| Y | y, Y, pos_y, position_y, y_mm, y_cm, y_m, col_y, y_pos, posy |
| Z | z, Z, pos_z, position_z, z_mm, z_cm, z_m, col_z, z_pos, posz |
Field column aliases:
| Type | Recognized names |
|---|---|
| Magnitude | B, Bmag, b_mag, magnitude, field, field_strength, b_total, |B| |
| Components | Bx/By/Bz, b_x/b_y/b_z, field_x/field_y/field_z |
When only vector components are present, magnitude is computed as √(Bx2 + By2 + Bz2).
MagPlotter also handles comment lines (#, //), extra columns, and malformed rows gracefully.
Field mapping runs automatically whenever X and Y coordinate columns are detected. No configuration required.
| File | Contents |
|---|---|
field_heatmap.png |
2D magnitude heatmap (viridis, measurement points overlaid) |
field_contours.png |
Filled contours with labels, peak (+) and minimum (×ばつ) marked |
vector_field.png |
Quiver arrows over magnitude background (when Bx/By available) |
field_surface.png |
3D surface — height encodes |B| |
interactive_heatmap.html |
Plotly heatmap with zoom/pan/hover |
interactive_surface.html |
Plotly 3D surface with rotation |
field_metrics.json |
Full characterization metrics (see below) |
| Metric | Description |
|---|---|
peak_field_strength |
Maximum |B| on the grid (μT) |
average_field_strength |
Mean |B| across valid grid cells |
minimum_field_strength |
Minimum |B| on the grid |
field_std / field_variance |
Statistical spread of field values |
field_uniformity_pct |
(1 − std/mean) ×ばつ 100 — 100% = perfectly uniform |
hot_spot |
Location and value of peak field |
cold_spot |
Location and value of minimum field |
magnetic_center |
|B|-weighted centroid of the field distribution |
gradient_stats |
Max/mean gradient magnitude and peak gradient location |
uniform_region_fraction |
Fraction of scan area where B ≥ mean − std |
coverage_area / coverage_fraction |
Area and fraction of scan with valid data |
field_distribution_histogram |
20-bin histogram of field values |
radial_falloff_profile |
Mean B vs distance from magnetic center |
symmetry_score |
0–1 field symmetry estimate (1 = perfectly symmetric) |
FIELD MAP SUMMARY
----------------------------------------
Spatial columns : X=X_mm, Y=Y_mm (3D=False, vector=True)
Interpolation : linear grid ×ばつ100
Peak Field : 100.200 μT
Average Field : 92.829 μT
Minimum Field : 82.100 μT
Std Deviation : 3.433 μT
Uniformity : 96.3 %
Hot Spot : (0.000, 0.000) B=100.200 μT
Magnetic Center : (9.820, 9.894)
Max Gradient : 1.405 μT/mm
Mean Gradient : 0.681 μT/mm
Coverage Area : 408.122 mm2
Symmetry Score : 0.942
field_mapping: enabled: true interpolation: method: linear # nearest | linear | cubic grid_resolution: 100 # grid points per axis [10–1000] heatmap: enabled: true colormap: viridis contour: enabled: true levels: 15 vector_field: enabled: true density: 20 # quiver arrows per axis normalize: false # true = direction only, false = length encodes strength surface: enabled: true interactive: enabled: true # requires: pip install plotly
If the file is absent, all features default to enabled with sensible values.
magplotter/
├── main.py # CLI entrypoint (batch or --watch)
├── config/
│ └── config.yaml # All runtime config (field mapping, interpolation, plots)
├── src/
│ ├── file_manager.py # File discovery and folder management
│ ├── report_generator.py # report.txt generation
│ └── field_mapping/ # Spatial field mapping subsystem
│ ├── __init__.py # Orchestrator: run_field_mapping()
│ ├── detector.py # Coordinate/field column detection
│ ├── interpolator.py # scipy griddata → uniform grid
│ ├── heatmap.py # 2D magnitude heatmap
│ ├── contour.py # Filled contour map
│ ├── vectorfield.py # Quiver vector field
│ ├── surface.py # 3D surface plot
│ ├── metrics.py # Engineering characterization metrics
│ └── export.py # Plotly interactive HTML
├── sensor_lab/
│ ├── clean.py # CSV parsing and cleaning
│ ├── analysis.py # Physics metrics and circular statistics
│ ├── viz.py # Time-series plot generation
│ ├── processor.py # Pipeline orchestrator
│ └── watcher.py # File system watching
├── tests/
│ ├── test_detector.py
│ ├── test_interpolator.py
│ ├── test_heatmap.py
│ └── test_metrics.py
├── incoming/ # Drop CSV files here
├── processed/ # Archive of processed CSVs
├── output/ # Per-run output folders
└── requirements.txt
CSV
└─ Validation & Cleaning (sensor_lab/clean.py)
└─ Time-Series Analysis (sensor_lab/analysis.py)
└─ TS Plots (sensor_lab/viz.py)
└─ Field Mapping (src/field_mapping/) ← skipped if no X/Y columns
└─ Report (src/report_generator.py)
└─ Archive to processed/
Field Magnitude — |B| = √(mx2 + my2 + mz2)
B_mean— average field strengthB_std— variation (noise floor indicator)B_drift— change from first to last sample (±0 = stable)
Heading — θ = arctan2(my, mx) using circular statistics
heading_mean_deg— dominant compass direction (0–360°)heading_std_deg— angular stability (smaller = steadier)
Noise Metric — mean standard deviation across all three axes; smaller = quieter sensor.
Uniformity — (1 − σ/μ) ×ばつ 100%. 100% means a perfectly flat field; lower values indicate a non-uniform distribution.
Magnetic Center — the |B|-weighted centroid. Differs from geometric center when the field is asymmetric — useful for locating the effective source position.
Gradient — computed via numpy on the interpolated grid. High gradient regions have rapidly changing field strength; important for applications sensitive to field uniformity.
Symmetry Score — compares each grid cell against its 180°-rotated counterpart through the magnetic center. Score of 1.0 = perfectly symmetric.
Radial Falloff — mean |B| in radial bands from the magnetic center. Reveals whether the field follows a dipole-like 1/r3 decay or a more complex profile.
cp lab_run_20240618.csv incoming/ python main.py cat output/lab_run_20240618/report.txt
cp magnet_scan.csv incoming/ # columns: X_mm, Y_mm, Bmag
python main.py
open output/magnet_scan/field_heatmap.png
open output/magnet_scan/interactive_heatmap.html
cat output/magnet_scan/field_metrics.jsonpython main.py --watch # In another terminal: cp new_scan.csv incoming/ # Watcher detects and processes automatically
cp ~/experiments/*.csv incoming/ python main.py # Each produces its own output/ subfolder
"Found 0 CSV files" — ensure files are in incoming/ with a .csv extension.
Field mapping not running — check that your CSV has X and Y columns (any of the supported aliases). Run python main.py and look for the detector: log line.
Plots don't generate on a headless server:
export MPLBACKEND=Agg
python main.pyInteractive HTML not generated — install Plotly:
pip install plotly
Sparse data warning / high NaN fraction — fewer than ~10 measurement points spread across the scan area will produce a poor interpolation. Switch to method: nearest in config/config.yaml for sparse datasets.
CSV parsing errors — MagPlotter skips malformed rows automatically. If all rows are skipped, check that at least 3–4 numeric columns are present.
python -m pytest tests/ -v # run the full suite python -m pytest tests/ --cov=sensor_lab --cov=src --cov-report=term-missing # with coverage
300+ tests covering the full pipeline plus the public API, with no hardware
or real filesystem state required — everything runs against in-memory
DataFrames and pytest's tmp_path:
| File | Covers |
|---|---|
test_schema.py |
Ingestion schema validation: valid/missing/malformed/duplicate columns, bad dtypes |
test_clean.py |
Comment stripping, header detection, invalid-value handling, key-value format |
test_transform.py |
Magnitude/heading derived columns |
test_analysis.py |
Axis stats, circular statistics, gradients, noise, compute_all_metrics |
test_processor.py |
Per-run orchestration, directory creation, run_id versioning, progress callback, config/archive_source overrides |
test_pipeline.py |
End-to-end stage chaining and batch CLI processing |
test_reports.py |
report.txt / JSON metrics / interactive HTML generation |
test_config.py |
src/config.py: defaults, YAML merge, validation, env overrides, immutability, caching |
test_errors.py |
src/errors.py: structured exception hierarchy, to_dict(), from_exception() wrapping |
test_api.py |
magplotter.run_pipeline / PipelineResult: success/failure paths, progress checkpoints, config overrides, archive_source/output_directory semantics |
test_detector.py, test_interpolator.py, test_heatmap.py, test_metrics.py |
Field-mapping subsystem (spatial detection, interpolation, maps, metrics) |
Core processing modules (sensor_lab/clean.py, processor.py, analysis.py,
transform.py, viz.py, src/config.py, src/errors.py, magplotter/) are
all at 91–100% line coverage. sensor_lab/watcher.py and src/logger.py are
largely I/O/filesystem-event glue and are exercised only at the seams (own
low coverage is expected there).
- Independent field mapping reader —
src/field_mappingreads the raw CSV directly (bypassingsensor_lab/clean.py's column aliasing) to preserve all spatial columns. - Non-destructive integration — field mapping is a try/except block in
processor.py; any failure skips mapping and continues the rest of the pipeline. - Extensible schema —
FieldSchemacaptures all detected column names; adding new column aliases only requires updating the pattern lists indetector.py. - Designed for future expansion — the architecture is ready for 3D volumetric reconstruction, live sensor streaming, Hall sensor characterization, motor/Halbach array mapping, and AS5600/BNO055 integration.
| Package | Purpose |
|---|---|
numpy, pandas |
Data handling |
matplotlib |
All static plots |
scipy |
Spatial interpolation (field mapping) |
plotly |
Interactive HTML exports (optional) |
watchdog |
File system watching (--watch mode) |
pyarrow |
Parquet output |
pyyaml |
config/config.yaml parsing |
pytest |
Test suite |
See LICENSE.