Multi-Station Diagnostic Panels#

pycsamt.emtools.plot is the multi-station plotting layer for response diagnostics. It is the place to go when one station at a time is too narrow, but a map or pseudo-section is too compressed.

The module covers five common plotting jobs:

  • compact apparent-resistivity and phase panels for many stations;

  • raw full-tensor station groups;

  • response plus tipper quality-control panels;

  • before/after comparison figures;

  • measured-versus-predicted fit grids with per-component RMS labels.

Full callable signatures live in the API reference. This page explains when to use each figure, which arguments matter, and how to build reproducible plotting scripts.

Where This Module Fits#

Use pycsamt.emtools.plot after loading and inspecting data. It does not replace the first-look inventory tools in inspect or the specialized tensor/impedance diagnostics. Instead, it gives dense station-group figures for QC, comparison, and reporting.

Function

Best Use

Figure Shape

plot_sites_panels

Quick rho/phase panels for selected stations.

One station per small two-row group.

plot_raw_sites_1d

Full-tensor raw-data review.

One station group, one column per component.

plot_response_tipper

Joint impedance and tipper QC.

Rho/phase rows plus Tx/Ty rows.

plot_sites_compare

Raw vs processed or before vs after.

Paired columns per station.

plot_sites_fit_grid

Observed vs predicted inversion-response review.

Component columns with RMS labels.

All functions accept a path, Sites object, collection, or compatible iterable. Internally they normalize inputs with ensure_sites unless a function has a special duplicate-preservation mode.

Load Once, Plot Many#

Normalize the survey once, then pass the resulting object into each plot. This makes scripts faster and keeps failure points obvious.

 1from pathlib import Path
 2
 3from pycsamt.emtools import ensure_sites
 4
 5survey = ensure_sites(
 6    Path("data/AMT/WILLY_DATA/L18PLT"),
 7    recursive=True,
 8    on_dup="replace",
 9    strict=True,
10    verbose=1,
11)
12
13stations = ["18-001A", "18-007U", "18-016A", "18-018A"]

Use a short station list for most figures. The functions can plot many stations, but readability falls quickly when every panel has multiple components and legends.

Quick Station Panels#

plot_sites_panels is the simplest overview. Each station gets two stacked panels: apparent resistivity or impedance magnitude on top, and phase below. The default components are "xy" and "yx".

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools import ensure_sites, plot_sites_panels
 4
 5survey = ensure_sites("data/AMT/WILLY_DATA/L18PLT", strict=True)
 6
 7fig = plot_sites_panels(
 8    survey,
 9    stations=["18-001A", "18-007U", "18-016A", "18-018A"],
10    components=("xy", "yx"),
11    quantity="rhoa",
12    x_axis="period",
13    ncols=4,
14    show_error_bars=True,
15    show_legend=True,
16)
17
18fig.savefig("l18plt_station_panels.png", dpi=200)
19plt.close(fig)
../../_images/user-guide-emtools-plot-02.png

Use this when you want a quick visual sweep of several stations. It is not meant to show every tensor component or tipper row. For that, use plot_raw_sites_1d or plot_response_tipper.

Choose Rho Or Impedance Magnitude#

Most field reports use apparent resistivity, but sometimes you need to look directly at impedance magnitude. Set quantity="impedance" to plot log10(abs(Z)) instead of log10(rho_a).

 1from pycsamt.emtools import plot_sites_panels
 2
 3fig = plot_sites_panels(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    stations=["18-001A", "18-016A"],
 6    components=("xx", "xy", "yx", "yy"),
 7    quantity="impedance",
 8    phase_range=(-180.0, 180.0),
 9    ncols=2,
10    show_legend=True,
11)
12
13fig.savefig("impedance_magnitude_panels.png", dpi=200)
../../_images/user-guide-emtools-plot-03.png

Use quantity="impedance" for tensor debugging, instrument checks, or comparison with diagnostics that work directly with Z. Use quantity="rhoa" for ordinary geophysical response plots.

Phase Range And X Axis#

The high-level panel function has explicit x_axis and phase_range arguments.

 1from pycsamt.emtools import plot_sites_panels
 2
 3fig = plot_sites_panels(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    stations=["18-001A", "18-007U"],
 6    components=("xy", "yx"),
 7    x_axis="frequency",
 8    phase_range=(0.0, 360.0),
 9    ylim_phase=(0.0, 360.0),
10    ncols=2,
11)
12
13fig.savefig("frequency_axis_phase_0_360.png", dpi=200)
../../_images/user-guide-emtools-plot-04.png

Use phase_range=None when you want to show the raw phase values without wrapping. Use an explicit range when comparing stations whose phase should share a common display convention.

Raw Full-Tensor Panels#

plot_raw_sites_1d is designed for raw or nearly raw response review. Each station is a group. Each selected component becomes a column. Each component column contains rho above phase.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools import ensure_sites, plot_raw_sites_1d
 4
 5survey = ensure_sites("data/AMT/WILLY_DATA/L18PLT", strict=True)
 6
 7fig = plot_raw_sites_1d(
 8    survey,
 9    stations=["18-001A", "18-007U", "18-016A"],
10    components=("xx", "xy", "yx", "yy"),
11    raw=True,
12    force_style=False,
13    ncols_groups=3,
14    show_error_bars=True,
15    show_component_legend=True,
16)
17
18fig.savefig("raw_full_tensor_panels.png", dpi=200)
19plt.close(fig)
../../_images/user-guide-emtools-plot-05.png

With raw=True the function uses the package raw-data style, which is deliberately plain. In tests this style is black by default. That is useful for first QC because the display does not imply interpretation by colour.

Force Component Colours#

After the raw diagnostic pass, use force_style=True to restore the usual component colours while keeping the same layout.

 1from pycsamt.emtools import plot_raw_sites_1d
 2
 3fig = plot_raw_sites_1d(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    stations=["18-001A", "18-007U", "18-016A"],
 6    components=("xy", "yx"),
 7    raw=True,
 8    force_style=True,
 9    ncols_groups=3,
10)
11
12fig.savefig("raw_panels_component_colours.png", dpi=200)
../../_images/user-guide-emtools-plot-06.png

This is a good second view: first look for obvious raw-data problems in plain black, then re-render with component colours when you want to compare modes quickly.

Shared Labels Or Axis Labels#

plot_raw_sites_1d can use shared group labels or repeated axis labels. Shared labels are more compact for dense reports. Axis labels are sometimes easier during debugging.

 1from pycsamt.emtools import plot_raw_sites_1d
 2
 3fig_shared = plot_raw_sites_1d(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    stations=["18-001A"],
 6    components=("xx", "xy", "yx", "yy"),
 7    label_mode="shared",
 8    show_component_legend=False,
 9    ncols_groups=1,
10    figsize_scale=(8.0, 4.0),
11)
12fig_shared.subplots_adjust(left=0.20)
13fig_shared.savefig("raw_shared_labels.png", dpi=200)
14
15fig_axis = plot_raw_sites_1d(
16    "data/AMT/WILLY_DATA/L18PLT",
17    stations=["18-001A"],
18    components=("xx", "xy"),
19    label_mode="axis",
20    show_component_legend=False,
21    ncols_groups=1,
22    figsize_scale=(6.0, 4.0),
23)
24fig_axis.savefig("raw_axis_labels.png", dpi=200)

label_mode="shared" puts station and axis labels at the group level. label_mode="axis" repeats labels on axes and rotates bottom ticks more aggressively.

Use Display Control#

plot_raw_sites_1d and plot_response_tipper read display policy from PYCSAMT_CONTROL unless you pass a specific control object. This is how you change x-axis convention, phase wrapping, and rho display consistently.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.api.control import PYCSAMT_CONTROL
 4from pycsamt.emtools import plot_raw_sites_1d
 5
 6with PYCSAMT_CONTROL.context(
 7    x__view="frequency",
 8    rho__view="linear",
 9    phase__range=(0.0, 360.0),
10):
11    fig = plot_raw_sites_1d(
12        "data/AMT/WILLY_DATA/L18PLT",
13        stations=["18-001A"],
14        components=("xy", "yx"),
15        label_mode="axis",
16        force_style=True,
17        show_component_legend=False,
18        ncols_groups=1,
19        figsize_scale=(6.0, 4.0),
20    )
21
22fig.savefig("raw_panels_frequency_linear_rho.png", dpi=200)
23plt.close(fig)
../../_images/user-guide-emtools-plot-08.png

Use this pattern when one report needs a consistent non-default display style. It is better than editing labels or axes after the fact.

Response Plus Tipper#

plot_response_tipper adds tipper rows to the impedance response layout. Use it for MT surveys that actually contain vertical-field data. It is not useful for AMT/CSAMT lines where all stations lack tipper.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools import ensure_sites, plot_response_tipper
 4
 5survey = ensure_sites("data/MT/kap03lmt_edis", strict=True)
 6
 7fig = plot_response_tipper(
 8    survey,
 9    stations=["kap103", "kap142", "kap151"],
10    components=("xy", "yx"),
11    tipper_components=("tx", "ty"),
12    tipper_span_group=True,
13    ylim_tipper=(-2.5, 2.5),
14    ncols_groups=3,
15)
16
17fig.savefig("kap03_response_tipper.png", dpi=200)
18plt.close(fig)
../../_images/user-guide-emtools-plot-09.png

When tipper_span_group=True, each tipper row spans the station group. This is often the clearest layout when you have only two impedance components and want tipper to read as a station-level property.

Compact Tipper Rows#

Set tipper_span_group=False when you prefer a compact repeated layout where the tipper rows sit under each component column.

 1from pycsamt.emtools import plot_response_tipper
 2
 3fig = plot_response_tipper(
 4    "data/MT/kap03lmt_edis",
 5    stations=["kap151"],
 6    components=("xx", "xy", "yx", "yy"),
 7    tipper_components=("tx", "ty"),
 8    tipper_span_group=False,
 9    show_tipper_error_bars=False,
10    show_component_legend=False,
11    ncols_groups=1,
12    figsize_scale=(7.2, 5.6),
13    shared_x_label_pad=0.11,
14)
15
16fig.savefig("kap151_response_tipper_compact.png", dpi=200, bbox_inches="tight")
../../_images/user-guide-emtools-plot-10.png

Use the compact layout for one or two stations. Use the spanning layout when comparing several stations and you want fewer small axes.

Before And After Comparison#

plot_sites_compare pairs stations from two datasets. The usual use is raw versus processed, before versus after static-shift correction, or original versus smoothed.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools import ensure_sites, plot_sites_compare, smooth_mavg
 4
 5raw = ensure_sites("data/AMT/WILLY_DATA/L18PLT", strict=True)
 6smoothed = smooth_mavg(raw, k=5)
 7
 8fig = plot_sites_compare(
 9    raw,
10    smoothed,
11    stations=["18-001A", "18-007U", "18-016A"],
12    components=("xy", "yx"),
13    labels=("raw", "smoothed k=5"),
14    quantity="rhoa",
15    x_axis="period",
16    ncols_groups=3,
17    show_legend=True,
18)
19
20fig.savefig("raw_vs_smoothed_compare.png", dpi=200)
21plt.close(fig)
../../_images/user-guide-emtools-plot-11.png

The function pairs stations by station name. If the second dataset is missing a station, the corresponding after-column is blank.

Compare Impedance Instead Of Rho#

The comparison view can also use quantity="impedance". This is helpful when a processing step changes the complex tensor directly and you want to avoid hiding that change behind apparent-resistivity conversion.

 1from pycsamt.emtools import ensure_sites, plot_sites_compare, smooth_mavg
 2
 3raw = ensure_sites("data/AMT/WILLY_DATA/L18PLT", strict=True)
 4processed = smooth_mavg(raw, k=3)
 5
 6fig = plot_sites_compare(
 7    raw,
 8    processed,
 9    stations=["18-001A", "18-016A"],
10    components=("xx", "xy", "yx", "yy"),
11    quantity="impedance",
12    phase_range=(-180.0, 180.0),
13    labels=("raw", "processed"),
14    ncols_groups=2,
15)
16
17fig.savefig("impedance_before_after.png", dpi=200)
../../_images/user-guide-emtools-plot-12.png

Use fixed ylim_rhoa and ylim_phase when the before/after comparison needs exact visual scale matching across multiple figures.

Measured Versus Predicted Fit Grid#

plot_sites_fit_grid is built for inversion QC. It pairs measured and predicted stations by name, aligns predicted values onto measured frequencies, plots both curves, and writes an RMS value in each component panel.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools import ensure_sites, plot_sites_fit_grid, smooth_mavg
 4
 5measured = ensure_sites("data/AMT/WILLY_DATA/L18PLT", strict=True)
 6
 7# Demonstration only: a smoothed copy stands in for predicted data.
 8# In production, pass forward-model or inversion-response EDI files.
 9predicted = smooth_mavg(measured, k=5)
10
11fig = plot_sites_fit_grid(
12    measured,
13    predicted,
14    stations=["18-001A", "18-016A"],
15    components=("xy", "yx"),
16    quantity="rhoa",
17    phase_range=(-180.0, 180.0),
18    ncols_groups=2,
19    show_mode_legend=True,
20)
21
22fig.savefig("measured_vs_predicted_fit_grid.png", dpi=200)
23plt.close(fig)
../../_images/user-guide-emtools-plot-13.png

The RMS is computed from the measured-predicted residual. When measured errors are available, the residual is weighted by the display-space error estimate. That means an apparently small visual difference can still produce a high RMS if the data errors are very small.

Understand TE And TM Fit Colours#

The fit grid uses separate fit colours for TE-like and TM-like components:

  • xx and xy use the TE fit colour;

  • yx and yy use the TM fit colour.

 1from pycsamt.emtools import ensure_sites, plot_sites_fit_grid, smooth_mavg
 2
 3measured = ensure_sites("data/AMT/WILLY_DATA/L18PLT", strict=True)
 4predicted = smooth_mavg(measured, k=5)
 5
 6fig = plot_sites_fit_grid(
 7    measured,
 8    predicted,
 9    stations=["18-001A"],
10    components=("xx", "xy", "yx", "yy"),
11    color_fit_te="#2ca02c",
12    color_fit_tm="#d62728",
13    lw_fit=2.2,
14    ls_fit="-",
15    ncols_groups=1,
16    figsize_scale=(8.0, 4.0),
17    show_mode_legend=False,
18)
19
20fig.savefig("fit_grid_custom_fit_colours.png", dpi=200, bbox_inches="tight")
../../_images/user-guide-emtools-plot-14.png

Use custom fit colours when assembling figures for publication or when the default colours conflict with another report convention.

Choosing The Right Figure#

Here is a practical decision path:

I only need a quick rho/phase overview

Use plot_sites_panels with components=("xy", "yx") and a short station list.

I want a raw tensor QC view

Use plot_raw_sites_1d with all four components and raw=True.

The survey has tipper and I need to inspect it with response curves

Use plot_response_tipper. Confirm tipper exists with list_missing_sections or sites_summary first.

I processed the data and need before/after panels

Use plot_sites_compare with the original and processed Sites objects.

I have model predictions or inversion responses

Use plot_sites_fit_grid and read the RMS labels component by component.

Common Pitfalls#

Too many stations in one figure

Start with four to six stations. If you need every station, create multiple figures by station group.

Comparing figures with auto-scaled axes

Use fixed ylim_rhoa and ylim_phase when the visual comparison matters.

Using tipper plots on AMT lines with no tipper

The figure can still render, but it will not add useful interpretation. Check missing sections first.

Treating a smoothed dataset as a real prediction

A smoothed copy is useful for demonstrating the plotting API, but it is not a forward-model response.

Ignoring display control

If a report switches between period and frequency axes or between linear and log rho displays, use PYCSAMT_CONTROL.context so all plots share the same convention.

Save A Multi-Figure Plot Bundle#

The following script writes the core report figures for one line.

 1from pathlib import Path
 2
 3import matplotlib.pyplot as plt
 4
 5from pycsamt.emtools import (
 6    ensure_sites,
 7    plot_raw_sites_1d,
 8    plot_sites_compare,
 9    plot_sites_fit_grid,
10    plot_sites_panels,
11    smooth_mavg,
12)
13
14out = Path("plot_report_l18plt")
15out.mkdir(parents=True, exist_ok=True)
16
17survey = ensure_sites("data/AMT/WILLY_DATA/L18PLT", strict=True)
18stations = ["18-001A", "18-007U", "18-016A", "18-018A"]
19processed = smooth_mavg(survey, k=5)
20
21fig = plot_sites_panels(
22    survey,
23    stations=stations,
24    components=("xy", "yx"),
25    ncols=4,
26    show_legend=True,
27)
28fig.savefig(out / "station_panels.png", dpi=200)
29plt.close(fig)
30
31fig = plot_raw_sites_1d(
32    survey,
33    stations=stations[:3],
34    components=("xx", "xy", "yx", "yy"),
35    ncols_groups=3,
36)
37fig.savefig(out / "raw_full_tensor.png", dpi=200)
38plt.close(fig)
39
40fig = plot_sites_compare(
41    survey,
42    processed,
43    stations=stations[:3],
44    components=("xy", "yx"),
45    labels=("raw", "smoothed"),
46    ncols_groups=3,
47)
48fig.savefig(out / "raw_vs_smoothed.png", dpi=200)
49plt.close(fig)
50
51fig = plot_sites_fit_grid(
52    survey,
53    processed,
54    stations=stations[:2],
55    components=("xy", "yx"),
56    ncols_groups=2,
57)
58fig.savefig(out / "fit_grid_smoothed_standin.png", dpi=200)
59plt.close(fig)

Worked Example#

The gallery example demonstrates the same plotting layer on bundled AMT and MT surveys, including a tipper-capable MT line and display-control overrides.

Open the rendered gallery page here: Multi-station diagnostic panels (pycsamt.emtools.plot).