2.14. pycsamt.emtools#

Electromagnetic processing, diagnostics, tensor analysis, quality control, static-shift correction, source-effect tools, and plotting helpers.

See also

EM Tools Guide for narrative, runnable examples built module by module (currently: Transfer Functions And Tipper Diagnostics).

2.14.1. pycsamt.emtools — public API#

All user-facing functions and constants from the emtools sub-modules, organised by workflow stage.

pycsamt.emtools.resolve_rose_style(style, **overrides)#

Resolve style to a RoseStyle, then apply overrides.

Parameters:
  • style (str, RoseStyle, or None) – Named preset string, a RoseStyle instance, or None (falls back to "pycsamt").

  • **overrides – Any RoseStyle attribute to override after resolving the base preset, e.g. compass_labels="degrees".

Return type:

RoseStyle

Raises:
  • ValueError – If style is a string that does not match a known preset.

  • TypeError – If style is not a str, RoseStyle, or None.

Examples

>>> rs = resolve_rose_style("pycsamt", compass_labels="degrees")
>>> rs.compass_labels
'degrees'
pycsamt.emtools.ensure_sites(sites, *, recursive=True, on_dup='replace', order_by=None, strict=False, verbose=0)#

Normalize arbitrary user input to a Sites object.

This is the single entry-point validator for all emtools public APIs. It guarantees that downstream code receives a Sites instance, no matter whether the caller passed a path, an EDIFile/EDICollection, a single Site, an existing Sites, or an iterable of EDI-like items.

sitesAny

User-provided input. Accepts filesystem paths, glob patterns, EDI-like objects (EDIFile, EDICollection), Site, Sites, or iterables of such.

recursivebool, default=True

When walking directories, recurse into subfolders if the lower-level coercion utility supports it.

on_dup : {“replace”, “keep_first”, “keep_last”, “raise”},

default=”replace”

Duplicate site-name policy. See pycsamt.seg.base.to_sites() for semantics.

order_by : {“auto”, “chainage”, “input”, “station”, “latitude”,

“longitude”}, optional

Site ordering policy. None uses the package-wide pycsamt.api.PYCSAMT_ORDERING setting. Automatic mode uses coordinate-derived profile chainage only when the coordinates pass a conservative single-line geometry check; otherwise input order is preserved.

strictbool, default=False

If True, raise when no items can be resolved.

verboseint, default=0

Verbosity level. >0 emits warnings about duplicates and coercion steps.

pycsamt.site.base.Sites

Canonical Sites wrapper suitable for all processing tools.

ValueError

If sites is None or, in strict mode, nothing could be resolved into EDI-like items.

TypeError

If the result is not a Sites instance (indicates a broken installation or import cycle).

All emtools module functions should start with:

S = ensure_sites(sites, ...)

to guarantee API consistency across the package.

Parameters:
pycsamt.emtools.euler_rotation_matrix(yaw, pitch, roll, *, degrees=True)#

Return the ZYX “airline convention” attitude rotation matrix.

Implements Liu et al. (2018) Eq. 3-4 (equivalently their appendix A1-A6): the standard aerospace Tait-Bryan rotation \(R_{LS} = R_z(\psi)\,R_y(\theta)\,R_x(\phi)\) from the sensor/body frame (S) to the local, fixed east-north-up frame (L), where \(\psi\) = yaw (about \(Z_S\)), \(\theta\) = pitch (about the once-rotated \(Y_S'\)), \(\phi\) = roll (about the twice-rotated \(X_S''\)).

Parameters:
  • yaw (array-like) – Attitude angles, broadcastable to a common shape (...,). Degrees by default; radians when degrees=False.

  • pitch (array-like) – Attitude angles, broadcastable to a common shape (...,). Degrees by default; radians when degrees=False.

  • roll (array-like) – Attitude angles, broadcastable to a common shape (...,). Degrees by default; radians when degrees=False.

  • degrees (bool, default True) – Whether yaw, pitch, roll are in degrees.

Returns:

Rotation matrix R_LS for each broadcast attitude sample; x_L = R_LS @ x_S.

Return type:

ndarray of shape (…, 3, 3)

Notes

R_LS is a proper rotation: it is always orthogonal (R_LS.T == inv(R_LS)) with determinant +1, for any input angles. Identity input (yaw=pitch=roll=0) returns the identity matrix.

Examples

>>> from pycsamt.emtools.afmag import euler_rotation_matrix
>>> R = euler_rotation_matrix(0.0, 0.0, 0.0)
>>> np.allclose(R, np.eye(3))
True
pycsamt.emtools.geomagnetic_field_direction(inclination, declination, *, degrees=True)#

Return the local geomagnetic field unit vector \(B_E\).

In the (east, north, up) local frame used throughout this module (Liu et al. 2018 Eq. 8-9), with inclination the dip angle below horizontal (positive downward) and declination the horizontal bearing from geographic north toward east:

\[\hat{B}_E = (\cos I \sin D,\ \cos I \cos D,\ -\sin I)\]
Parameters:
  • inclination (array-like) – Geomagnetic inclination and declination, broadcastable to a common shape (...,). Degrees by default.

  • declination (array-like) – Geomagnetic inclination and declination, broadcastable to a common shape (...,). Degrees by default.

  • degrees (bool, default True) – Whether inclination and declination are in degrees.

Returns:

Unit vector(s) (east, north, up).

Return type:

ndarray of shape (…, 3)

Examples

>>> from pycsamt.emtools.afmag import geomagnetic_field_direction
>>> np.round(geomagnetic_field_direction(90.0, 0.0), 6)  # straight down
array([ 0.,  0., -1.])
pycsamt.emtools.coil_normal_direction(yaw, pitch, roll, *, degrees=True)#

Return the coil-normal unit vector \(N_L\) in the local frame.

Implements Liu et al. (2018) Eq. 10-12 for a single z-axis coil (\(N_S = (0, 0, 1)^T\), “we use z-axis hereinafter for the sake of simplification”): \(N_L = R_{LS}\,N_S\), i.e. the third column of euler_rotation_matrix().

Parameters:
Returns:

Unit vector(s) (east, north, up). Identity attitude (yaw=pitch=roll=0) returns straight up, (0, 0, 1).

Return type:

ndarray of shape (…, 3)

pycsamt.emtools.motion_coupling_cosine(yaw, pitch, roll, inclination, declination, *, degrees=True)#

Return \(\cos\theta(t)\), the coil/field coupling factor.

Implements Liu et al. (2018) Eq. 13-14: \(\cos\theta(t) = \hat{B}_E \cdot N_L(t)\), the cosine of the angle between the (fixed) geomagnetic field direction and the (time-varying) coil-normal direction. This is the quantity whose time derivative drives the motion-induced noise voltage; see simulate_motion_induced_voltage().

Parameters:
  • yaw (array-like) – Attitude time series, broadcastable to a common shape.

  • pitch (array-like) – Attitude time series, broadcastable to a common shape.

  • roll (array-like) – Attitude time series, broadcastable to a common shape.

  • inclination (array-like) – Geomagnetic field geometry, broadcastable against the attitude shape (a single site typically supplies scalars here while attitude varies over time).

  • declination (array-like) – Geomagnetic field geometry, broadcastable against the attitude shape (a single site typically supplies scalars here while attitude varies over time).

  • degrees (bool, default True) – Whether all five angle inputs are in degrees.

Returns:

\(\cos\theta(t)\), broadcast shape of the inputs, values in [-1, 1].

Return type:

ndarray

Examples

At identity attitude the coil normal points straight up; at 90 degrees inclination (a magnetic pole) the field points straight down, so the two are exactly anti-aligned and \(\cos\theta = -1\):

>>> from pycsamt.emtools.afmag import motion_coupling_cosine
>>> motion_coupling_cosine(0.0, 0.0, 0.0, 90.0, 0.0)
-1.0
pycsamt.emtools.motion_coupling_angle(yaw, pitch, roll, inclination, declination, *, degrees=True)#

Return \(\theta(t)\) in degrees.

See motion_coupling_cosine() for the underlying computation.

Parameters:
Return type:

ndarray

pycsamt.emtools.simulate_motion_induced_voltage(cos_theta, *, dt, gain=1.0, axis=-1)#

Return the simulated motion-induced noise voltage \(V(t)\).

Implements Liu et al. (2018) Eq. 2, \(V(t) \propto -\,d(\cos\theta(t))/dt\), via a centred finite difference (numpy.gradient()).

Parameters:
  • cos_theta (array-like) – \(\cos\theta(t)\), typically from motion_coupling_cosine().

  • dt (float) – Sample interval in seconds along axis.

  • gain (float, default 1.0) – Bundles the instrument-specific scale factor \(S \cdot N \cdot |B_E|\) (coil area x turns x field magnitude) from Eq. 2. The paper calibrates this from the instrument rather than deriving it analytically, so it is left as an explicit, user-supplied factor rather than a fabricated constant.

  • axis (int, default -1) – Time axis of cos_theta.

Returns:

Simulated noise voltage, same shape as cos_theta.

Return type:

ndarray

pycsamt.emtools.correct_motion_induced_noise(measured, predicted_noise)#

Return the motion-noise-corrected signal (Liu et al. 2018 Sec. 5).

The corrected signal is the measured signal minus the predicted noise voltage from simulate_motion_induced_voltage(). This function exists to give that one-line step an obvious, documented name rather than leaving every caller to subtract the two arrays themselves.

Parameters:
  • measured (array-like) – Raw movement-system signal.

  • predicted_noise (array-like) – Predicted motion-induced noise, same shape as measured.

Returns:

measured - predicted_noise.

Return type:

ndarray

Raises:

ValueError – If measured and predicted_noise have different shapes.

pycsamt.emtools.afmag_tilt_angles(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)#

Return per-station, per-frequency AFMAG tilt angles as a table.

The classical AFMAG comparator readout is, in modern tipper terms, \(\arctan|T|\) decomposed into in-phase (“real”) and quadrature (“imag”) components (Ward 1959) — the same real/imag split pycsamt.emtools.tf’s induction-arrow functions already use (plot_induction_arrows()), computed directly from tipper. No electric field or apparent-resistivity quantity is involved or derived.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(). Ground tipper-shaped input goes through Sites as before; airborne input goes through AirborneSites but only finds data here when it carries a ZTEM-style tipper transfer function – see airmt_tilt_angles()/ original_afmag_tilt_table() for the real AFMAG-family shapes instead.

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

Returns:

Columns: station, freq, period, tilt_real_deg, tilt_real_azimuth_deg, tilt_imag_deg, tilt_imag_azimuth_deg, tilt_resultant_deg. Stations with no tipper are omitted, not filled with a fabricated zero.

Return type:

pandas.DataFrame

pycsamt.emtools.motion_susceptibility_table(sites, *, inclination, declination, roll_amplitude_deg, pitch_amplitude_deg, yaw_amplitude_deg=0.0, recursive=True, on_dup='replace', strict=False, verbose=0)#

Score each station’s exposure to motion-induced noise.

Sweeps a nominal sinusoidal attitude envelope (amplitude only, no assumed platform frequency) through motion_coupling_cosine() at the survey’s geomagnetic geometry, and reports the resulting \(\cos\theta\) swing — the geometric factor that Liu et al. (2018) show drives motion-induced noise. This is independent of EM frequency; the paper’s finding that the noise concentrates at low EM frequency comes from the platform’s own physical oscillation spectrum (typically a few Hz), not from a frequency-dependence of this coupling geometry itself.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(). Ground tipper-shaped input goes through Sites as before; airborne input goes through AirborneSites but only finds data here when it carries a ZTEM-style tipper transfer function – see airmt_tilt_angles()/ original_afmag_tilt_table() for the real AFMAG-family shapes instead.

  • inclination (float) – Geomagnetic inclination/declination in degrees, assumed common to the survey (a local, small-region approximation matching the paper’s own “geomagnetic field is a constant vector in a local region” assumption).

  • declination (float) – Geomagnetic inclination/declination in degrees, assumed common to the survey (a local, small-region approximation matching the paper’s own “geomagnetic field is a constant vector in a local region” assumption).

  • roll_amplitude_deg (float) – Nominal peak platform roll/pitch amplitude in degrees.

  • pitch_amplitude_deg (float) – Nominal peak platform roll/pitch amplitude in degrees.

  • yaw_amplitude_deg (float, default 0.0) – Nominal peak yaw amplitude; the paper finds yaw has no effect for a z-axis coil (rotation about the coil’s own normal), so this defaults to zero rather than an assumed value.

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

Returns:

Columns: station, inclination_deg, declination_deg, cos_theta_min, cos_theta_max, susceptibility_score (cos_theta_max - cos_theta_min; larger means more exposed).

Return type:

pandas.DataFrame

pycsamt.emtools.flag_motion_susceptible_band(sites, *, inclination, declination, roll_amplitude_deg, pitch_amplitude_deg, yaw_amplitude_deg=0.0, band_hz=(150.0, 510.0), threshold=0.05, action='mask', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Flag/mask a station’s low-frequency tipper band as motion-suspect.

For stations whose motion_susceptibility_table() score exceeds threshold, the tipper values within band_hz are either masked (set to nan, distinguishing “suspect” from “measured zero”) or dropped entirely. For ground Sites input, this mirrors the same ensure_sites -> _apply_each mutation contract used by notch_powerline() and drop_freqs_manual(). Only action="mask" is offered for AirborneSites input (see Raises), matching mask_outside_ztem_band()’s identical restriction for the identical reason – this function only ever finds real tipper data on an AirborneSite in the first place when it carries a ZTEM-style tipper transfer function, since neither real AFMAG-family shape (afmag_tilt_deg, interstation_tensor) is one.

This does not attempt the paper’s literal time-domain subtraction (see the module docstring for why: Sites carries no raw time series to subtract from). It is a QC gate, not a noise-removal algorithm — flagging a plausibly contaminated band so it can be reviewed or excluded, the same way the rest of emtools treats other unreliable-frequency conditions.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(). Ground tipper-shaped input goes through Sites as before; airborne input goes through AirborneSites but only finds data here when it carries a ZTEM-style tipper transfer function – see airmt_tilt_angles()/ original_afmag_tilt_table() for the real AFMAG-family shapes instead.

  • inclination (float) – Forwarded to motion_susceptibility_table().

  • declination (float) – Forwarded to motion_susceptibility_table().

  • roll_amplitude_deg (float) – Forwarded to motion_susceptibility_table().

  • pitch_amplitude_deg (float) – Forwarded to motion_susceptibility_table().

  • yaw_amplitude_deg (float, default 0.0) – Forwarded to motion_susceptibility_table().

  • band_hz ((float, float), default (150.0, 510.0)) – Frequency band to flag on a susceptible station, in Hz. Defaults to the historical original-AFMAG comparator band.

  • threshold (float, default 0.05) – Minimum susceptibility_score (see motion_susceptibility_table()) for a station to be flagged at all.

  • action ({"mask", "drop"}, default "mask") – "mask" sets flagged tipper values to nan in place; "drop" removes the corresponding frequency rows entirely.

  • inplace (bool) – Standard emtools processing-function tail; see notch_powerline() for the established convention this mirrors.

  • recursive (bool) – Standard emtools processing-function tail; see notch_powerline() for the established convention this mirrors.

  • on_dup (str) – Standard emtools processing-function tail; see notch_powerline() for the established convention this mirrors.

  • strict (bool) – Standard emtools processing-function tail; see notch_powerline() for the established convention this mirrors.

  • verbose (int) – Standard emtools processing-function tail; see notch_powerline() for the established convention this mirrors.

Returns:

The (optionally new) sites collection with flagged bands masked or dropped.

Return type:

Sites

Raises:

ValueError – If action is not "mask" or "drop", or if action is "drop" and sites resolves to AirborneSites.

pycsamt.emtools.plot_afmag_tilt_profile(sites, *, component='real', frequency_hz=None, period_s=None, figsize=(9.5, 4.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)#

Plot the classic AFMAG flight-line tilt-angle profile.

One value per station at a single reference frequency/period — the historical field presentation of AFMAG data, analogous to plot_emap_filter_profile()’s station-profile layout.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(). Ground tipper-shaped input goes through Sites as before; airborne input goes through AirborneSites but only finds data here when it carries a ZTEM-style tipper transfer function – see airmt_tilt_angles()/ original_afmag_tilt_table() for the real AFMAG-family shapes instead.

  • component ({"real", "imag", "resultant"}, default "real") – Which afmag_tilt_angles() column to plot.

  • frequency_hz (float, optional) – Reference frequency/period; nearest available value is used per station. Exactly one may be given; the median frequency across all stations is used when neither is given.

  • period_s (float, optional) – Reference frequency/period; nearest available value is used per station. Exactly one may be given; the median frequency across all stations is used when neither is given.

  • figsize ((float, float), default (9.5, 4.0)) – Used only when ax is not supplied.

  • station_label_step (int | None) – Forwarded to style_for() via _apply_station_rendering(), matching the top-of- section station convention used throughout pyCSAMT.

  • station_preset (str) – Forwarded to style_for() via _apply_station_rendering(), matching the top-of- section station convention used throughout pyCSAMT.

  • station_style (Any | None) – Forwarded to style_for() via _apply_station_rendering(), matching the top-of- section station convention used throughout pyCSAMT.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_afmag_tilt_psection(sites, *, component='resultant', cmap='RdYlBu_r', clim=None, clim_pct=(2.0, 98.0), figsize=(9.0, 5.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)#

Plot an AFMAG tilt-angle pseudosection (station x log-period).

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(). Ground tipper-shaped input goes through Sites as before; airborne input goes through AirborneSites but only finds data here when it carries a ZTEM-style tipper transfer function – see airmt_tilt_angles()/ original_afmag_tilt_table() for the real AFMAG-family shapes instead.

  • component ({"real", "imag", "resultant"}, default "resultant") – Which afmag_tilt_angles() column to image.

  • cmap (str, default "RdYlBu_r") – Colormap name.

  • clim ((float, float), optional) – Explicit color limits; overrides clim_pct.

  • clim_pct ((float, float), default (2.0, 98.0)) – Percentile color limits when clim is not given.

  • figsize ((float, float), default (9.0, 5.0)) – Used only when ax is not supplied.

  • station_label_step (int | None) – See plot_afmag_tilt_profile().

  • station_preset (str) – See plot_afmag_tilt_profile().

  • station_style (Any | None) – See plot_afmag_tilt_profile().

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_afmag_tilt_polar(sites, *, station=None, component='real', cmap='viridis', figsize=(5.5, 5.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Polar view of AFMAG tilt: azimuth and magnitude vs. period.

Direct AFMAG-labelled counterpart of plot_tipper_polar(): each frequency is one scatter point, colour encodes \(\log_{10}(\text{period})\).

Parameters:
  • sites (Sites-like)

  • station (str, optional) – Station to plot; defaults to the first (sorted) station.

  • component ({"real", "imag"}, default "real")

  • cmap (str, default "viridis")

  • figsize ((float, float), default (5.5, 5.5))

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

  • ax (matplotlib.axes.Axes, optional) – Existing polar axes to draw on.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_motion_coupling(yaw, pitch, roll, inclination, declination, *, x=None, xlabel='sample', degrees=True, ax=None, figsize=(8.0, 4.0))#

Plot \(\theta(t)\) and \(\cos\theta(t)\) for attitude data.

Direct visualization of the paper’s own Fig. 2-5 style curves, from raw attitude arrays – not Sites-based (see the module docstring for why).

Parameters:
  • yaw (array-like) – Attitude time series (or a swept parameter), same shape.

  • pitch (array-like) – Attitude time series (or a swept parameter), same shape.

  • roll (array-like) – Attitude time series (or a swept parameter), same shape.

  • inclination (float) – Geomagnetic field geometry in degrees.

  • declination (float) – Geomagnetic field geometry in degrees.

  • x (array-like, optional) – X-axis values (time or sample index); defaults to a plain integer index.

  • xlabel (str, default "sample") – X-axis label used when x is not supplied a label of its own.

  • degrees (bool, default True) – Whether yaw/pitch/roll are in degrees.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on; a right-hand twin axes is added for \(\cos\theta\).

  • figsize ((float, float), default (8.0, 4.0)) – Used only when ax is not supplied.

Returns:

The primary (\(\theta\)) axes; the twin axes is reachable via ax.figure.axes[-1].

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_motion_susceptibility_map(sites, *, inclination, declination, roll_amplitude_deg, pitch_amplitude_deg, yaw_amplitude_deg=0.0, cmap='magma_r', figsize=(7.0, 5.5), ax=None)#

Map each station’s motion-noise susceptibility score.

Uses station coordinates when available (coords), otherwise falls back to a station-index profile – the same fallback strategy pycsamt.emtools.tf uses for stations with no geo-referencing.

Parameters:
Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_afmag_correction_comparison(before_sites, after_sites=None, *, component='resultant', inclination=None, declination=None, roll_amplitude_deg=None, pitch_amplitude_deg=None, yaw_amplitude_deg=0.0, cmap='RdYlBu_r', delta_cmap='RdBu_r', figsize=(11.0, 8.2), axes=None, **flag_kws)#

Plot before/after/delta AFMAG tilt pseudosections.

Structurally identical to plot_emap_filter_psection()’s triptych: before, after, and \(\Delta\) (after - before) panels. If after_sites is omitted, it is computed by calling flag_motion_susceptible_band() on before_sites with inclination, declination, roll_amplitude_deg, and pitch_amplitude_deg (all then required).

Parameters:
  • before_sites (Sites-like)

  • after_sites (Sites-like, optional) – Already-corrected sites; computed from before_sites when omitted.

  • component ({"real", "imag", "resultant"}, default "resultant")

  • inclination (float, optional) – Forwarded to flag_motion_susceptible_band() when after_sites is omitted; required in that case.

  • declination (float, optional) – Forwarded to flag_motion_susceptible_band() when after_sites is omitted; required in that case.

  • roll_amplitude_deg (float, optional) – Forwarded to flag_motion_susceptible_band() when after_sites is omitted; required in that case.

  • pitch_amplitude_deg (float, optional) – Forwarded to flag_motion_susceptible_band() when after_sites is omitted; required in that case.

  • yaw_amplitude_deg (float, default 0.0) – Forwarded to flag_motion_susceptible_band() when after_sites is omitted.

  • cmap (str) – Colormaps for the before/after and delta panels.

  • delta_cmap (str) – Colormaps for the before/after and delta panels.

  • figsize ((float, float), default (11.0, 8.2)) – Used only when axes is not supplied.

  • axes (sequence of 3 Axes, optional) – Existing axes (before, after, delta) to draw on.

  • **flag_kws – Forwarded to flag_motion_susceptible_band() (for example band_hz, threshold, action).

Return type:

matplotlib.Figure

Raises:

ValueError – If after_sites is omitted and inclination, declination, roll_amplitude_deg, or pitch_amplitude_deg is not given.

pycsamt.emtools.bostick_depth_from_rho(rho, freq)#

Bostick depth estimate D(f) from apparent resistivity.

D(f) = 356 × √(ρ_a / f) [metres]

Parameters:
  • rho (float or array) – Apparent resistivity ρ_a in Ω·m. Broadcastable with freq.

  • freq (float or array) – Frequency in Hz.

Returns:

Depth in metres, same shape as broadcast of rho and freq.

Return type:

numpy.ndarray

References

Zhang et al. (2025), Eq. (1), Measurement.

pycsamt.emtools.vertical_resolution_pair(rho, f_lo, f_hi)#

Vertical resolution ΔD between two adjacent frequencies.

ΔD = 356 × √ρ_c × (1/√f_lo − 1/√f_hi) [metres; f_lo < f_hi]

Parameters:
  • rho (float) – Characteristic (apparent) resistivity ρ_c in Ω·m.

  • f_lo (float) – Lower frequency in Hz (deeper penetration).

  • f_hi (float) – Higher frequency in Hz (shallower penetration).

Returns:

Vertical resolution in metres. Positive when f_lo < f_hi.

Return type:

float

References

Zhang et al. (2025), Eq. (2), Measurement.

pycsamt.emtools.frequency_for_depth(depth_m, rho)#

Invert the Bostick formula: return the frequency (Hz) that maps to a given depth for a background resistivity rho.

f = ρ × (356 / D)² [Hz]

Parameters:
  • depth_m (float or array) – Target depth(s) in metres.

  • rho (float) – Background apparent resistivity in Ω·m.

Returns:

Frequency in Hz, same shape as depth_m.

Return type:

numpy.ndarray

pycsamt.emtools.frequency_schedule(target_depths, rho_estimate, *, f_min=9600.0, f_max=614400.0, min_resolution_m=None, fill_decades=False, per_decade=3, as_khz=False)#

Design a CSUMT frequency schedule that samples a set of target depths.

Each target depth is converted to a frequency via frequency_for_depth(), then clipped to [f_min, f_max]. Optionally, intermediate frequencies can be inserted to guarantee a minimum vertical resolution between consecutive depth levels.

Parameters:
  • target_depths (float or array) – Target depths in metres (deepest first or any order — sorted internally).

  • rho_estimate (float) – Background apparent resistivity ρ (Ω·m) used for the conversion.

  • f_min (float, default=9.6e3) – Minimum transmitter frequency in Hz (lower bound of CSUMT range).

  • f_max (float, default=614.4e3) – Maximum transmitter frequency in Hz (upper bound of CSUMT range).

  • min_resolution_m (float or None) – If given, insert additional frequencies between adjacent target depths whenever their vertical resolution would exceed this value.

  • fill_decades (bool, default=False) – If True, add per_decade log-spaced frequencies within each decade of the schedule to smooth coverage.

  • per_decade (int, default=3) – Number of extra frequencies to insert per decade when fill_decades is True.

  • as_khz (bool, default=False) – If True, return frequencies in kHz instead of Hz.

Returns:

Sorted frequencies in Hz (or kHz if as_khz is True).

Return type:

numpy.ndarray

References

Zhang et al. (2025), “Controlled source ultra-audio frequency magnetotellurics (CSUMT) transmitter”, Measurement.

pycsamt.emtools.plot_frequency_schedule(target_depths, rho_estimate, *, f_min=9600.0, f_max=614400.0, ax=None, figsize=(7.0, 5.0), title='Frequency schedule from target depths')#

Visualize which requested target depths survive the CSUMT band filter applied by frequency_schedule().

Each target depth is converted to its raw frequency with frequency_for_depth() and drawn as an open circle. Targets whose raw frequency falls inside [f_min, f_max] are additionally marked with a cross – exactly the filter frequency_schedule() applies before any min_resolution_m padding. This makes the schedule’s silent clipping of unreachable targets visible instead of only reporting a row count.

Parameters:
  • target_depths (float or array) – Target depths in metres, as passed to frequency_schedule().

  • rho_estimate (float) – Background apparent resistivity ρ (Ω·m).

  • f_min (float) – CSUMT transmitter band, default F_MIN_CSUMT / F_MAX_CSUMT.

  • f_max (float) – CSUMT transmitter band, default F_MIN_CSUMT / F_MAX_CSUMT.

  • ax (matplotlib.axes.Axes, optional)

  • figsize ((float, float), default=(7, 5))

  • title (str)

Return type:

matplotlib.axes.Axes

Examples

>>> from pycsamt.emtools.csumt import plot_frequency_schedule
>>> ax = plot_frequency_schedule([10.0, 20.0, 35.0, 50.0, 65.0], 300.0)
pycsamt.emtools.bostick_depth(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)#

Bostick depth estimate per station per frequency from measured data.

Uses the apparent resistivity derived from the off-diagonal impedance tensor components (geometric mean):

D(f) = 356 × √(ρ_a(f) / f) [metres]

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Any input accepted by ensure_sites().

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

One row per (station, frequency) with columns: station, freq_hz, period_s, rho_a_ohmm, depth_m.

Return type:

pandas.DataFrame

References

Zhang et al. (2025), Eq. (1).

pycsamt.emtools.vertical_resolution(sites, *, rho_override=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Vertical resolution ΔD between adjacent frequencies per station.

For each consecutive pair (f_lo, f_hi) in the station’s frequency list (sorted ascending), computes:

ΔD = D(f_lo) − D(f_hi) [metres]

using the Bostick depths derived from the measured ρ_a. Alternatively, supply rho_override to use a fixed background resistivity with the analytical formula 356 × √ρ × (1/√f_lo 1/√f_hi).

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • rho_override (float or None) – If given, use this constant resistivity for all ΔD calculations (analytical formula) instead of the per-frequency ρ_a.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

Columns: station, freq_lo_hz, freq_hi_hz, depth_lo_m, depth_hi_m, delta_depth_m, rho_a_ohmm.

Return type:

pandas.DataFrame

pycsamt.emtools.depth_coverage_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)#

Summary depth-coverage statistics per station.

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

One row per station with columns: station, n_freq, freq_min_hz, freq_max_hz, depth_min_m, depth_max_m, mean_resolution_m, median_resolution_m.

Return type:

pandas.DataFrame

pycsamt.emtools.plot_depth_section(sites, *, log_color=True, sort_by=None, cmap='viridis_r', figsize=(10.0, 5.0), period_axis=True, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Pseudosection of Bostick depth across stations and periods/frequencies.

Each cell (station × period) is coloured by the Bostick depth D(f) = 356 √(ρ_a / f).

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • log_color (bool, default=True) – Color by log10(depth) instead of depth.

  • sort_by ({"auto", "chainage", "input", "name", "lon", "lat"}, optional) – Station ordering along the x-axis. None inherits pycsamt.api.PYCSAMT_ORDERING.

  • cmap (str, default="viridis_r") – Matplotlib colormap name.

  • figsize ((float, float), default=(10, 5))

  • period_axis (bool, default=True) – If True y-axis is period (s); otherwise frequency (Hz).

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • ax (matplotlib.axes.Axes, optional)

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_depth_coverage_ranking(sites, *, depth_unit='km', flag_coarse_resolution=True, color_fine='#2e6f9e', color_coarse='#c0392b', title='Per-station Bostick depth coverage', ax=None, figsize=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Horizontal ranking of the deepest Bostick depth reached per station.

Reduces depth_coverage_table() to one horizontal bar per station, sorted by depth_max_m descending – the fastest way to see which stations drive a line’s overall depth coverage. When flag_coarse_resolution is True, stations whose median_resolution_m is coarser than the survey-wide median are drawn in color_coarse rather than color_fine, so a station that only reaches deep with coarse vertical resolution is visually distinguished from one that reaches comparable depth with fine resolution.

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Any input accepted by ensure_sites().

  • depth_unit ({"km", "m"}, default="km") – Unit for the plotted depth axis.

  • flag_coarse_resolution (bool, default=True) – Colour bars by whether the station’s median vertical resolution exceeds the survey-wide median.

  • color_fine (str) – Bar colours for resolution at-or-finer than, and coarser than, the survey median.

  • color_coarse (str) – Bar colours for resolution at-or-finer than, and coarser than, the survey median.

  • title (str)

  • ax (matplotlib.axes.Axes, optional)

  • figsize ((float, float), optional) – Defaults to a height that grows with the station count.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Return type:

matplotlib.axes.Axes

Examples

>>> from pycsamt.emtools.csumt import plot_depth_coverage_ranking
>>> ax = plot_depth_coverage_ranking("data/AMT/WILLY_DATA/L18PLT")
pycsamt.emtools.wavenumber(freq, rho=None)#

Effective real wavenumber k [m⁻¹] for CSAMT or free-space propagation.

Parameters:
  • freq (float) – Frequency [Hz].

  • rho (float or None) – Half-space resistivity [Ω·m]. If given, returns the earth (CSAMT) effective wavenumber Re(k₁) = sqrt(π f μ₀ / ρ). If None, returns the free-space wavenumber f / c.

Returns:

k – Wavenumber [m⁻¹].

Return type:

float

Notes

The complex earth wavenumber is k₁ = √(i ω μ₀ / ρ). Its real part equals |k₁| / √2 = √(π f μ₀ / ρ). The corresponding wavelength is λ = 2π / k_eff ≈ 2π × 503 × √(ρ/f) [m].

pycsamt.emtools.sdas_element_pattern(theta_deg, l, k, *, normalize=True)#

Far-field element pattern for a single finite-length SDAS (eq. 7).

The dipole / array axis is the y-axis. The angle θ is measured FROM the y-axis, so θ = 0° is along the dipole (null) and θ = 90° is broadside (maximum).

Parameters:
  • theta_deg (float or ndarray) – Angle(s) from the dipole axis [degrees], range [0, 180].

  • l (float) – SDAS (dipole) physical length [m].

  • k (float) – Wavenumber [m⁻¹]. Use wavenumber() to compute for given frequency and resistivity.

  • normalize (bool) – Normalize the peak to 1.0 (default True).

Returns:

F|F(θ)| pattern values (≥ 0).

Return type:

ndarray

Notes

F(θ) = |[cos(kl cosθ/2) − cos(kl/2)]| / |sinθ|. The singularity at θ = 0° and 180° resolves to zero by L’Hôpital’s rule.

pycsamt.emtools.array_factor(theta_b_deg, N, d, k, beta=0.0)#

Normalised array factor AF_n for an N-element linear PAS (eq. 19).

The angle θ_b is measured FROM BROADSIDE (perpendicular to the array axis). θ_b = 0° is the maximum direction when β = 0; θ_b = ±90° is along the array (end-fire direction).

Parameters:
  • theta_b_deg (float or ndarray) – Broadside angle(s) [degrees], range [−90, 90].

  • N (int) – Number of SDAS elements.

  • d (float) – Element-to-element spacing [m].

  • k (float) – Wavenumber [m⁻¹].

  • beta (float) – Inter-element phase shift [rad]. β = 0 → broadside array; use beam_steer() to compute β for a target angle.

Returns:

AF – Normalised |AF_n(θ_b)| ∈ [0, 1].

Return type:

ndarray

Notes

AF_n = sin(N ψ/2) / [N sin(ψ/2)], ψ = k d sinθ_b + β.

pycsamt.emtools.pas_pattern(theta_b_deg, N, d, k, beta=0.0, l=1000.0, *, normalize=True)#

Total normalised far-field pattern of an N-element PAS.

The combined pattern is the product of the SDAS element pattern and the array factor, evaluated at the same observation angle.

Parameters:
  • theta_b_deg (float or ndarray) – Broadside angle(s) [degrees], range [−90, 90].

  • N (int) – Number of SDAS elements.

  • d (float) – Element spacing [m].

  • k (float) – Wavenumber [m⁻¹].

  • beta (float) – Inter-element phase shift [rad].

  • l (float) – SDAS length [m] (default 1000 m matching gxac023).

  • normalize (bool) – Normalize peak to 1.0 (default True).

Returns:

pattern – Combined |E_total(θ_b)| pattern (≥ 0).

Return type:

ndarray

pycsamt.emtools.beam_steer(theta_m_deg, d, k)#

Inter-element phase shift β [rad] to steer the main lobe to θ_m (eq. 23).

Parameters:
  • theta_m_deg (float) – Target main-lobe broadside angle [degrees].

  • d (float) – Element spacing [m].

  • k (float) – Wavenumber [m⁻¹].

Returns:

beta – Required phase shift [rad]. Apply the same β to each SDAS via the feed-network inductance delay.

Return type:

float

Notes

Condition (eq. 23): β = −k d sinθ_m.

pycsamt.emtools.steering_angles(N, d, k, beta, *, n_range=3)#

All main-lobe broadside angles [degrees] for the given PAS configuration.

Solves k d sinθ + β = ±2nπ (eq. 21) for n = 0, ±1, ±2, …

Parameters:
  • N (int / float) – Array parameters (as in array_factor()).

  • d (int / float) – Array parameters (as in array_factor()).

  • k (int / float) – Array parameters (as in array_factor()).

  • beta (float) – Inter-element phase shift [rad].

  • n_range (int) – Search over n = −n_range … +n_range (default 3).

Returns:

angles – Sorted array of main-lobe broadside angles [degrees] inside [−90°, 90°].

Return type:

ndarray

pycsamt.emtools.sdas_directivity(l, k, *, n_theta=2000)#

2-D horizontal-plane directivity D₀ = 2π U_max / ∫ U(θ) dθ (eq. 12).

Parameters:
  • l (float) – SDAS length [m].

  • k (float) – Wavenumber [m⁻¹].

  • n_theta (int) – Number of angular samples for numerical integration.

Returns:

D0 – Directivity (dimensionless). A perfect omnidirectional source has D₀ = 1 in 2-D.

Return type:

float

pycsamt.emtools.snr_gain_db(N)#

SNR improvement of an N-element PAS relative to a single SDAS [dB].

For coherent beam forming, the gain scales as N²: G_PAS / G_SDAS = N² → 10 log₁₀(N²) = 20 log₁₀(N) dB.

Parameters:

N (int) – Number of SDAS elements.

Returns:

gain_dB – SNR gain [dB].

Return type:

float

pycsamt.emtools.plot_radiation_pattern(theta_b_deg, patterns, *, labels=None, polar=True, normalize=True, log_scale=False, db_floor=-40.0, title='Radiation pattern', figsize=(7.0, 7.0), ax=None)#

Plot one or more radiation patterns in polar or Cartesian format.

Parameters:
  • theta_b_deg (array-like) – Broadside angles [degrees], range [−90, 90].

  • patterns (array-like or list of array-like) – Pattern amplitude(s). A 2-D array is treated as multiple patterns with shape (n_patterns, n_angles).

  • labels (list of str or None) – Legend labels for each pattern.

  • polar (bool) – Polar (default) or Cartesian plot.

  • normalize (bool) – Normalise each pattern to its peak before plotting.

  • log_scale (bool) – Convert to dB (20 log₁₀) for the radial / y-axis.

  • db_floor (float) – Minimum dB value when log_scale=True (default −40 dB).

  • title (str) – Axes title.

  • figsize (tuple) – Figure size if a new figure is created.

  • ax (matplotlib.axes.Axes or None) – Axes to draw on; created if None.

Returns:

ax

Return type:

matplotlib.axes.Axes

pycsamt.emtools.classify_field_zones(sites, source_offset=None, *, far_threshold=3.0, near_threshold=0.3, recursive=True, on_dup='replace', strict=False, verbose=0)#

Classify CSAMT measurement zones per station per frequency.

For each (station, frequency) pair the dimensionless field-zone parameter is computed as:

|k·r| = r / δ_B where δ_B = 356 √(ρ_a / f) (metres)

and the zone is assigned as:

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Any input accepted by ensure_sites().

  • source_offset (float, dict {station: float}, or None) – Source–receiver separation r in metres. If a dict is given, missing stations are skipped (or read from ed.offset / ed.source_offset).

  • far_threshold (float, default=3.0) – |k·r| threshold for the far-field (plane-wave) zone.

  • near_threshold (float, default=0.3) – |k·r| threshold below which the near-field zone is declared.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

Tidy table, one row per (station, frequency):

station, freq_hz, period_s, offset_m, rho_a_ohmm, delta_bostick_m, kr, zone

Return type:

pandas.DataFrame

References

Chen & Yan (2005), J. Geophysics and Engineering 2, 105–120. Yan & Fu (2004), analytical shadow/overprint estimation.

pycsamt.emtools.near_field_factor(sites, source_offset=None, *, recursive=True, on_dup='replace', strict=False, verbose=0)#

Near-field correction factor for apparent resistivity (equatorial HED).

For the equatorial E_y component from a horizontal electric dipole over a homogeneous half-space, the ratio of the measured E_y to the plane-wave (far-field) E_y is:

F(p) = 1 − 3/p² + 3/p³ p = k·r (complex)

so the apparent resistivity is biased by factor |F(p)|². When |F(p)| ≈ 1 the data are in the plane-wave zone; strong departures indicate near-field contamination.

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • source_offset (float, dict {station: float}, or None) – Source–receiver separation r in metres.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

Columns: station, freq_hz, period_s, offset_m, rho_a_ohmm, kr, nf_factor.

  • nf_factor = |F(p)|; close to 1.0 → far-field (safe).

  • nf_factor far from 1.0 → near-field bias present.

Return type:

pandas.DataFrame

References

Chen & Yan (2005), eqs. (8)–(10).

pycsamt.emtools.plot_field_zones(sites, source_offset=None, *, far_threshold=3.0, near_threshold=0.3, contour_kr=True, kr_levels=(0.1, 0.3, 1.0, 3.0, 10.0), sort_by=None, period_axis=True, log_y=True, figsize=(10.0, 5.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Pseudosection of CSAMT field zones across stations and frequencies.

Each cell (station × period/frequency) is filled by zone colour:

  • green → far field (|k·r|far_threshold)

  • orange → transition

  • red → near field (|k·r| < near_threshold)

Dashed white contours of constant |k·r| can be overlaid.

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • source_offset (float, dict {station: float}, or None) – Source–receiver separation r in metres.

  • far_threshold (float) – Zone boundaries in |k·r|.

  • near_threshold (float) – Zone boundaries in |k·r|.

  • contour_kr (bool, default=True) – Draw |k·r| contours.

  • kr_levels (tuple of float) – |k·r| values to contour.

  • sort_by ({"auto", "chainage", "input", "name", "lon", "lat"}, optional) – Station ordering along the x-axis. None inherits pycsamt.api.PYCSAMT_ORDERING.

  • period_axis (bool, default=True) – If True y-axis shows period (s), else frequency (Hz).

  • log_y (bool, default=True) – Use a quasi-log y-axis (log-spaced tick labels).

  • figsize ((float, float), default=(10, 5))

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • ax (matplotlib.axes.Axes, optional) – Draw on existing axes.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.overprint_beta(rho, freq, offset, *, dh_frac=0.001)#

Ground-wave / surface-wave amplitude ratio β_Ey (%).

Evaluates equation (6) of Yan & Fu (2004) analytically at the surface receiver position broadside to the source dipole.

Parameters:
  • rho (float or ndarray) – Half-space apparent resistivity [Ω·m].

  • freq (float or ndarray) – Frequency [Hz].

  • offset (float or ndarray) – Source–receiver horizontal offset r [m].

  • dh_frac (float) – Step size as a fraction of offset used for numerical differentiation (default 1e-3).

Returns:

beta_pct – β × 100 [%]. Values above BETA_THRESH_PCT (3 %) indicate potential shadow / source overprint (yan2004).

Return type:

ndarray

Notes

The function uses central finite differences to evaluate the partial derivatives of the Sommerfeld term P = e^{−k₁r}/r and the Foster term N = I₀(p) K₀(q), where k₁ = √(iωμ₀/ρ) is the complex wavenumber and p, q are related to the 3-D distance and depth.

pycsamt.emtools.detect_source_overprint(sites, source_offset=None, *, beta_threshold=3.0, recursive=True, on_dup='replace', strict=False, verbose=0)#

Per-frequency source overprint β index for a set of CSAMT sites.

Computes the ground-wave / surface-wave ratio β_Ey (yan2004) for every measurement frequency at each site and returns a long-form DataFrame.

Parameters:
  • sites (Sites | list) – EDI-like objects or a Sites container.

  • source_offset (float | dict | None) – Source–receiver offset [m]. A scalar applies to all sites; a dict maps {station: offset}. If None the function tries to read the offset from site attributes (source_offset, offset, dist).

  • beta_threshold (float) – β [%] above which the overprint flag is raised (default 3.0).

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

Columns: station, freq_hz, period_s, offset_m, rho_a_ohmm, kr, beta_pct, overprint_flag. Rows with unknown offset have NaN in kr/beta_pct.

Return type:

pd.DataFrame

pycsamt.emtools.source_overprint_table(sites, source_offset=None, *, beta_threshold=3.0, f_split=1.0, recursive=True, on_dup='replace', strict=False, verbose=0)#

Per-station summary of source overprint metrics.

In addition to the maximum and mean β values (yan2004), the table includes the log-log ρ_a–frequency slope in the low-frequency (LF) and high-frequency (HF) bands and their difference (da2016). A strongly negative slope_delta (LF slope << HF slope) indicates a resistivity contrast beneath the source (da2016 §2.2–2.3).

Parameters:
  • sites (Sites | list)

  • source_offset (float | dict | None)

  • beta_threshold (float) – β [%] threshold (default BETA_THRESH_PCT = 3.0).

  • f_split (float) – Frequency [Hz] dividing LF from HF bands for slope analysis.

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

Columns: station, n_freq, offset_m, beta_max_pct, beta_mean_pct, n_overprint, overprint_frac, lf_slope, hf_slope, slope_delta, overprint_flag.

Return type:

pd.DataFrame

pycsamt.emtools.plot_overprint_section(sites, source_offset=None, *, beta_threshold=3.0, log_color=True, cmap='hot_r', figsize=(10, 5), period_axis=True, log_y=True, contour_beta=True, beta_levels=(1.0, 3.0, 10.0, 30.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot source overprint β pseudo-section (station × frequency).

A colour-coded pseudo-section of the ground-wave / surface-wave ratio β_Ey is drawn for each site. Contour lines at selected β levels highlight the overprint-prone zones.

Parameters:
  • sites (Sites | list)

  • source_offset (float | dict | None)

  • beta_threshold (float) – Dashed contour drawn at this level [%] (default 3.0).

  • log_color (bool) – Use log₁₀(β) colour scale.

  • cmap (str) – Matplotlib colormap name.

  • period_axis (bool) – Show periods on the right y-axis when True.

  • log_y (bool) – Logarithmic frequency axis.

  • contour_beta (bool) – Overlay β contour lines.

  • beta_levels (tuple) – β [%] values for contour lines.

  • ax (matplotlib.axes.Axes or None) – Axes to draw on; created if None.

  • figsize (tuple)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

ax

Return type:

matplotlib.axes.Axes

pycsamt.emtools.normalize_response(sites, rho_ref=100.0, source_offset=None, *, comp='det', phi_ref_deg=45.0, recursive=True, on_dup='replace', strict=False, verbose=0)#

Normalized apparent resistivity and subtracted phase (Wang & Lin 2023).

For each (station, frequency) pair computes:

ρ_n    = ρ_obs / ρ_ref
φ_diff = φ_obs − φ_ref

and classifies the measurement zone using the skin-depth formula proposed by Wang & Lin (2023, eq. 1):

δ = 503 √(ρ_a / f) [m]

with thresholds: near (r/δ < 0.5), transition (0.5–4), far (>4).

Parameters:
  • sites (Sites | list) – EDI-like objects or a Sites container.

  • rho_ref (float) – Reference half-space resistivity [Ω·m] (default 100).

  • source_offset (float | dict | None) – Source–receiver offset r [m]. A dict maps {station: r}. If None, zone and kr are NaN.

  • comp ({"det", "xy", "yx"}) – Impedance component used for ρ_a and φ ("det" = geometric-mean determinant).

  • phi_ref_deg (float) – Reference half-space phase [°]. 45° (default) is the far-field plane-wave value for a homogeneous 1-D half-space.

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

Columns: station, freq_hz, period_s, offset_m, rho_a_ohmm, rho_n, phi_obs_deg, phi_ref_deg, phi_diff_deg, zone, kr. zone / kr are None / NaN when no offset is available.

Return type:

pandas.DataFrame

References

Wang & Lin (2023), Geophysics **88**(6), E215–E230.

pycsamt.emtools.correct_near_field(sites, source_offset, *, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Correct impedance tensor for CSAMT near-field contamination.

Divides each element of Z by the complex near-field factor F(p):

Z_corrected = Z_obs / F(p)

where F(p) = 1 − 3/p² + 3/p³ is the equatorial HED transfer-function ratio and p = k · r, k = √(i·ω·μ₀ / ρ_a). In the far field F(p) → 1 so no correction is applied; in the near/transition zone the correction restores the plane-wave equivalent impedance.

Uses _apply_each() to apply the per-site correction and return a new Sites (or modify in-place).

Parameters:
  • sites (Sites | list) – EDI-like objects or a Sites container.

  • source_offset (float | dict) – Source–receiver separation r [m]. Dict maps {station: r}.

  • inplace (bool, default False) – Modify Z.z in-place and return the original Sites.

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

Sites with corrected impedance tensors.

Return type:

pycsamt.site.base.Sites

References

Wang & Lin (2023), Geophysics **88**(6), E215–E230. Chen & Yan (2005), eqs. (8)–(10).

pycsamt.emtools.plot_normalized_response(sites, rho_ref=100.0, source_offset=None, *, comp='det', phi_ref_deg=45.0, period_axis=True, figsize=(12.0, 5.0), cmap_rho='RdBu_r', cmap_phi='RdBu', rho_n_lim=None, phi_diff_lim=None, recursive=True, on_dup='replace', strict=False, verbose=0, axes=None)#

Pseudosection of normalized ρ_a and subtracted phase (Wang & Lin 2023).

Produces a two-panel figure analogous to Fig. 8(e–f) of Wang & Lin (2023):

  • Left panel: ρ_n = ρ_obs / ρ_ref (centred at 1.0; red = high).

  • Right panel: φ_diff = φ_obs − φ_ref [°] (centred at 0°).

Parameters:
  • sites (Sites | list)

  • rho_ref (float) – Reference half-space resistivity [Ω·m].

  • source_offset (float | dict | None)

  • comp ({"det", "xy", "yx"})

  • phi_ref_deg (float) – Reference half-space phase [°] (default 45°).

  • period_axis (bool) – Use period (s) on the y-axis when True (default).

  • figsize ((float, float), default (12, 5))

  • cmap_rho (str) – Matplotlib colormap names for the two panels.

  • cmap_phi (str) – Matplotlib colormap names for the two panels.

  • rho_n_lim ((vmin, vmax) or None) – Colour limits for ρ_n. Default: symmetric about 1.

  • phi_diff_lim ((vmin, vmax) or None) – Colour limits for φ_diff. Default: symmetric about 0.

  • axes ((ax1, ax2) or None) – Draw on existing axes; created if None.

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

(ax1, ax2)

Return type:

tuple of matplotlib.axes.Axes

References

Wang & Lin (2023), Geophysics **88**(6), E215–E230 (Figs. 8e–f).

pycsamt.emtools.coverage_score(y_true, y_lo, y_hi)#

Empirical coverage fraction of a prediction interval (kouadio2025 eq. 1).

Parameters:
  • y_true (array-like) – Observed values.

  • y_lo (array-like) – Lower and upper bounds of the predicted interval.

  • y_hi (array-like) – Lower and upper bounds of the predicted interval.

Returns:

cov – Fraction of observations that fall inside [y_lo, y_hi] ∈ [0, 1].

Return type:

float

pycsamt.emtools.rho_coverage(sites, q_lo, q_hi, *, rho_comp='xy', recursive=True, on_dup='replace', strict=False, verbose=0)#

Per-frequency coverage of observed ρ_a within predicted quantile bounds.

For each site and frequency, checks whether the Cagniard apparent resistivity extracted from the observed Z tensor falls inside the predicted interval [L_j, U_j] (kouadio2025 eq. 1):

c_j = 1(L_j <= rho_a,obs,j <= U_j)
Parameters:
  • sites (Sites | list) – Observed CSAMT sites.

  • q_lo (dict {station: array} or array or scalar) – Lower / upper quantile bounds aligned with each site’s frequency array. When a dict, keys must match station names; sites without a key are skipped. A scalar broadcasts to every frequency of every site.

  • q_hi (dict {station: array} or array or scalar) – Lower / upper quantile bounds aligned with each site’s frequency array. When a dict, keys must match station names; sites without a key are skipped. A scalar broadcasts to every frequency of every site.

  • rho_comp ({"xy", "yx"}) – Impedance component used to derive ρ_a (default "xy").

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

Columns: station, freq_hz, period_s, rho_obs, q_lo, q_hi, covered, width_pct. covered is bool; width_pct = 100 × (q_hi−q_lo) / ρ_obs.

Return type:

pd.DataFrame

pycsamt.emtools.rho_error_stats(sites, model_rho, *, rho_comp='xy', recursive=True, on_dup='replace', strict=False, verbose=0)#

Per-frequency relative error between observed and predicted ρ_a.

Computes ε_j = (ρ_a,pred,j − ρ_a,obs,j) / ρ_a,obs,j × 100 % for each station and frequency, analogous to the error distribution visualised in the k-diagram polar violin (kouadio2025 Fig 2b).

Parameters:
  • sites (Sites | list) – Observed CSAMT sites.

  • model_rho (dict {station: array} or Sites-like) – Predicted apparent resistivity. Either a dict mapping station names to 1-D arrays (same length as the corresponding site’s frequency array), or a Sites-like container from which ρ_a is extracted with the same rho_comp setting.

  • rho_comp ({"xy", "yx"}) – Impedance component.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

Columns: station, freq_hz, period_s, rho_obs, rho_pred, rel_err_pct, abs_err_pct.

Return type:

pd.DataFrame

pycsamt.emtools.coverage_table(sites, q_lo, q_hi, *, rho_comp='xy', nominal=0.9, recursive=True, on_dup='replace', strict=False, verbose=0)#

Per-station coverage summary.

Parameters:
Returns:

Columns: station, n_freq, empirical_cov, mean_width_pct, calibrated_flag.

Return type:

pd.DataFrame

pycsamt.emtools.plot_polar_coverage(sites, q_lo, q_hi, *, rho_comp='xy', log_radius=True, n_freq_ticks=8, figsize=(7, 7), title='Coverage evaluation', recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Polar coverage plot: angle ∝ log₁₀(f), radius ∝ ρ_a,obs.

Green markers = observed ρ_a within predicted interval (covered); red = outside. Thin radial segments show each [q_lo, q_hi] range. Every station shares (almost) the same frequency grid, so each angular position is really one frequency shared by every station — the angle axis is labelled with that frequency directly (rather than the otherwise-meaningless default degree ticks) so a reader can tell which part of the band a cluster of misses falls in.

Parameters:
  • n_freq_ticks (int, default 8) – Number of evenly (log-)spaced frequency labels drawn around the ring. Set to 0 to fall back to the default degree ticks.

  • sites (Any)

  • q_lo (dict | ndarray | float)

  • q_hi (dict | ndarray | float)

  • rho_comp (str)

  • log_radius (bool)

  • figsize (tuple)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

ax

Return type:

matplotlib.axes.Axes (polar projection)

pycsamt.emtools.plot_polar_errors(sites, model_rho, *, rho_comp='xy', n_bins=18, figsize=(7, 7), title='Error distribution', recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Polar rose diagram of relative residuals (ε = (ρ_pred − ρ_obs)/ρ_obs × 100 %).

Each angular sector spans one frequency decade. Bar length = mean abs(ε) within that sector; red = over-prediction (mean ε > 0), blue = under. Analogous to the polar violin in kouadio2025 Fig 2b.

Returns:

ax

Return type:

matplotlib.axes.Axes (polar projection)

Parameters:
pycsamt.emtools.plot_width_drift(sites, q_lo, q_hi, *, rho_comp='xy', n_bands=8, polar=False, figsize=(8, 4), title='Frequency-width drift', recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Mean relative interval width per frequency band (horizon-drift analogue).

Visualises how the predicted interval width — relative to observed ρ_a — changes across the frequency spectrum, a proxy for how model uncertainty grows with probing depth (kouadio2025 § Forecast Horizon Drift).

Parameters:
Returns:

ax

Return type:

matplotlib.axes.Axes

pycsamt.emtools.build_qc_table(sites, *, include_skew=True, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#
Parameters:
Return type:

Any

pycsamt.emtools.confidence_ratio(scores, *, weights=None, n_freq=1, return_error=False)#

Compute the composite confidence ratio from diagnostic scores.

The confidence ratio is a weighted finite-score mean:

\[\mathrm{CR} = \frac{\sum_k w_k s_k \mathbf{1}_{s_k\ finite}} {\sum_k w_k \mathbf{1}_{s_k\ finite}}, \qquad 0 \leq s_k \leq 1.\]

The default score vector is coverage, uncertainty, offdiag, diagonal, phase, spatial with weights 0.35, 0.20, 0.15, 0.10, 0.10, 0.10. Missing scores are ignored and all finite scores are clipped to [0, 1].

The optional error is the population spread of available component scores; when only one score is available it falls back to the binomial standard error sqrt(CR * (1 - CR) / n_freq).

Parameters:
Return type:

float | tuple[float, float]

pycsamt.emtools.export_confidence_map(sites, *, csv_path=None, surfer_path=None, method='composite', coordinate_system='auto', line_labels=None, grid_shape=(200, 200), max_triangle_edge=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Export station confidence to CSV and/or a Surfer DSAA grid.

CSV contains station coordinates, confidence, uncertainty, and composite components. The Surfer grid contains linearly interpolated confidence inside the station convex hull; cells outside it (and optionally across triangles longer than max_triangle_edge) use Surfer’s blank value. grid_shape is (nx, ny).

Parameters:
Return type:

dict[str, Path]

pycsamt.emtools.frequency_confidence_table(sites, *, method='composite', weights=None, ci_hi=0.95, ci_lo=0.85, relerr_threshold=0.2, offdiag_tolerance_log10=0.35, diagonal_leakage_max=0.35, phase_jump_tolerance_deg=90.0, spatial_tolerance_log10=0.6, spacing_m=200.0, force_spacing=False, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Return frequency-level confidence scores for EM stations.

The returned table has one row for each station-frequency sample. It is designed as a reusable quality-control source for plots, masking rules, and inversion-preparation reports. method="presence" scores only finite impedance-tensor availability. method="composite" combines coverage, tensor uncertainty, off-diagonal consistency, diagonal leakage, phase smoothness, and same-frequency spatial coherence.

See station_confidence_table() for how distance_m, spacing_m, and force_spacing interact.

Parameters:
Return type:

Any

pycsamt.emtools.qc_flags(sites, *, min_frac_ok=0.6, min_snr_med=2.0, max_skew_med=6.0, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

DataFrame

pycsamt.emtools.station_confidence_table(sites, *, method='composite', weights=None, relerr_threshold=0.2, offdiag_tolerance_log10=0.35, diagonal_leakage_max=0.35, phase_jump_tolerance_deg=90.0, spatial_tolerance_log10=0.6, spacing_m=200.0, force_spacing=False, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Return station-level confidence scores for EM transfer functions.

method="presence" reproduces the legacy criterion based only on finite tensor rows. method="composite" combines several station trust indicators: finite data coverage, tensor uncertainty when error tensors exist, off-diagonal consistency, diagonal leakage, phase smoothness, and spatial coherence with neighboring stations.

distance_m in the returned table is the real inter-station distance projected along the survey line, derived from EDI coordinates (east/north, or lat/lon as a fallback) whenever at least two stations carry usable coordinates. spacing_m is only used as a uniform per-station fallback for stations without coordinates, or for the whole line when no station has any. Pass force_spacing=True to bypass coordinate lookup entirely and lay every station out at uniform spacing_m steps – e.g. when the available coordinates are known to be unreliable.

Parameters:
Return type:

Any

pycsamt.emtools.plot_confidence_band_summary(sites, *, method='composite', ci_hi=0.95, ci_lo=0.85, figsize=(8.0, 4.0), spacing_m=200.0, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot line-wide confidence statistics for each period sample.

Parameters:
Return type:

Axes

pycsamt.emtools.plot_confidence_component_map(sites, *, method='composite', components=None, coordinate_system='auto', line_labels=None, connect=True, cmap='RdYlGn', marker_size=38.0, station_labels=False, station_label_step=None, line_names=True, ncols=4, figsize=None, map_aspect='auto', panel_letters=True, colorbar_label='Confidence component score', axes=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Plot publication-style maps of confidence and its components.

Every panel uses the same station geometry, extent, and fixed 0–1 color normalization, allowing direct scientific comparison between component scores. The default seven panels are overall confidence, coverage, uncertainty, off-diagonal consistency, diagonal leakage, phase smoothness, and spatial coherence.

map_aspect="auto" is the compact publication default. Use "geographic" to preserve longitude/latitude ground proportions or "equal" for equal numeric axis units.

Parameters:
Return type:

Figure

pycsamt.emtools.plot_confidence_map(sites, *, method='composite', coordinate_system='auto', mode='auto', line_labels=None, connect=True, contour_levels=12, colorbar_min='auto', segmented_colors=True, max_triangle_edge=None, show_stations=True, show_confidence_values=False, confidence_value_step=None, confidence_value_fmt='{:.2f}', confidence_value_fontsize=7.0, show_contour_lines=False, contour_line_levels=None, contour_line_colors='0.25', contour_linewidths=0.65, contour_linestyles='solid', contour_labels=True, contour_label_fmt='%.2f', contour_label_fontsize=7.0, contour_label_inline=True, show_threshold_contours=True, threshold_line_color='black', threshold_linewidth=1.35, threshold_linestyle='solid', station_labels=False, station_label_step=None, ci_hi=0.95, ci_lo=0.85, boundary_levels=None, cmap='RdYlGn', marker_size=72.0, figsize=(7.5, 5.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Map station-level confidence at geographic or projected coordinates.

Route and scatter modes do not interpolate between stations. Contour mode uses a triangular surface inside the survey’s convex hull and rejects collinear station layouts, so a single profile cannot accidentally appear to provide two-dimensional spatial coverage.

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Input stations accepted by station_confidence_table().

  • method ({"presence", "composite"}) – Confidence scoring method.

  • coordinate_system ({"auto", "geographic", "projected"}) – "auto" prefers longitude/latitude and falls back to easting/northing. The selected pair must be available per station.

  • mode ({"auto", "scatter", "route", "contour"}) – Map representation. "auto" selects "route" when connect is true and "scatter" otherwise. "contour" requires at least three non-collinear station coordinates.

  • line_labels (mapping, sequence, str, or None) – Optional survey-line membership. A mapping is keyed by station name; a sequence follows table order; one string assigns every station to that line. When omitted all stations form one route.

  • connect (bool) – Overlay routes within each line. In contour mode this is useful for retaining the acquisition geometry above the interpolated surface.

  • contour_levels (int or array-like) – Number of discrete filled intervals, or explicit boundaries.

  • colorbar_min (float or "auto") – Lower contour/colorbar boundary. "auto" rounds down the observed minimum: to a 0.05 step when all CR values are at least 0.5, otherwise to a 0.1 step. Applies consistently to contour, route, and scatter modes. Pass 0.0 to retain the original full 0–1 scale.

  • segmented_colors (bool) – Use discrete color intervals with explicit breaks at 0.50, ci_lo, ci_hi, and 1.00. Set false for a continuous gradient.

  • max_triangle_edge (float or None) – Optional maximum triangle-edge length, expressed in the selected map units. Triangles crossing a larger unsurveyed gap are masked.

  • show_stations (bool) – Draw confidence-coloured station markers over the map.

  • show_confidence_values (bool) – Annotate the numeric confidence beside each displayed station.

  • confidence_value_step (int or None) – Label every Nth station. None automatically thins surveys with more than 20 stations while retaining the final station value.

  • confidence_value_fmt (str) – Python format string for station confidence annotations.

  • confidence_value_fontsize (float) – Font size for station confidence annotations.

  • show_contour_lines (bool) – Overlay ordinary confidence isolines and, by default, their values.

  • contour_line_levels (array-like or None) – Isoline values. None uses the discrete filled-contour boundaries that fall within the observed confidence range.

  • contour_line_colors (Any) – Matplotlib styling passed to tricontour().

  • contour_linewidths (Any) – Matplotlib styling passed to tricontour().

  • contour_linestyles (Any) – Matplotlib styling passed to tricontour().

  • contour_labels (bool) – Label ordinary contour lines with their confidence values.

  • contour_label_fmt (str, mapping, or callable) – Label formatter passed to clabel().

  • contour_label_fontsize (float) – Numeric contour-label size.

  • contour_label_inline (bool) – Remove the line beneath each numeric contour label.

  • show_threshold_contours (bool) – Delineate 0.50, ci_lo, and ci_hi on the contour surface when those values are crossed by the observed data. The colorbar always marks every applicable boundary.

  • threshold_line_color (Any) – Styling for the emphasized confidence-class borders.

  • threshold_linewidth (float) – Styling for the emphasized confidence-class borders.

  • threshold_linestyle (str) – Styling for the emphasized confidence-class borders.

  • station_labels (bool) – Annotate station names. station_label_step controls thinning.

  • ci_hi (float) – Safe and recoverable/review confidence thresholds.

  • ci_lo (float) – Safe and recoverable/review confidence thresholds.

  • boundary_levels (array-like or None) – Confidence-class borders used by segmented colors, emphasized isolines, and colorbar ticks. None uses (0.50, ci_lo, ci_hi, 1.00). For example, pass (0.50, 0.85, 0.90, 1.00) for boundary-only contours at those values.

  • cmap (str) – Matplotlib appearance controls.

  • marker_size (float) – Matplotlib appearance controls.

  • figsize (tuple[float, float]) – Matplotlib appearance controls.

  • recursive (bool) – Passed through the standard EMTools site-loading API.

  • on_dup (str) – Passed through the standard EMTools site-loading API.

  • strict (bool) – Passed through the standard EMTools site-loading API.

  • verbose (int) – Passed through the standard EMTools site-loading API.

  • ax (matplotlib.axes.Axes or None) – Existing axes, or None to create one.

  • station_label_step (int | None)

Returns:

The map axes. Its _pycsamt_coordinate_system attribute records the coordinate system selected by "auto".

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_confidence_method_comparison(sites, *, coordinate_system='auto', line_labels=None, connect=True, ci_hi=0.95, ci_lo=0.85, confidence_cmap='RdYlGn', difference_cmap='RdBu', marker_size=42.0, station_labels=False, station_label_step=None, line_names=True, show_statistics=True, difference_limit=None, map_aspect='auto', figsize=(11.2, 4.5), axes=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Compare presence and composite confidence on matched station maps.

Panels show presence confidence, composite confidence, and composite - presence. The confidence panels share a fixed 0–1 color scale; the difference panel uses a symmetric zero-centred scale so score gains and penalties remain visually comparable.

Parameters:
Return type:

Figure

pycsamt.emtools.plot_confidence_profile(sites, *, method='presence', ci_hi=0.95, ci_lo=0.85, shade_recoverable=True, shade_mode='score', annotate_low=True, annotate_low_step=None, station_labels=True, station_label_step=None, show_errorbars=True, smart_ylim=True, ylim=None, weights=None, spacing_m=200.0, force_spacing=False, figsize=(9.0, 4.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Profile confidence-ratio (CR) scatter plot along the survey line.

Reproduces the Fig. 3 style from Kouadio et al. (2024): one dot per station coloured green (CR >= ci_hi), pink (ci_lo <= CR < ci_hi), or red (CR < ci_lo), with dashed threshold lines.

With method="presence", CR is the fraction of frequencies with a valid finite Z tensor. With method="composite", CR combines coverage, tensor uncertainty, off-diagonal consistency, diagonal leakage, phase smoothness, and neighbor coherence.

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Input sites.

  • ci_hi (float) – Upper CR threshold (default 0.95, “safe”, green).

  • ci_lo (float) – Lower CR threshold (default 0.85, “recoverable”, pink).

  • shade_recoverable (bool) – If True, draw an interval cue for stations below ci_hi.

  • shade_mode ({"score", "full", "none"}) – "score" draws compact vertical intervals tied to each station point. "full" preserves the older full-height station shading. "none" disables station interval shading.

  • annotate_low (bool) – If True, draw a rotated station-name label above each point below ci_lo. Set False to turn these off entirely – e.g. when station_labels (the top-axis station ticks) already identifies every station and the per-point labels would just duplicate it.

  • annotate_low_step (int or None) – Gap between labeled low-confidence points, analogous to station_label_step but applied only to the (typically much smaller) subset of points below ci_lo. None auto-thins once there are more than 18 low points, the same threshold used for the top axis, so a survey where most stations are flagged doesn’t end up with every single one labeled. 1 forces every low point to be labeled regardless of count.

  • station_label_step (int or None) – Gap between visible station labels on the top axis. None chooses a readable spacing automatically while keeping all station tick marks.

  • show_errorbars (bool) – If True, draw the station-level confidence uncertainty returned by station_confidence_table().

  • smart_ylim (bool) – If True, zoom the lower y-limit when every station confidence is above ci_lo so small departures from the safe threshold remain visible.

  • ylim (tuple of float or None) – Explicit y-axis limits. Overrides smart_ylim when provided.

  • spacing_m (float) – The x-axis is real inter-station distance projected along the survey line (from EDI east/north, or lat/lon as a fallback) whenever at least two stations carry usable coordinates. spacing_m is only used as a uniform fallback for stations without coordinates, or for the whole line when none have any.

  • force_spacing (bool) – If True, skip coordinate lookup entirely and lay every station out at uniform spacing_m steps – e.g. when the available coordinates are known to be unreliable and a user-supplied spacing should be trusted instead.

  • figsize (tuple) – Figure size when a new figure is created.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Passed to ensure_sites().

  • strict (bool) – Passed to ensure_sites().

  • verbose (int) – Passed to ensure_sites().

  • ax (matplotlib.axes.Axes or None) – Axes to draw on; created if None.

  • method (str)

  • station_labels (bool)

  • weights (dict[str, float] | None)

Returns:

ax

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_frequency_confidence_psection(sites, *, method='composite', ci_hi=0.95, ci_lo=0.85, metric='confidence', cmap='RdYlGn', section='dynamic', figsize=None, station_label_step=None, station_preset='pseudosection', station_style=None, spacing_m=200.0, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot frequency confidence as a station-period pseudo-section.

Parameters:
Return type:

Axes

pycsamt.emtools.plot_station_confidence_dashboard(sites, *, station=None, method='composite', ci_hi=0.95, ci_lo=0.85, axes=None, figsize=(10.5, 6.0), spacing_m=200.0, recursive=True, on_dup='replace', strict=False, verbose=0)#

Plot a 2-by-3 confidence dashboard for one station.

The dashboard separates the final confidence score from the diagnostic components used to build it, avoiding the visual crowding of a single overlay axis.

Parameters:
Return type:

Figure

pycsamt.emtools.plot_station_confidence_spectrum(sites, *, station=None, method='composite', ci_hi=0.95, ci_lo=0.85, figsize=(7.0, 4.0), spacing_m=200.0, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot confidence components versus period for one station.

Parameters:
Return type:

Axes

pycsamt.emtools.notch_powerline(sites, *, mains_hz=50.0, n_harm=30, tol_hz=0.08, snap_frac=0.01, mode='interp', also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Suppress mains-frequency harmonics in impedance and tipper data.

mains_hz is normally a fixed number (50 or 60): each harmonic k * mains_hz is matched against real sampled frequencies within a fixed +-tol_hz window. Real EDI frequency grids are usually log-spaced rather than sampled exactly on multiples of 50/60 Hz, so a harmonic can fall outside that tight window and go un-notched with no warning.

Pass mains_hz="auto" to make this data-driven instead: pyCSAMT scores 50 Hz and 60 Hz (the only two real-world AC grid frequencies) against this survey’s actual pooled frequency array, resolves to whichever explains more harmonics, then snaps each harmonic to its single nearest real sample (within a relative snap_frac tolerance, default 1%, deliberately tight – real AC grids drift far less than that, and a wide window just finds the nearest point on a coarse log-spaced grid regardless of whether it is really mains-related) instead of requiring an exact-window match. If neither 50 Hz nor 60 Hz explains at least a handful of harmonics this tightly, the grid likely has no identifiable mains signature at all (common for coarse, general-purpose sounding schedules with only a few points spread over several decades) – “auto” then leaves the data untouched rather than guessing and notching an unrelated frequency. With verbose>=1 the resolved fundamental (or the “no reliable signature” outcome) is reported via a warning; with verbose>=2 every individual snapped harmonic is too. Passing a plain number is unaffected by any of this – output is identical to every previous release.

Parameters:
pycsamt.emtools.smooth_logfreq(sites, *, win=5, kind='tri', also='both', gate_snr=None, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.smooth_rho_phase(sites, *, components='offdiag', degree=3, min_points=None, smooth_rho=True, smooth_phase=True, robust=True, robust_iters=3, blend=1.0, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Smooth apparent resistivity and phase trends, then rebuild Z.

The function operates station-by-station along the frequency axis. It fits polynomial trends versus \(\log_{10}(f)\) to \(\log_{10}(\rho_a)\) and to unwrapped impedance phase, then writes the corresponding complex impedance back into each selected tensor component. This keeps apparent resistivity and phase physically coupled through the same complex Z tensor instead of only smoothing display arrays.

sitesobject

Any input accepted by ensure_sites().

components : {“offdiag”, “diagonal”, “all”, “xx”, “xy”, “yx”, “yy”}

or sequence, default “offdiag”

Tensor components to smooth. The default targets xy and yx because they are the usual MT/CSAMT apparent-resistivity and phase components used for interpretation and 2-D preparation.

degreeint, default 3

Polynomial degree for the log-frequency trend. It is automatically reduced when a station has too few valid frequencies.

min_pointsint or None, default None

Minimum number of finite points required per component. If None, uses degree + 2.

smooth_rho, smooth_phasebool, default True

Select whether the impedance amplitude, phase angle, or both are replaced by the fitted trend.

robustbool, default True

Use a Tukey-style iteratively reweighted polynomial fit to reduce the influence of isolated spikes.

robust_itersint, default 3

Maximum robust reweighting iterations.

blendfloat, default 1.0

Blend between original and smoothed curves. 1 fully applies the trend; 0.5 applies half of the correction.

inplacebool, default False

If True, mutate the normalized input sites. Otherwise work on a best-effort copy of the underlying EDI objects and return new Sites.

recursive, on_dup, strict, verbose

Forwarded to ensure_sites() / to_edis.

pycsamt.site.base.Sites

Sites containing the smoothed impedance tensors.

Apparent resistivity is smoothed in logarithmic space because \(\rho_a\) commonly spans orders of magnitude. Phase is unwrapped before fitting so that crossings near \(\pm 180^\circ\) do not create artificial jumps.

Parameters:
Return type:

Any

pycsamt.emtools.shrink_to_group_trend(sites, *, groups=None, group_key=None, lam=0.25, gate_harm=True, mains_hz=50.0, n_harm=30, tol_hz=0.08, also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.remove_noise_pipeline(sites, *, mains_hz=50.0, n_harm=30, tol_hz=0.08, notch_mode='interp', smooth_win=5, smooth_kind='tri', gate_snr=2.5, group_shrink=False, shrink_lam=0.25, groups=None, also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.hampel_filter_freq(sites, *, win=3, nsig=3.0, on='both', domain='reim', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Remove frequency-domain outliers with a sliding Hampel filter.

Parameters:
pycsamt.emtools.spatial_median_filter(sites, *, half_window=2, lam=0.25, on='z', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.fixed_length_moving_average(sites, *, window=5, component='all', frequency_rtol=1e-06, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Apply a fixed-length EMAP moving average along a profile.

The filter replaces each selected impedance component by the local arithmetic mean of neighboring stations at the same frequency. It is a v2 functional equivalent of the classic FLMA idea and preserves the input frequency grids.

Parameters:
Return type:

Any

pycsamt.emtools.trimmed_moving_average(sites, *, window=5, component='all', frequency_rtol=1e-06, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Apply a trimmed EMAP moving average along a profile.

The filter is similar to fixed_length_moving_average(), but when a full enough window is available it removes the smallest and largest magnitudes before averaging. This makes the profile smoothing less sensitive to isolated bad stations.

Parameters:
Return type:

Any

pycsamt.emtools.apply_emap_filter(sites, *, method='ama', window=5, window_m=1500.0, spacing_m=200.0, component='all', comp='det', frequency_rtol=1e-06, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Apply one EMAP-style spatial filter to MT/AMT sites.

method='ama' delegates to correct_static_shift(), the existing Hanning adaptive moving-average correction. 'flma' and 'tma' apply count-based spatial smoothing along station order.

Parameters:
Return type:

Any

pycsamt.emtools.confidence_gated_emap_filter(sites, *, before_sites=None, method='flma', confidence_method='composite', component='xy', ci_hi=0.9, ci_lo=0.5, weights=None, blend_power=1.0, window=5, window_m=1500.0, spacing_m=200.0, comp='det', frequency_rtol=1e-06, recursive=True, on_dup='replace', strict=False, verbose=0)#

Apply EMAP filtering only as strongly as confidence requires.

Rows with confidence greater than or equal to ci_hi are preserved. Rows below ci_lo are fully replaced by the EMAP-filtered estimate. Rows between the two limits are linearly blended, with optional blend_power shaping.

Parameters:
Return type:

EMAPFilterResult

class pycsamt.emtools.EMAPFilterResult(sites, report, decisions, method, confidence_method, ci_hi, ci_lo)#

Bases: object

Container returned by confidence-gated EMAP filtering.

Parameters:
sites: Any#
report: DataFrame#
decisions: DataFrame#
method: str#
confidence_method: str#
ci_hi: float#
ci_lo: float#
property n_preserved: int#

Number of station-frequency rows left unchanged.

property n_blended: int#

Number of station-frequency rows partially blended.

property n_filtered: int#

Number of station-frequency rows fully filtered.

summary()#

Return a compact text summary.

Return type:

str

pycsamt.emtools.emap_filter_report(before_sites, after_sites, *, component='xy', period_s=None, frequency_hz=None)#

Summarize station-level changes after an EMAP-style filter.

The report compares the selected impedance component before and after a filter. When period_s or frequency_hz is provided, the table also includes a reference-row before/after profile value for each station.

Parameters:
  • before_sites (Any)

  • after_sites (Any)

  • component (str)

  • period_s (float | None)

  • frequency_hz (float | None)

Return type:

DataFrame

pycsamt.emtools.plot_emap_filter_profile(before_sites, after_sites=None, *, method='flma', component='xy', period_s=None, frequency_hz=None, window=5, window_m=1500.0, spacing_m=200.0, comp='det', figsize=(9.5, 4.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None, **filter_kws)#

Plot a before/after EMAP filter station profile.

Parameters:
Return type:

Axes

pycsamt.emtools.plot_emap_filter_psection(before_sites, after_sites=None, *, method='flma', component='xy', window=5, window_m=1500.0, spacing_m=200.0, comp='det', cmap='RdYlBu_r', delta_cmap='RdBu_r', clim=None, clim_pct=(2.0, 98.0), delta_vlim=None, delta_vlim_pct=95.0, axes=None, figsize=(11.0, 8.2), station_label_step=1, station_preset='pseudosection', station_style=None, **filter_kws)#

Plot before/after/delta pseudo-sections for an EMAP filter.

Parameters:
Return type:

Figure

pycsamt.emtools.rpca_offdiag_denoise(sites, *, rank=2, keep_phase=True, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.enforce_offdiag_consistency(sites, *, mode='anti', lam=0.5, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.mask_incoherent_freqs(sites, *, snr_thresh=2.5, min_frac=0.4, also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Mask frequencies that fail the requested cross-station SNR vote.

Parameters:
pycsamt.emtools.drop_freqs_manual(sites, *, drop_freqs=(), tol_rel=0.005, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Drop Z (and tipper) rows at user-specified frequencies.

Parameters:
  • sites (object) – Any input accepted by ensure_sites().

  • drop_freqs (sequence of float) – Frequencies (Hz) to remove. Each value is matched within tol_rel relative tolerance: |f - f_drop| / f_drop < tol_rel.

  • tol_rel (float, default 0.005) – Relative frequency tolerance for matching (≈0.5%).

  • inplace (bool, default False) – If True mutate; otherwise work on a copy.

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

Sites with the specified frequency rows removed from Z, Z errors, apparent resistivity/phase, and tipper arrays where present.

Return type:

Sites

pycsamt.emtools.correct_static_shift(sites, *, window_m=1500.0, spacing_m=200.0, comp='det', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Remove static shift via Hanning adaptive moving-average (AMA) spatial filter.

Implements the Torres-Verdín & Bostick (1992) approach used in Kouadio et al. (2024):

  1. For each frequency, build the spatial log(ρ_a) profile across stations.

  2. Apply a Hanning low-pass spatial filter with full-width window_m.

  3. The static-shift correction factor at station i is C_i = sqrt(ρ_smooth_i / ρ_obs_i) (in log space: log C = 0.5 (log ρ_smooth log ρ_obs)).

  4. Update every Z component: Z_corrected = Z × C.

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Input sites.

  • window_m (float) – Full Hanning window width [m] (Torres-Verdín W_H). Stations further than window_m/2 contribute zero weight.

  • spacing_m (float) – Fallback station spacing [m] used when EDI metadata carries no coordinate information.

  • comp ({"det", "xy", "yx"}) – Apparent-resistivity component used to estimate the static shift. "det" uses the arithmetic mean of |Z_xy|² and |Z_yx|².

  • inplace (bool) – If True, modify the input sites in place. If False (default), return a new Sites object with corrected Z tensors.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Passed to ensure_sites().

  • strict (bool) – Passed to ensure_sites().

  • verbose (int) – Passed to ensure_sites().

Returns:

Sites with static-shift-corrected Z tensors (when inplace=False).

Return type:

Sites

pycsamt.emtools.nr_qc_delta_offdiag_psection(sites, *, method='pipeline', vlim=None, figsize=(9.0, 4.8), ax=None, **denoise)#

Plot denoising changes in off-diagonal impedance as a pseudosection.

Parameters:
Return type:

Axes

pycsamt.emtools.nr_qc_snr_gain_profile(sites, *, method='pipeline', pband=None, figsize=(8.6, 3.6), ax=None, **denoise)#

Plot the station-by-station SNR gain produced by denoising.

Parameters:
Return type:

Axes

pycsamt.emtools.nr_qc_harmonic_waterfall(sites, *, method='notch', mains_hz=50.0, n_harm=30, tol_hz=0.08, figsize=(9.0, 4.6), ax=None, **denoise)#

Plot harmonic-noise reduction by station and mains harmonic.

Parameters:
Return type:

Axes

pycsamt.emtools.nr_qc_station_offdiag_curves(sites, *, method='pipeline', station=None, mains_hz=50.0, n_harm=12, tol_hz=0.08, figsize=(8.0, 4.2), ax=None, **denoise)#

Compare raw and denoised off-diagonal curves for one station.

Parameters:
Return type:

Axes

pycsamt.emtools.estimate_ss_ama(sites, *, sort_by=None, half_window=3, weights='tri', pband=None, max_skew=6.0, robust_freq='median', robust_overall='median', recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Estimate AMA static-shift correction factors.

Computes the Adaptive Moving-Average (AMA) spatial log10-resistivity trend across half_window neighbours, then returns the per-station deviation from that trend as a correction-factor table.

Parameters:
  • sites (Sites, str, Path, list, EDICollection) – EDI data source accepted by ensure_sites().

  • sort_by (str, optional) – Along-line ordering policy. None inherits pycsamt.api.PYCSAMT_ORDERING. Explicit alternatives include 'auto', 'chainage', 'lon', 'lat', 'name', and 'input'.

  • half_window (int, default 3) – Neighbours on each side of the target.

  • weights (str, default 'tri') – Spatial weight scheme: 'tri' (triangular), 'gauss', or 'uniform'.

  • pband (tuple of float or None) – Period band (p_min_s, p_max_s) in seconds. None uses all periods.

  • max_skew (float or None, default 6.0) – Phase-tensor skew threshold. Points where |beta| > max_skew are excluded.

  • robust_freq (str, default 'median') – Neighbour aggregation per frequency.

  • robust_overall (str, default 'median') – Reduce per-frequency deltas to a scalar.

  • recursive (bool, default True) – Recursive EDI directory search.

  • on_dup (str, default 'replace') – Duplicate-station resolution.

  • strict (bool, default False) – Raise on EDI parse errors.

  • verbose (int, default 0) – Verbosity level.

  • api (bool or None) – Return an APIFrame when True.

Returns:

One row per station with columns:

station

Station identifier.

delta_log10_rho

Estimated log10 shift. Positive = rho above spatial trend.

fac_rho

Resistivity correction factor \(10^{-\delta}\).

fac_z

Impedance correction factor \(10^{-0.5\delta}\).

n_used

Frequencies used in the estimate.

Return type:

pandas.DataFrame

See also

correct_ss_ama

estimate + apply in one call.

apply_ss_factors

apply a pre-built table.

Examples

from pycsamt.api import read_edis
from pycsamt.emtools.ss import (
    estimate_ss_ama,
)

survey = read_edis("L22PLT/")
sites = survey.collection
tbl = estimate_ss_ama(
    sites,
    half_window=3,
    sort_by="lon",
)
print(
    tbl[
        [
            "station",
            "delta_log10_rho",
            "fac_z",
        ]
    ]
)
pycsamt.emtools.apply_ss_factors(sites, factors, *, key='fac_z', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Apply pre-computed static-shift correction factors to sites.

Scales each site’s impedance tensor Z by a per-station correction factor from a table (e.g. from estimate_ss_ama(), estimate_ss_loess(), etc.) or dictionary.

Parameters:
  • sites (any) – EDI data source accepted by ensure_sites().

  • factors (dict or pandas.DataFrame) – If DataFrame, must contain 'station' and key columns. If dict, maps station names to correction factors.

  • key (str, default 'fac_z') – Column name or dict key holding the impedance scaling factors. Common choices are 'fac_z' (impedance) or 'fac_rho' (resistivity).

  • inplace (bool, default False) – Modify the input Sites object. When False, a corrected copy is returned.

  • recursive (bool, default True) – Recursive EDI directory search.

  • on_dup (str, default 'replace') – Duplicate-station resolution.

  • strict (bool, default False) – Raise on EDI parse errors.

  • verbose (int, default 0) – Verbosity level.

Returns:

Corrected Sites object (same type as input). When inplace is True the original is modified and returned.

Return type:

Sites

See also

estimate_ss_ama

Estimate factors via AMA.

estimate_ss_loess

Estimate factors via LOESS.

pycsamt.emtools.correct_ss_ama(sites, *, sort_by=None, half_window=3, weights='tri', pband=None, max_skew=6.0, robust_freq='median', robust_overall='median', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Correct static shift by the AMA method.

Estimates per-station log10-resistivity shift factors with estimate_ss_ama(), then scales each site’s impedance tensor Z by the corresponding fac_z column.

Parameters:
  • sites (Sites, str, Path, list, EDICollection) – EDI data source.

  • sort_by (str, optional) – Along-line ordering policy for AMA estimation. None inherits the package-wide ordering configuration.

  • half_window (int, default 3) – Neighbours on each side of the target.

  • weights (str, default 'tri') – Spatial weight scheme ('tri', 'gauss', or 'uniform').

  • pband (tuple of float or None) – Period band (p_min_s, p_max_s) in seconds.

  • max_skew (float or None, default 6.0) – Phase-tensor skew exclusion threshold.

  • robust_freq (str, default 'median') – Neighbour aggregation per frequency.

  • robust_overall (str, default 'median') – Reduce per-frequency deltas to a scalar.

  • inplace (bool, default False) – Modify the input Sites object in place. When False, returns a corrected copy.

  • recursive (bool, default True) – Recursive EDI directory search.

  • on_dup (str, default 'replace') – Duplicate-station resolution.

  • strict (bool, default False) – Raise on EDI parse errors.

  • verbose (int, default 0) – Verbosity level.

Returns:

Corrected Sites object (same type as input). When inplace is True the original object is modified and returned.

Return type:

Sites

See also

estimate_ss_ama

inspect factors before apply.

apply_ss_factors

apply a custom factor table.

Examples

from pycsamt.api import read_edis
from pycsamt.emtools.ss import (
    correct_ss_ama,
)

survey = read_edis("L22PLT/")
sites = survey.collection
sites_corr = correct_ss_ama(
    sites,
    half_window=3,
    sort_by="lon",
)
pycsamt.emtools.estimate_ss_loess(sites, *, half_window=3, poly=1, it=2, pband=None, max_skew=6.0, summary='median', recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Estimate static-shift factors via locally-weighted regression (LOESS).

Fits a local polynomial trend across neighbouring stations in the along-line direction, then returns the per-station deviation from that trend as correction factors.

Parameters:
  • sites (Sites, str, Path, list, EDICollection) – EDI data source accepted by ensure_sites().

  • half_window (int, default 3) – Neighbours on each side of the target.

  • poly (int, default 1) – Polynomial degree (0=constant, 1=linear).

  • it (int, default 2) – Robust iteration count.

  • pband (tuple of float or None) – Period band \((p_{min}, p_{max})\) in seconds. None uses all periods.

  • max_skew (float or None, default 6.0) – Phase-tensor skew threshold. Points where :math:`|\\beta| > ` max_skew are excluded.

  • summary (str, default 'median') – Per-station aggregation: 'median' or 'mean'.

  • recursive (bool, default True) – Recursive EDI directory search.

  • on_dup (str, default 'replace') – Duplicate-station resolution.

  • strict (bool, default False) – Raise on EDI parse errors.

  • verbose (int, default 0) – Verbosity level.

  • api (bool or None) – Return an APIFrame when True.

Returns:

One row per station with columns: station, delta_log10_rho, fac_rho, fac_z, n_used.

Return type:

pandas.DataFrame

See also

estimate_ss_ama

AMA (moving average) method.

estimate_ss_bilateral

Bilateral filtering method.

pycsamt.emtools.estimate_ss_bilateral(sites, *, half_window=4, sig_dist=None, sig_val=None, pband=None, max_skew=6.0, summary='median', recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Estimate static-shift factors via bilateral filtering.

Applies a combined spatial and range-based Gaussian filter (bilateral filter) to compute a local trend, then returns per-station deviations as correction factors.

Parameters:
  • sites (Sites, str, Path, list, EDICollection) – EDI data source accepted by ensure_sites().

  • half_window (int, default 4) – Spatial window (neighbours each side).

  • sig_dist (float or None) – Spatial Gaussian width (in index units). When None, defaults to \(0.5 \\times \\texttt{half\\_window}\).

  • sig_val (float or None) – Range (value) Gaussian width. When None, estimated from data.

  • pband (tuple of float or None) – Period band \((p_{min}, p_{max})\) in seconds.

  • max_skew (float or None, default 6.0) – Phase-tensor skew threshold.

  • summary (str, default 'median') – Aggregation: 'median' or 'mean'.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • api (bool or None) – Return an APIFrame when True.

Returns:

One row per station with columns: station, delta_log10_rho, fac_rho, fac_z, n_used.

Return type:

pandas.DataFrame

See also

estimate_ss_ama

Moving-average method.

estimate_ss_loess

Local polynomial method.

pycsamt.emtools.estimate_ss_refmedian(sites, *, pband=None, max_skew=6.0, smooth_sites=0, summary='median', recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Estimate static-shift factors via reference-median method.

Computes a global frequency-wise median resistivity across all stations, then estimates per-station shifts as deviations from this reference curve.

Parameters:
  • sites (Sites, str, Path, list, EDICollection) – EDI data source.

  • pband (tuple of float or None) – Period band \((p_{min}, p_{max})\) in seconds.

  • max_skew (float or None, default 6.0) – Phase-tensor skew threshold.

  • smooth_sites (int, default 0) – Optional smoothing window (reserved for future use).

  • summary (str, default 'median') – Aggregation: 'median' or 'mean'.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • api (bool or None) – Return an APIFrame when True.

Returns:

One row per station with columns: station, delta_log10_rho, fac_rho, fac_z, n_used.

Return type:

pandas.DataFrame

See also

estimate_ss_ama

Moving-average method.

estimate_ss_loess

Local polynomial method.

pycsamt.emtools.plot_ss_delta_psection(before, after, *, axis_y='logperiod', vlim=None, pband=None, figsize=(9.0, 4.8), verbose=0, ax=None)#

Plot pseudosection of static-shift change (corrected minus original).

Displays a heatmap showing the pointwise difference \(\Delta\log_{10}\rho = \rho_{after} - \rho_{before}\) across all stations and frequencies on a log-period y-axis.

Parameters:
  • before (any) – EDI data source (uncorrected sites).

  • after (any) – EDI data source (corrected sites).

  • axis_y (str, default 'logperiod') – Y-axis scale: 'logperiod' or 'period'.

  • vlim (float or None) – Symmetric colour range \(\pm \texttt{vlim}\). When None, auto-scales from data.

  • pband (tuple of float or None) – Period band \((p_{min}, p_{max})\) in seconds.

  • figsize ((float, float), default (9, 4.8)) – Figure size.

  • verbose (int, default 0) – Verbosity level.

  • ax (matplotlib.axes.Axes or None) – Draw on existing axes.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_ss_station_curves(before, after, *, station=None, pband=None, log_period=False, figsize=(7.8, 4.2), verbose=0, ax=None)#

Plot before-and-after apparent-resistivity curves for a single station.

Overlays two 1-D sounding curves (before correction and after correction) on a period x-axis to visualize the magnitude and frequency-dependence of the correction at one location.

Parameters:
  • before (any) – Uncorrected EDI data.

  • after (any) – Corrected EDI data.

  • station (str or None) – Station identifier. When None, the first common station is used.

  • pband (tuple of float or None) – Period band \((p_{min}, p_{max})\) in seconds.

  • log_period (bool, default False) – When True, plot \(\log_{10}(T)\) (s) on a linear x-axis (LOG10_PERIOD_LABEL), matching the pseudo-section convention used elsewhere in pycsamt.emtools. When False (default), plot period on a log-scaled axis.

  • figsize ((float, float), default (7.8, 4.2)) – Figure size.

  • verbose (int, default 0) – Verbosity level.

  • ax (matplotlib.axes.Axes or None) – Draw on existing axes.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_ss_delta_profile(before, after, *, pband=None, robust='median', figsize=(8.6, 3.6), verbose=0, ax=None)#

Plot per-station static-shift correction amplitudes as a bar chart.

Shows the median (or mean) of the frequency-dependent correction \(\Delta\log_{10}\rho\) at each station, making it easy to identify spatial patterns in the applied corrections.

Parameters:
  • before (any) – Uncorrected EDI data.

  • after (any) – Corrected EDI data.

  • pband (tuple of float or None) – Period band \((p_{min}, p_{max})\) in seconds.

  • robust (str, default 'median') – Aggregation method: 'median' or 'mean'.

  • figsize ((float, float), default (8.6, 3.6)) – Figure size.

  • verbose (int, default 0) – Verbosity level.

  • ax (matplotlib.axes.Axes or None) – Draw on existing axes.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.ss_qc_psection(sites, *, method='ama', return_sites=False, axis_y='logperiod', vlim=None, pband=None, figsize=(9.0, 4.8), verbose=0, ax=None, **corr)#

Estimate static-shift correction and plot delta pseudosection.

Combines automatic static-shift estimation with a heatmap visualization in one call. A convenience wrapper around a correction estimator and plot_ss_delta_psection().

Parameters:
Return type:

matplotlib.axes.Axes or (Axes, Sites)

pycsamt.emtools.ss_qc_station_curves(sites, *, method='ama', station=None, return_sites=False, pband=None, figsize=(7.8, 4.2), verbose=0, ax=None, **corr)#

Estimate correction and plot before/after curves for one station.

A convenience wrapper combining automatic static-shift estimation with 1-D curve visualization.

Parameters:
Return type:

matplotlib.axes.Axes or (Axes, Sites)

pycsamt.emtools.ss_qc_profile(sites, *, method='ama', return_sites=False, pband=None, robust='median', figsize=(8.6, 3.6), verbose=0, ax=None, **corr)#

Estimate correction and plot per-station shift profile.

A convenience wrapper for automatic static-shift estimation with bar-chart visualization of the per-station amplitudes.

Parameters:
Return type:

matplotlib.axes.Axes or (Axes, Sites)

pycsamt.emtools.plot_ss_comparison_psection(logRho_before, logRho_after, *, freqs, station_labels=None, show_delta=True, cmap='RdYlBu_r', delta_cmap='RdBu_r', clim=None, clim_pct=(2.0, 98.0), delta_vlim=None, delta_vlim_pct=95.0, period_up=True, title_before='(a) Before static-shift correction', title_after='(b) After static-shift correction', title_delta='(c) Correction amplitude $\\Delta\\log_{10}\\rho$', suptitle='', xlabel='Station', ylabel='Period (s)', n_yticks=7, colorbar_label='$\\log_{10}\\,\\rho_a$ (Ω·m)', delta_colorbar_label='$\\Delta\\log_{10}\\rho$', tick_label_rotation=45.0, tick_fontsize=7, figsize=None, axes=None)#

Two- or three-panel pseudo-section comparison for static-shift correction.

The before and after panels share a colour scale so that the station-dependent vertical offsets are directly visible. The optional third panel shows the pointwise difference Δ log₁₀ ρ = after − before on a diverging scale, making the spatial pattern of the correction explicit.

Parameters:
  • logRho_before (ndarray, shape (n_st, n_f)) – Log₁₀ apparent resistivity before static-shift correction (Ω·m).

  • logRho_after (ndarray, shape (n_st, n_f)) – Log₁₀ apparent resistivity after static-shift correction (Ω·m).

  • freqs (ndarray, shape (n_f,)) – Frequency array in Hz. Need not be sorted.

  • station_labels (list of str or None) – X-axis tick labels. Defaults to "0", "1", .

  • show_delta (bool, default True) – Append a third panel showing Δ log₁₀ ρ.

  • cmap (str, default "RdYlBu_r") – Colormap for the before/after panels.

  • delta_cmap (str, default "RdBu_r") – Diverging colormap for the Δ panel.

  • clim ((vmin, vmax) or None) – Explicit colour limits (log₁₀ Ω·m) shared by the before/after panels.

  • clim_pct ((lo, hi), default (2.0, 98.0)) – Percentile bounds for automatic clim.

  • delta_vlim (float or None) – Symmetric limit (−δ, +δ) for the Δ panel. When None, derived from delta_vlim_pct of |Δ|.

  • delta_vlim_pct (float, default 95.0)

  • period_up (bool, default True) – Long period at the top of each panel (MT convention).

  • title_before (str) – Per-panel titles. Pass "" to suppress.

  • title_after (str) – Per-panel titles. Pass "" to suppress.

  • title_delta (str) – Per-panel titles. Pass "" to suppress.

  • suptitle (str) – Figure-level title.

  • xlabel (str) – Axis labels.

  • ylabel (str) – Axis labels.

  • n_yticks (int, default 7) – Number of log-period y-ticks.

  • colorbar_label (str)

  • delta_colorbar_label (str)

  • tick_label_rotation (float, default 45.0) – Station tick rotation (degrees).

  • tick_fontsize (int, default 7)

  • figsize ((w, h) or None) – Override automatic size.

  • axes (sequence of Axes or None) – Pre-created axes (length 2 without delta, 3 with).

Returns:

fig

Return type:

matplotlib.figure.Figure

pycsamt.emtools.plot_ss_1d_curves(logRho_before, logRho_after, *, freqs, stations=None, station_labels=None, n_cols=4, max_stations=16, color_before=<object object>, color_after=<object object>, ls_before=<object object>, ls_after=<object object>, marker_before=<object object>, marker_after=<object object>, marker_size=<object object>, lw=<object object>, log_period=True, show_shift_annotation=True, annotation_fontsize=7, ylabel='$\\log_{10}\\, \\rho_a$ (Ω·m)', xlabel='Period (s)', axes=None, figsize=None, title='', legend_loc='best', show_grid=True)#

Per-station 1-D apparent-resistivity curves: before and after correction.

Lays out a grid of subplots (one per selected station) each showing the before/after sounding curves on a period x-axis. A small annotation reports the mean correction amplitude Δ per station, making it easy to spot outliers.

Parameters:
  • logRho_before (ndarray, shape (n_st, n_f))

  • logRho_after (ndarray, shape (n_st, n_f))

  • freqs (ndarray, shape (n_f,) Hz.)

  • stations (list of int, list of str, or None) – Stations to display. Integers are row indices into logRho_before. Strings are matched against station_labels. None → all stations, capped at max_stations.

  • station_labels (list of str or None) – Label for each row. Defaults to "0", "1", .

  • n_cols (int, default 4) – Subplot grid columns.

  • max_stations (int, default 16) – Cap when stations is None.

  • color_before (str, default "#2c7bb6" (blue))

  • color_after (str, default "#d7191c" (red))

  • ls_before (str, default "--")

  • ls_after (str, default "-")

  • marker_before (str)

  • marker_after (str)

  • marker_size (float, default 3.0)

  • lw (float, default 1.2)

  • log_period (bool, default True) – Log-scale period x-axis.

  • show_shift_annotation (bool, default True) – Print mean Δ log₁₀ ρ in the lower-right corner of each subplot.

  • annotation_fontsize (int, default 7)

  • ylabel (str)

  • xlabel (str)

  • figsize ((w, h) or None)

  • title (str) – Figure-level title.

  • legend_loc (str, default "best") – Legend location (first subplot only).

  • show_grid (bool, default True)

Returns:

fig

Return type:

matplotlib.figure.Figure

pycsamt.emtools.plot_ss_summary(logRho_before, logRho_after, *, freqs, station_labels=None, cmap='RdYlBu_r', delta_cmap='RdBu_r', clim=None, clim_pct=(2.0, 98.0), delta_vlim=None, delta_vlim_pct=95.0, period_up=True, n_yticks=7, tick_label_rotation=45.0, tick_fontsize=7, colorbar_label='$\\log_{10}\\,\\rho_a$ (Ω·m)', shift_bar_color='#4c72b0', shift_bar_neg_color='#c44e52', shift_robust='median', suptitle='', axes=None, figsize=None)#

Four-panel summary figure for static-shift correction.

Layout:

┌──────────────┬──────────────┐
│  (a) Before  │  (b) After   │  shared y-axis · shared colorbar
├──────────────┴──────────────┤
│  (c) Δ log₁₀ ρ section     │  diverging colorbar
├──────────────────────────── ┤
│  (d) Per-station shift bar  │  positive/negative coloured bars
└─────────────────────────────┘
Parameters:
  • logRho_before (ndarray, shape (n_st, n_f))

  • logRho_after (ndarray, shape (n_st, n_f))

  • freqs (ndarray, shape (n_f,) Hz.)

  • station_labels (list of str or None) – X-axis tick labels for all panels.

  • cmap (str, default "RdYlBu_r")

  • delta_cmap (str, default "RdBu_r")

  • clim (see plot_ss_comparison_psection().)

  • clim_pct (see plot_ss_comparison_psection().)

  • delta_vlim (see plot_ss_comparison_psection().)

  • delta_vlim_pct (see plot_ss_comparison_psection().)

  • period_up (bool, default True)

  • n_yticks (int, default 7)

  • tick_label_rotation (float, default 45.0)

  • tick_fontsize (int, default 7)

  • colorbar_label (str)

  • shift_bar_color (str) – Bar colour for positive per-station shifts (default blue).

  • shift_bar_neg_color (str) – Bar colour for negative shifts (default red).

  • shift_robust ("median" | "mean") – Aggregation used to reduce per-frequency shifts to a scalar per station for panel (d).

  • suptitle (str) – Figure-level title.

  • figsize ((w, h) or None)

Returns:

fig

Return type:

matplotlib.figure.Figure

pycsamt.emtools.ss_comparison_psection(sites, *, method='ama', return_sites=False, station_labels=None, show_delta=True, cmap='RdYlBu_r', delta_cmap='RdBu_r', clim=None, clim_pct=(2.0, 98.0), delta_vlim=None, delta_vlim_pct=95.0, period_up=True, suptitle='', tick_label_rotation=45.0, tick_fontsize=7, figsize=None, verbose=0, **corr)#

Correct sites for static shift and plot a comparison pseudo-section.

A convenience wrapper that combines correct_ss_ama() (or the chosen method) with plot_ss_comparison_psection().

Parameters:
  • sites (any) – EDI paths, glob pattern, or Sites accepted by ensure_sites().

  • method ("ama" | "loess" | "bilateral" | "refmedian") – Static-shift estimator.

  • return_sites (bool, default False) – When True, return (fig, corrected_sites) instead of fig.

  • **corr (Any) – Forwarded to the correction estimator.

  • station_labels (list[str] | None)

  • show_delta (bool)

  • cmap (str)

  • delta_cmap (str)

  • clim (tuple[float, float] | None)

  • clim_pct (tuple[float, float])

  • delta_vlim (float | None)

  • delta_vlim_pct (float)

  • period_up (bool)

  • suptitle (str)

  • tick_label_rotation (float)

  • tick_fontsize (int)

  • figsize (tuple[float, float] | None)

  • verbose (int)

  • **corr

Returns:

fig – Or (fig, corrected_sites) when return_sites is True.

Return type:

matplotlib.figure.Figure

See also

plot_ss_comparison_psection

Lower-level function that accepts pre-built arrays directly.

pycsamt.emtools.plot_ss_radar(sites, *, station=None, pband=None, rotate='pt', rotate_stat='median', rotate_deg=0.0, radial='log10rho', theta_axis='logperiod', fill_between=False, colors=<object object>, marker=<object object>, ms=<object object>, lw=<object object>, ls=<object object>, figsize=(4.8, 4.8), recursive=True, on_dup='replace', strict=False, verbose=0, eps=1e-24, ax=None)#

Plot apparent resistivity against period on a polar grid.

Displays the off-diagonal impedance components (xy and yx) as radial curves on a polar coordinate system, where the azimuthal angle encodes frequency (or period) and the radius encodes resistivity magnitude. Useful for detecting anisotropy and strike angles across the full frequency spectrum.

Parameters:
  • sites (any) – EDI data source.

  • station (str or None) – Station identifier. When None, uses the first.

  • pband (tuple of float or None) – Period band \((p_{min}, p_{max})\) in seconds.

  • rotate (str, default 'pt') – Rotation mode: 'pt' (phase-tensor strike), 'deg' (fixed angle), or 'none' (no rotation).

  • rotate_stat (str, default 'median') – Per-frequency aggregation for phase-tensor rotation.

  • rotate_deg (float, default 0.0) – Fixed rotation angle (degrees) when rotate=’deg’.

  • radial (str, default 'log10rho') – Radial scale: 'log10rho' (log base 10 of apparent resistivity) or 'rho' (linear resistivity).

  • theta_axis (str, default 'logperiod') – Angular axis: 'logperiod', 'period', or 'freq' (Hz).

  • fill_between (bool, default False) – Shade the region between xy and yx curves.

  • colors (tuple or _UNSET) – (color_xy, color_yx). Defaults from style.

  • marker (_UNSET or values) – Line and marker style. Defaults from style.

  • ms (_UNSET or values) – Line and marker style. Defaults from style.

  • lw (_UNSET or values) – Line and marker style. Defaults from style.

  • ls (_UNSET or values) – Line and marker style. Defaults from style.

  • figsize ((float, float), default (4.8, 4.8)) – Figure size.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • eps (float, default 1e-24) – Numerical floor to avoid division by zero.

  • ax (matplotlib.axes.Axes or None) – Draw on existing axes (auto-creates polar if needed).

Returns:

Polar axes object.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.detect_near_surface(sites, *, f_split=1.0, pband=None, ns_threshold=2.0, ss_threshold=0.1, sort_by=None, half_window=3, weights='tri', max_skew=6.0, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Detect and classify near-surface distortion in CSAMT/MT apparent resistivity curves.

Distinguishes between two types of distortion:

  • Static effect — frequency-independent multiplicative shift of the whole ρ_a curve. Addressable by AMA/LOESS static-shift correction.

  • Near-surface effect — frequency-dependent distortion concentrated at high frequencies (f ≥ f_split), caused by shallow inhomogeneities. Not correctable by conventional static-shift methods; 2-D inversion is recommended.

Three per-station diagnostics are computed from the residuals of the ρ_a curve relative to an AMA spatial trend:

  1. NS index η = σ_HF / σ_LF — spread ratio between the high-frequency (f ≥ f_split) and low-frequency bands. η >> 1 is the hallmark of near-surface contamination.

  2. Gradient delta Δγ = |slope_HF − slope_LF| — absolute difference of the log-log slope d(log ρ_a)/d(log f) between the two bands.

  3. Static shift δ = median(log10 ρ_a − AMA trend) — classic AMA shift estimate over the full frequency range.

Classification:

"clean"

η ≤ ns_threshold, |δ| ≤ ss_threshold

"static"

η ≤ ns_threshold, |δ| > ss_threshold

"near_surface"

η > ns_threshold, |δ| ≤ ss_threshold

"mixed"

η > ns_threshold, |δ| > ss_threshold

Parameters:
  • sites (path, EDI-like, Sites, or iterable) – Any input accepted by ensure_sites().

  • f_split (float, default=1.0) – Frequency boundary in Hz separating the HF (f ≥ f_split) from the LF (f < f_split) band.

  • pband ((float, float) or None) – Period band (lo_s, hi_s) pre-filter applied before all computations.

  • ns_threshold (float, default=2.0) – η > this → near-surface flag.

  • ss_threshold (float, default=0.1) – |δ| > this (log10 units) → static-shift flag.

  • sort_by ({"auto", "chainage", "lon", "lat", "name", "input"}, optional) – Station ordering for the AMA spatial trend. None inherits the package-wide ordering configuration.

  • half_window (int, default=3) – Number of neighbouring stations each side in the AMA trend.

  • weights ({"tri", "gauss", "uniform"}, default="tri") – Spatial weighting for the AMA trend.

  • max_skew (float or None, default=6.0) – Phase-tensor skew ceiling; data above this are excluded.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • api (bool | None)

Returns:

One row per station with columns: station, n_hf, n_lf, sigma_hf, sigma_lf, ns_index, slope_hf, slope_lf, gradient_delta, ss_delta_log10, ns_flag, ss_flag, distortion_type.

Return type:

pandas.DataFrame

References

Lei et al. (2017), “The non-static effect of near-surface inhomogeneity on CSAMT data”, Geophysics.

pycsamt.emtools.plot_ns_detection(sites, *, f_split=1.0, pband=None, ns_threshold=2.0, ss_threshold=0.1, sort_by=None, half_window=3, weights='tri', max_skew=6.0, show_ss=True, figsize=(9.0, 4.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Bar chart of the NS index per station, colored by distortion type.

Each bar height is η = σ_HF / σ_LF. A dashed line marks ns_threshold. An optional secondary y-axis shows the static-shift estimate δ (log10 units) as a stem plot.

Parameters:
  • sites (path, EDI-like, Sites, or iterable)

  • f_split (float, default=1.0) – HF/LF split frequency in Hz.

  • pband ((float, float) or None)

  • ns_threshold (float)

  • ss_threshold (float)

  • sort_by ({"auto", "chainage", "lon", "lat", "name", "input"})

  • half_window (int) – Forwarded to detect_near_surface().

  • weights (str) – Forwarded to detect_near_surface().

  • max_skew (float | None) – Forwarded to detect_near_surface().

  • show_ss (bool, default=True) – If True and ax has room, overlay static-shift δ as a grey stem plot on a secondary y-axis.

  • figsize ((float, float), default=(9, 4.5))

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

  • ax (matplotlib.axes.Axes, optional) – Draw on existing axes.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.select_band(sites, *, fmin=None, fmax=None, band_hz=None, keep=None, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.drop_duplicates(sites, *, tol=1e-10, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.drop_low_confidence_frequencies(sites, *, method='composite', threshold=0.5, weights=None, also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Drop rows whose frequency confidence is below threshold.

The confidence scores are computed with pycsamt.emtools.qc.frequency_confidence_table(). The operation is station-aware: each station keeps or drops its own bad frequency rows. A new Sites object is returned unless inplace=True.

Parameters:
pycsamt.emtools.edit_frequencies_by_confidence(sites, *, mode='recover', before_sites=None, method='composite', threshold=0.5, ci_hi=0.9, ci_lo=0.5, weights=None, interpolation='linear', reject='drop', also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Edit frequency rows and return diagnostics in one workflow.

This is the high-level confidence-editing entry point. It applies one of the low-level edit strategies and immediately computes a station report and a station-frequency decision table. Use before_sites when sites is already an in-memory object and a reliable before/after comparison is required, because lower-level site editors can mutate the wrapped impedance objects while constructing the edited return value.

Parameters:
  • sites (path-like, EDI-like, Sites, or sequence) – Input data to edit. Path-like inputs are normally safe to use directly because they can be loaded independently by the package. In-memory objects should be paired with before_sites when the report must preserve an untouched baseline.

  • mode ({'recover', 'drop', 'mask'}, default 'recover') – Frequency-editing strategy. 'recover' interpolates recoverable rows in log-frequency and handles rejected rows according to reject. 'drop' removes rows below threshold. 'mask' keeps the frequency grid but replaces low-confidence tensor rows by missing values when the container allows it.

  • before_sites (optional) – Independent baseline used only for reporting and decision tracking. If omitted, sites is used as the baseline.

  • method (str, default 'composite') – Confidence metric passed to pycsamt.emtools.qc.frequency_confidence_table().

  • threshold (float, default 0.50) – Confidence threshold used by mode='drop' and mode='mask'.

  • ci_hi (float, default 0.90 and 0.50) – High-confidence and low-confidence limits used by mode='recover' and by the diagnostic report.

  • ci_lo (float, default 0.90 and 0.50) – High-confidence and low-confidence limits used by mode='recover' and by the diagnostic report.

  • weights (dict or None, default None) – Optional confidence-metric weights.

  • interpolation ({'linear', 'nearest'}, default 'linear') – Interpolation strategy for recoverable rows in mode='recover'.

  • reject ({'drop', 'mask', 'keep'}, default 'drop') – Handling of rows below ci_lo in mode='recover'.

  • also ({'z', 'tipper', 'both'}, default 'both') – Data blocks edited when present.

  • inplace (bool, default False) – Forwarded to the low-level edit function.

  • recursive (bool) – Site-loading options forwarded to ensure_sites().

  • on_dup (str) – Site-loading options forwarded to ensure_sites().

  • strict (bool) – Site-loading options forwarded to ensure_sites().

  • verbose (int) – Site-loading options forwarded to ensure_sites().

  • api (bool | None)

Returns:

Edited sites together with station-level and station-frequency diagnostics.

Return type:

FrequencyEditResult

class pycsamt.emtools.FrequencyEditResult(sites, report, decisions, mode, method, ci_hi, ci_lo, reject, interpolation)#

Bases: object

Container returned by confidence-based frequency editing.

Parameters:
sites: Any#
report: Any#
decisions: Any#
mode: str#
method: str#
ci_hi: float#
ci_lo: float#
reject: str#
interpolation: str#
property n_dropped: int#

Total number of dropped station-frequency rows.

property n_masked: int#

Total number of masked station-frequency rows.

property n_recovered: int#

Total number of recovered station-frequency rows.

summary()#

Return a compact text summary of the edit result.

Return type:

str

pycsamt.emtools.frequency_edit_decision_table(before_sites, after_sites, *, method='composite', ci_hi=0.9, ci_lo=0.5, weights=None, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Return one row per original station-frequency edit decision.

Parameters:
pycsamt.emtools.frequency_edit_report(before_sites, after_sites, *, method='composite', ci_hi=0.9, ci_lo=0.5, weights=None, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Summarize station-level changes after frequency editing.

The report compares the native frequency rows and finite tensor rows before and after an edit such as dropping, masking, or recovery. It also carries the median confidence from pycsamt.emtools.qc.frequency_confidence_table().

Parameters:
pycsamt.emtools.mask_low_confidence_frequencies(sites, *, method='composite', threshold=0.5, weights=None, also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Set low-confidence frequency rows to NaN without changing the grid.

Parameters:
pycsamt.emtools.plot_frequency_edit_decisions(before_sites, after_sites, *, method='composite', ci_hi=0.9, ci_lo=0.5, figsize=(10.0, 5.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)#

Plot dropped, masked, recovered, and kept frequency decisions.

Parameters:
pycsamt.emtools.plot_frequency_edit_summary(before_sites, after_sites, *, method='composite', ci_hi=0.9, ci_lo=0.5, figsize=(9.0, 4.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)#

Plot station-level before/after frequency-edit summary.

Parameters:
pycsamt.emtools.recover_low_confidence_frequencies(sites, *, method='composite', ci_hi=0.9, ci_lo=0.5, weights=None, interpolation='linear', reject='mask', also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Recover recoverable frequency rows using trusted neighboring rows.

Rows with confidence in [ci_lo, ci_hi) are treated as recoverable and are interpolated in log-frequency from rows with confidence >= ci_hi. Rows below ci_lo are considered rejected and are either masked, dropped, or kept depending on reject.

Parameters:
pycsamt.emtools.regrid_to(sites, target_freq, *, method='nearest', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.regrid_logspace(sites, *, fmin=None, fmax=None, band_hz=None, per_decade=6, n_per_decade=None, method='nearest', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.decimate_step(sites, *, step=2, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.smooth_mavg(sites, *, k=3, window=None, on='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.align_grid(sites, *, mode='union', ref_station=None, method='nearest', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.plot_coverage_quality_heatmap(sites, *, axis='period', figsize=(7.5, 4.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_apparent_depth_psection(sites, *, axis_y='period', agg='median', figsize=(7.5, 4.5), log_color=True, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_band_microstrips(sites, *, bands=None, n_bands=6, figsize=(9.0, 6.0), marker_size=16.0, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.bahr_skewness(Z)#
pycsamt.emtools.skew_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

DataFrame

pycsamt.emtools.mask_by_skew(sites, *, thresh=6.0, mode='abs_gt', also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.keep_longest_low_skew(sites, *, thresh=3.0, min_len=3, pad=0, also='both', fallback='keep_all', recursive=True, on_dup='replace', strict=False, verbose=0, inplace=False)#
Parameters:
pycsamt.emtools.close_skew_gaps(sites, *, thresh=3.0, max_gap=1, also='both', recursive=True, on_dup='replace', strict=False, verbose=0, inplace=False)#
Parameters:
pycsamt.emtools.select_low_skew_band(sites, *, thresh=3.0, frac=0.6, min_len=3, pad=0, also='both', recursive=True, on_dup='replace', strict=False, verbose=0, inplace=False)#
Parameters:
pycsamt.emtools.plot_skew_traffic_psection(sites, *, t1=3.0, t2=6.0, figsize=(9.0, 4.8), axis_y='logperiod', recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_skew_percentile_ribbon(sites, *, n_bins=30, q_lo=25.0, q_hi=75.0, extra=(10.0, 90.0), figsize=(8.6, 3.8), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_skew_vote_band(sites, *, thresh=3.0, n_bins=40, figsize=(8.6, 3.4), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_skewness(f_hz, Z, *, threshold=0.4, ax=None, title=None)#
pycsamt.emtools.analyze_anisotropy(sites, *, ratio_threshold=0.1, skew_threshold=0.2, recursive=True, on_dup='replace', strict=False, verbose=0)#

Per-frequency anisotropy metrics for a set of CSAMT sites.

Computes the two Cagniard apparent resistivities ρ_xy and ρ_yx (wang2017 eqs 17–18), their log-ratio Λ = log₁₀(ρ_xy/ρ_yx), the phase difference, and the Swift skew from the full Z tensor.

Parameters:
Returns:

Columns: station, freq_hz, period_s, rho_xy_ohmm, rho_yx_ohmm, phi_xy_deg, phi_yx_deg, ratio_log10, phase_diff_deg, swift_skew, strike_deg.

Return type:

pd.DataFrame

Notes

ratio_log10 = 0 and swift_skew = 0 indicate a perfectly isotropic 1-D earth. Non-zero diagonal Z elements (contributing to swift_skew > 0) suggest 3-D structure or electrical anisotropy (wang2017 §5.3).

pycsamt.emtools.anisotropy_table(sites, *, ratio_threshold=0.1, skew_threshold=0.2, recursive=True, on_dup='replace', strict=False, verbose=0)#

Per-station summary of anisotropy metrics.

Parameters:
  • sites (Sites | list)

  • ratio_threshold (float) – |log₁₀(Λ)| threshold for anisotropy flag (default 0.1).

  • skew_threshold (float) – Swift skew threshold for anisotropy flag (default 0.2).

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

Columns: station, n_freq, mean_ratio_log10, max_abs_ratio_log10, mean_phase_diff_deg, mean_swift_skew, median_strike_deg, anisotropy_flag.

anisotropy_flag is True when |mean_ratio_log10| > ratio_threshold OR mean_swift_skew > skew_threshold.

Return type:

pd.DataFrame

pycsamt.emtools.plot_anisotropy(sites, *, metric='ratio_log10', ratio_threshold=0.1, skew_threshold=0.2, cmap='RdBu_r', figsize=(10, 5), period_axis=True, log_y=True, contour_zero=True, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot anisotropy metric pseudo-section (station × frequency).

Parameters:
  • sites (Sites | list)

  • metric (str) – Column from analyze_anisotropy() to map to colour: "ratio_log10" (default), "swift_skew", "phase_diff_deg", or "strike_deg".

  • ratio_threshold (float)

  • skew_threshold (float)

  • cmap (str) – Colormap (default "RdBu_r" — diverging, centred at 0).

  • period_axis (bool) – Show period on y-axis (default) rather than frequency.

  • log_y (bool) – Logarithmic y-axis.

  • contour_zero (bool) – Draw a white contour at value = 0 (relevant for ratio_log10).

  • ax (matplotlib.axes.Axes or None)

  • figsize (tuple)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

ax

Return type:

matplotlib.axes.Axes

pycsamt.emtools.phase_features_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#
Parameters:
Return type:

Any

pycsamt.emtools.classify_dimensionality(sites, *, skew_th=3.0, ellipt_th=0.2, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#
Parameters:
Return type:

Any

pycsamt.emtools.pre2d_inversion_assessment(sites, *, band=None, skew_th=3.0, ellipt_th=0.2, rotation_applied=False, rotation_method='consensus', groom_bailey_attempted=False, groom_bailey_applied=False, groom_bailey_reason=None, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Summarise dimensionality and strike checks before 2-D inversion.

The table is designed for audit trails and manuscript responses. It combines phase-tensor skew/ellipticity dimensionality labels, impedance sweep strike, phase-tensor strike, consensus strike, and frequency-dependent strike variability. It also records whether data were rotated to strike and whether Groom-Bailey decomposition was attempted/applied.

Parameters:
Return type:

Any

pycsamt.emtools.mask_by_dimensionality(sites, *, keep=(0, 1), inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.project_to_2d(sites, *, strike=None, method='swift', antisym=True, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.learn_dim_dictionary(sites, *, n_atoms=6, lam=0.05, n_iter=40, code_iter=50, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

dict[str, Any]

pycsamt.emtools.encode_dimensionality(sites, model, *, lam=0.05, code_iter=50, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#
Parameters:
Return type:

Any

pycsamt.emtools.mask_by_dictionary(sites, model, *, keep=(0, 1), lam=0.05, code_iter=50, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.plot_atom_psection(sites, model, *, energy='l2', figsize=(9.0, 4.8), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_dim_confidence_grid(sites, *, skew_th=3.0, ellipt_th=0.2, figsize=(8.8, 4.2), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_dim_occupancy_area(sites, *, skew_th=3.0, ellipt_th=0.2, n_bands=24, figsize=(8.8, 3.6), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_dim_map(sites, *, period=10.0, skew_th=3.0, ellipt_th=0.2, figsize=(8.0, 6.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

class pycsamt.emtools.GroomBaileyResult(sites, table, applied, method)#

Bases: object

Container returned by groom_bailey_decomposition().

Parameters:
sites: Any#
table: DataFrame#
applied: bool#
method: str#
property n_station: int#

Number of stations with fitted distortion parameters.

summary()#

Return a compact text summary.

Return type:

str

pycsamt.emtools.apply_groom_bailey(sites, table=None, *, band=None, rotate_deg=None, min_freq=4, max_iter=30, tol=1e-06, robust=True, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Remove fitted Groom-Bailey galvanic distortion from impedance tensors.

Parameters:
Return type:

Any

pycsamt.emtools.groom_bailey_decomposition(sites, *, apply=False, band=None, rotate_deg=None, min_freq=4, max_iter=30, tol=1e-06, robust=True, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Estimate, and optionally apply, Groom-Bailey distortion correction.

Parameters:
Return type:

GroomBaileyResult

pycsamt.emtools.groom_bailey_table(sites, *, band=None, rotate_deg=None, min_freq=4, max_iter=30, tol=1e-06, robust=True, recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#

Estimate Groom-Bailey-style galvanic distortion parameters.

The fitted model is

\[Z_\mathrm{obs}(f) \approx D\,Z_{2D}(f),\]

where D is a real, frequency-independent 2x2 distortion matrix and Z_2D is anti-diagonal at each frequency. The fitted matrix is decomposed into gain, twist, shear, and anisotropy-style parameters.

Parameters:
Return type:

Any

pycsamt.emtools.estimate_strike_sweep(sites, *, angles=array([-90., -89., -88., -87., -86., -85., -84., -83., -82., -81., -80., -79., -78., -77., -76., -75., -74., -73., -72., -71., -70., -69., -68., -67., -66., -65., -64., -63., -62., -61., -60., -59., -58., -57., -56., -55., -54., -53., -52., -51., -50., -49., -48., -47., -46., -45., -44., -43., -42., -41., -40., -39., -38., -37., -36., -35., -34., -33., -32., -31., -30., -29., -28., -27., -26., -25., -24., -23., -22., -21., -20., -19., -18., -17., -16., -15., -14., -13., -12., -11., -10., -9., -8., -7., -6., -5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., 90.]), metric='diag_ratio', band=None, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

DataFrame

pycsamt.emtools.estimate_strike_phase_tensor(sites, *, band=None, robust=True, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

DataFrame

pycsamt.emtools.estimate_strike_consensus(sites, *, band=None, w_sweep=0.5, w_pt=0.5, angles=array([-90., -89., -88., -87., -86., -85., -84., -83., -82., -81., -80., -79., -78., -77., -76., -75., -74., -73., -72., -71., -70., -69., -68., -67., -66., -65., -64., -63., -62., -61., -60., -59., -58., -57., -56., -55., -54., -53., -52., -51., -50., -49., -48., -47., -46., -45., -44., -43., -42., -41., -40., -39., -38., -37., -36., -35., -34., -33., -32., -31., -30., -29., -28., -27., -26., -25., -24., -23., -22., -21., -20., -19., -18., -17., -16., -15., -14., -13., -12., -11., -10., -9., -8., -7., -6., -5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., 90.]), metric='diag_ratio', recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

DataFrame

pycsamt.emtools.rotate_to_strike(sites, *, method='consensus', band=None, angles=array([-90., -89., -88., -87., -86., -85., -84., -83., -82., -81., -80., -79., -78., -77., -76., -75., -74., -73., -72., -71., -70., -69., -68., -67., -66., -65., -64., -63., -62., -61., -60., -59., -58., -57., -56., -55., -54., -53., -52., -51., -50., -49., -48., -47., -46., -45., -44., -43., -42., -41., -40., -39., -38., -37., -36., -35., -34., -33., -32., -31., -30., -29., -28., -27., -26., -25., -24., -23., -22., -21., -20., -19., -18., -17., -16., -15., -14., -13., -12., -11., -10., -9., -8., -7., -6., -5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., 90.]), metric='diag_ratio', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.strike_curve_sweep(sites, *, angles=array([-90., -89., -88., -87., -86., -85., -84., -83., -82., -81., -80., -79., -78., -77., -76., -75., -74., -73., -72., -71., -70., -69., -68., -67., -66., -65., -64., -63., -62., -61., -60., -59., -58., -57., -56., -55., -54., -53., -52., -51., -50., -49., -48., -47., -46., -45., -44., -43., -42., -41., -40., -39., -38., -37., -36., -35., -34., -33., -32., -31., -30., -29., -28., -27., -26., -25., -24., -23., -22., -21., -20., -19., -18., -17., -16., -15., -14., -13., -12., -11., -10., -9., -8., -7., -6., -5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., 90.]), metric='diag_ratio', smooth=5, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

DataFrame

pycsamt.emtools.plot_strike_rose(sites, *, style='pycsamt', groups=None, group_key=None, band=None, freq_bands=None, band_labels=None, band_colors=None, method='consensus', bins=36, weight='inv_iqr', bar_style=<object object>, bar_color=<object object>, bar_alpha=<object object>, bar_edgecolor=<object object>, bar_edgelw=<object object>, cmap=<object object>, outer_ring_lw=<object object>, outer_ring_color=<object object>, n_rings=<object object>, ring_color=<object object>, ring_ls=<object object>, ring_lw=<object object>, ring_labels=<object object>, ring_label_angle=<object object>, ring_label_fontsize=<object object>, ring_label_color=<object object>, ring_label_fmt=<object object>, spoke_every=<object object>, spoke_color=<object object>, spoke_ls=<object object>, spoke_lw=<object object>, compass_labels=<object object>, compass_fontsize=<object object>, compass_color=<object object>, compass_fontweight=<object object>, show_mean=<object object>, mean_color=<object object>, mean_lw=<object object>, mean_ls=<object object>, show_secondary=<object object>, secondary_color=<object object>, secondary_ls=<object object>, secondary_lw=<object object>, show_annotation=<object object>, annotation_pos=<object object>, annotation_fontsize=<object object>, annotation_bg=<object object>, annotation_ec=<object object>, show_n_stations=<object object>, subplot_size=3.2, n_cols=None, axes=None, figsize=None, suptitle='', suptitle_fontsize=10.0, tight_layout=True, recursive=True, on_dup='replace', strict=False, verbose=0)#

Publication-quality rose diagram of geoelectric strike direction.

Each subplot shows the angular distribution of the estimated MT geoelectric strike for one station group (profile line). Bars are drawn with axial symmetry — 0–180° mirrored to 180–360° — to reflect the inherent 180° ambiguity of geoelectric strike.

Parameters:
  • sites (any) – EDI paths, EDI objects, or SitesCollection accepted by ensure_sites().

  • groups (dict[str, list[str]], optional) – Explicit map {group_label: [station_name, ...], ...}. If None, stations are auto-grouped by profile prefix (e.g. "E1S01" → group "E1").

  • group_key (str, optional) – EDI attribute to read as group label when groups is None.

  • band ((float, float), optional) – Period band (lo_s, hi_s) in seconds for strike estimation. None uses all available frequencies.

  • freq_bands (list of (float, float), optional) – Period sub-bands used for bar_style="bands". Each tuple is (lo_s, hi_s); one histogram per band is stacked.

  • band_labels (list[str], optional) – Legend labels matching freq_bands (one per band).

  • band_colors (list, optional) – Bar colours for each freq_bands entry (any matplotlib colour spec). Defaults to tab10 palette.

  • method ({"consensus", "sweep", "pt"}) – Strike estimation method — see estimate_strike_consensus().

  • bins (int) – Number of histogram bins over 0–180°, mirrored to 360°.

  • weight ({"inv_iqr", "uniform"}) – Weighting scheme. "inv_iqr" down-weights unstable sites.

  • bar_style ({"gradient", "bands", "solid"}) – "gradient" — bars coloured by height via cmap (paper style); "bands" — stacked per-band bars with distinct colours; "solid" — uniform bar_color.

  • bar_color (str) – Bar fill colour for bar_style="solid".

  • bar_edgecolor (str) – Bar edge colour ("none" → no edge).

  • bar_edgelw (float) – Bar edge line-width.

  • cmap (str) – Colormap name for bar_style="gradient".

  • outer_ring_lw (float) – Line-width of the bold outer circle.

  • outer_ring_color (str) – Colour of the outer circle.

  • n_rings (int) – Number of concentric reference rings inside the plot.

  • ring_color (str) – Colour of grid rings and radial spokes.

  • ring_ls (str) – Line-style of grid rings and radial spokes.

  • spoke_every (float) – Angular spacing (degrees) of radial spokes / tick marks.

  • compass_labels ({"NESW", "degrees", "none"}) – Labels around the polar perimeter. "NESW" shows cardinal directions; "degrees" shows degree values; "none" suppresses all labels.

  • compass_fontsize (float) – Font size for compass / degree labels.

  • compass_color (str) – Colour of compass labels.

  • mean_color (str) – Colour of the mean-direction line.

  • mean_lw (float) – Line-width of the mean-direction line.

  • mean_ls (str) – Line-style of the mean-direction line.

  • show_secondary (bool) – Draw the 180°-conjugate mean line (axial symmetry axis).

  • secondary_color (str, optional) – Colour for the conjugate line; defaults to mean_color.

  • secondary_ls (str) – Line-style for the conjugate line.

  • secondary_lw (float, optional) – Line-width for the conjugate line; defaults to mean_lw.

  • annotation_pos ((float, float)) – Axes-fraction (x, y) of the strike angle annotation box.

  • annotation_fontsize (float) – Font size of the annotation text.

  • annotation_bg (str) – Background colour of the annotation box.

  • annotation_ec (str) – Edge colour of the annotation box.

  • show_n_stations (bool) – Append the station count n = N to the annotation text.

  • subplot_size (float) – Side length (inches) of each polar subplot.

  • n_cols (int, optional) – Number of subplot columns. Defaults to len(groups).

  • figsize ((float, float), optional) – Override the auto-computed figure size.

  • suptitle (str) – Figure-level super-title.

  • suptitle_fontsize (float) – Font size of the super-title.

  • tight_layout (bool) – Call fig.tight_layout() before returning.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Duplicate-handling strategy for ensure_sites().

  • strict (bool) – Strict mode for ensure_sites().

  • verbose (int) – Verbosity level.

  • style (str | RoseStyle | None)

Returns:

Figure with one polar axes per station group.

Return type:

matplotlib.figure.Figure

Examples

Basic usage — one rose per profile line, gradient style:

>>> from pycsamt.emtools import plot_strike_rose
>>> fig = plot_strike_rose("path/to/edis/")

Frequency-band decomposition (short / long period stacked):

>>> fig = plot_strike_rose(
...     sites,
...     bar_style="bands",
...     freq_bands=[(0.001, 0.1), (0.1, 100.0)],
...     band_labels=["Short period", "Long period"],
... )
pycsamt.emtools.plot_strike_rose_by_line(sites, *, groups=None, group_key=None, band=None, method='consensus', bins=36, weight='inv_iqr', axes=None, figsize=(8.6, 4.6), recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.plot_strike_ribbon(sites, *, method='sweep', win=5, show_colorbar=True, cbar_ticks=None, figsize=(9.0, 4.2), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_strike_profile(sites, *, method='consensus', band=None, sort_by=None, figsize=(8.6, 3.8), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_strike_mapsticks(sites, *, method='consensus', band=None, len_deg=0.02, alpha_scale=0.9, figsize=(7.8, 6.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_strike_analysis(sites, *, style='pycsamt', band=None, bins=36, method='sweep', cmap_z=None, cmap_pt=None, cmap_tipper=None, title_fc_z='#ffe0e0', title_fc_pt='#ffffd0', title_fc_tipper='#d5e8ff', title_ec='0.35', axes=None, figsize=None, subplot_size=3.8, suptitle='', tight_layout=True, recursive=True, on_dup='replace', strict=False, verbose=0)#

Rose diagram: Strike (Z), PT Azimuth, and, if present, Tipper Strike.

Produces a publication-quality figure with one polar rose per analysis type, analogous to the MTPy StrikeAnalysis plot. All panels share the same RoseStyle so colours remain visually consistent. Each panel carries a coloured title box to distinguish the quantities at a glance.

The Tipper Strike panel is only drawn when sites actually carries a vertical-field (tipper) channel on at least one station – surveys with no tipper at all (e.g. most AMT) get a two-panel Strike/PT figure instead of a third, permanently empty “no data” rose. This detection is independent of band: a tipper-bearing survey whose selected band happens to contain no tipper rows still gets a three-panel figure, with “no data” drawn in that one panel, since the channel genuinely exists elsewhere in the survey. When axes is supplied explicitly, all three panels are always used, since the caller has already committed to that layout.

Parameters:
  • sites (any) – EDI paths, EDI objects, or SitesCollection accepted by ensure_sites().

  • style (str, RoseStyle, or None) – Named style preset or RoseStyle instance. Default "pycsamt" uses the YlOrRd-gradient, crimson-mean-line paper style.

  • band ((lo_s, hi_s) or None) – Period window in seconds applied to all three panels. None uses all available periods / frequencies.

  • bins (int) – Number of histogram bins over 0–180°, mirrored to 0–360°. Default 36 → 5° bins.

  • method ({"sweep", "pt", "consensus"}) –

    Strike estimation algorithm for the Strike (Z) panel.

    "sweep" — impedance-tensor rotation sweep (calls estimate_strike_sweep()); "pt" — phase-tensor θ median per station (calls estimate_strike_phase_tensor()); "consensus" — weighted blend of sweep and PT (calls estimate_strike_consensus()).

  • cmap_z (str or None) – Colormap name for each panel when bar_style="gradient". None falls back to the colormap in style.

  • cmap_pt (str or None) – Colormap name for each panel when bar_style="gradient". None falls back to the colormap in style.

  • cmap_tipper (str or None) – Colormap name for each panel when bar_style="gradient". None falls back to the colormap in style.

  • title_fc_z (str) – Facecolour of the title annotation box for each panel.

  • title_fc_pt (str) – Facecolour of the title annotation box for each panel.

  • title_fc_tipper (str) – Facecolour of the title annotation box for each panel.

  • title_ec (str) – Edge colour shared by all title boxes.

  • figsize ((float, float) or None) – Figure size. Auto-derived from subplot_size when None.

  • subplot_size (float) – Side length (inches) of each polar panel when figsize is auto.

  • suptitle (str) – Figure-level super-title.

  • tight_layout (bool) – Call fig.tight_layout() before returning.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Passed to ensure_sites().

  • strict (bool) – Passed to ensure_sites().

  • verbose (int) – Passed to ensure_sites().

Returns:

Figure with two polar axes (Strike (Z), PT Azimuth) when sites has no tipper channel and axes was not supplied, otherwise three (adding Tipper Strike).

Return type:

matplotlib.figure.Figure

Examples

Default style, all periods:

>>> from pycsamt.emtools import plot_strike_analysis
>>> fig = plot_strike_analysis("path/to/edis/")
>>> fig.savefig("strike_analysis.png", dpi=150, bbox_inches="tight")

Short-period band, publication style:

>>> fig = plot_strike_analysis(
...     sites,
...     band=(0.01, 1.0),
...     style="publication",
...     suptitle="WILLY AMT — short-period band",
... )
pycsamt.emtools.rotate_z_to_strike(sites, *, method='swift', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.rotate(sites, angle, *, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.rotate_by_map(sites, angle_by_station, *, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.antisymmetrize(sites, *, how='rms', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.invert(sites, *, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.orient_from_sensors(sites, ex, ey, bx, by, *, degrees=True, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.sigma_clip_z(sites, *, sigma=3.0, inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.balance_offdiag(sites, *, mode='avgabs', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Sites

pycsamt.emtools.build_phase_tensor_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

DataFrame

pycsamt.emtools.plot_phase_tensor_psection(sites, *, stations=None, period_range=None, axis_y='logperiod', period_up=False, frame_pct=(1.0, 99.0), scale=<object object>, normalise_by=<object object>, s1_ref=None, min_aspect=<object object>, c_by=<object object>, cmap=<object object>, clim=None, clim_pct=<object object>, symmetric_clim=<object object>, color_mode='continuous', segment_bounds=None, segment_colors=('#2166ac', '#f7f7f7', '#b2182b'), edgecolor=<object object>, linewidth=<object object>, alpha=<object object>, ellipse_kws=None, cb_kws=None, colorbar=True, skew_threshold=<object object>, mark_3d=<object object>, ref_ellipse=<object object>, legend_fontsize=8.0, title='', xlabel='', ylabel='', tick_label_rotation=45.0, figsize=(10.0, 5.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Phase-tensor ellipse pseudo-section (Caldwell et al. 2004 style).

Each cell (station × period) is drawn as an ellipse whose shape, orientation, and fill colour encode phase-tensor invariants:

  • major axis ∝ φ_max (s1 eigenvalue — mean phase level)

  • minor axis ∝ φ_min (s2 eigenvalue — minimum phase)

  • aspect ratio = φ_min / φ_max (1 = isotropic / 1-D)

  • rotation = θ (geoelectric strike, CCW from E)

  • fill colour controlled by c_by (default: skew angle β)

Parameters:
  • sites (any) – EDI paths, glob pattern, Sites, or any input accepted by ensure_sites().

  • stations (list of str or None) – Restrict to a subset of station names.

  • period_range ((T_min, T_max) in seconds or None) – Restrict the period range plotted.

  • axis_y ("logperiod" | "logfreq") – Y-axis quantity. "logperiod" is the MT convention.

  • period_up (bool, default False) – When False (default; standard pseudo-section convention, as used elsewhere in pycsamt.emtools, e.g. plot_dimensionality_psection()), short period (high frequency, shallow sensitivity) is at the top of the figure and long period (low frequency, deep sensitivity) is at the bottom – matching a geological cross-section with depth increasing downward. True flips the y-axis.

  • frame_pct ((lo, hi) or None, default (1, 99)) – Robust percentiles of finite log-period/log-frequency values used for the visible y-frame. This prevents a few isolated frequency rows from creating large empty bands. Pass None to show the absolute range, or use period_range for explicit scientific limits.

  • scale (float, default 0.85) – Fraction of each cell occupied by the reference ellipse (the one with s1 = s1_ref). Values above 1 cause overlap.

  • normalise_by ("shape" | "cell" | "unity" | "abs") –

    Sizing strategy:

    "shape"

    MTpy-style display: every major axis has the same physical size and the minor/major ratio is s2 / s1. This emphasizes tensor shape without allowing absolute phase magnitude or unequal plot axes to turn ellipses into misleading needles.

    "cell"

    Sizes are normalised so the 90th-percentile s1 fills scale of its cell. Preserves relative size information.

    "unity"

    s1_ref = 1.0 (tan 45° = 1-D half-space reference). A 45° phase tensor is drawn as a circle filling scale of its cell.

    "abs"

    Raw: width = scale × s1, height = scale × s2 in data units (legacy behaviour).

  • s1_ref (float or None) – Override the reference s1 for "cell" and "unity" modes.

  • min_aspect (float, default 0.18) – Minimum height/width ratio enforced on every ellipse so that near-degenerate (φ_min ≈ 0) cells stay visible as ovals instead of collapsing into an invisible hairline. Set to 0 to plot raw ellipticity.

  • c_by (str, default "skew") – Column name or derived quantity to map to fill colour. Supported values: "skew", "beta", "alpha", "theta", "ellipt", "s1", "s2", "|skew|", "|beta|", "|theta|", "phi_mean", "phi_max", "phi_min".

  • cmap (str, default "RdBu_r") – Matplotlib colormap name.

  • clim ((vmin, vmax) or None) – Explicit colour limits. When None, derived from clim_pct.

  • clim_pct ((lo, hi), default (5.0, 95.0)) – Percentile limits used when clim is None.

  • color_mode ("continuous" | "segmented") – Continuous colour interpolation or three discrete classes. Segmented mode follows the usual MTpy-style skew presentation.

  • segment_bounds ((lower, upper) or None) – Class boundaries for segmented colour. By default these are (-skew_threshold, skew_threshold).

  • segment_colors (sequence of three colours) – Colours below, inside, and above segment_bounds, respectively.

  • symmetric_clim (bool, default True) – Enforce vmin = −vmax for skew-like quantities.

  • edgecolor (str, default "k") – Ellipse border colour. Set to "none" to suppress borders.

  • linewidth (float, default 0.2) – Ellipse border width (pts). 3-D cells receive 3 × this width when mark_3d is True.

  • alpha (float, default 0.92) – Ellipse fill opacity.

  • ellipse_kws (dict or None) – Additional ellipse styling, such as edgecolor and linewidth. Geometry and transform keys remain controlled by this function.

  • cb_kws (dict or None) – Colourbar customization. size, pad, labelsize and ticksize control layout; other entries go to Figure.colorbar.

  • colorbar (bool, default True) – Draw the per-axes colourbar. Set False when composing several calls onto a shared grid (e.g. one panel per line) and drawing a single shared colourbar separately, using the same cmap/clim passed to every call so the mapping is identical across panels.

  • skew_threshold (float or None, default 3.0) – |β| threshold (degrees) separating 1-D/2-D from 3-D structure. It does not set the color limits; clim_pct remains data-driven. The threshold only highlights 3-D cells with a thicker border when mark_3d is True.

  • mark_3d (bool, default True) – Draw a thicker border on ellipses where |β| > skew_threshold.

  • ref_ellipse (bool, default True) – Draw a labelled reference circle (φ_max = φ_min = s1_ref, β = 0°) inside the axes, without expanding the data frame.

  • legend_fontsize (float, default 8.0) – Font size for the reference-circle label and the 1-D/2-D vs 3-D annotation shown when ref_ellipse / skew_threshold are active.

  • title (str) – Axes title and axis labels. Sensible defaults are used when empty.

  • xlabel (str) – Axes title and axis labels. Sensible defaults are used when empty.

  • ylabel (str) – Axes title and axis labels. Sensible defaults are used when empty.

  • tick_label_rotation (float, default 45.0) – Station-name tick rotation in degrees.

  • figsize ((width, height), default (10.0, 5.5)) – Figure size (ignored when ax is provided).

  • recursive (see ensure_sites().)

  • on_dup (see ensure_sites().)

  • strict (see ensure_sites().)

  • verbose (see ensure_sites().)

  • ax (Axes or None) – If provided, draw into this axes and return it; otherwise create a new figure.

Returns:

ax

Return type:

Axes

See also

build_phase_tensor_table

Underlying data computation.

plot_phase_tensor_summary

3-panel figure combining ellipses, dimensionality, and skew distribution.

plot_phase_tensor_map

Geographic map view.

pycsamt.emtools.plot_phase_tensor_skewmap(sites, *, axis_y='logperiod', agg='median', figsize=(9.0, 4.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_theta_vs_period(sites, *, figsize=(8.0, 4.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_strike_director_field(sites, *, color_by='skew', length_by='ellipt', streamlines=True, skew_max=6.0, cmap=None, period_subsample=None, bar_scale=26.0, show_legend=True, title=None, figsize=(12.0, 5.2), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Geoelectric-strike director field over station and period.

A supplement to plot_theta_vs_period(). Because the phase-tensor strike theta is an axial angle (defined mod 180 deg), the correct glyph is not a point on a linear axis but a head-less bar pointing along the strike. This draws one director per (station, period) cell on a station x log-period grid, encoding three more channels at once:

  • orientation – the strike theta;

  • lengthlength_by (default ellipticity), the 2-D strength: near-1-D cells get a short bar because there strike is ill-defined;

  • colourcolor_by (default |skew|), the departure from 2-D: green = low-skew / reliable, red = high-skew / 3-D or galvanic distortion where the strike should not be trusted.

An optional smoothed streamline overlay (streamlines=True) integrates the director field into a strike “flow”, so lateral and vertical coherence read at a glance.

2.14. Interpretation#

  • long, aligned, green bars flowing in a laminar bundle -> robust, depth-consistent 2-D strike; read the azimuth with confidence;

  • bars rotating smoothly with depth -> strike varies with depth (dipping structure or layered anisotropy);

  • short and/or red, swirling bars -> 1-D, 3-D, or noise: do not over-interpret the direction there.

param sites:

Anything accepted by build_phase_tensor_table().

type sites:

path, EDI object, APISurvey, Sites, or iterable of sites

param color_by:

Table column mapped to bar colour (its absolute value is used).

type color_by:

{‘skew’, ‘ellipt’, ‘s1’, ‘s2’, …}, default ‘skew’

param length_by:

Table column mapped to bar length (absolute value, 95th-percentile normalised). None draws uniform-length bars.

type length_by:

str or None, default ‘ellipt’

param streamlines:

Overlay smoothed strike streamlines (needs SciPy).

type streamlines:

bool, default True

param skew_max:

Upper clip of the |skew| colour scale, in degrees (only used when color_by='skew'). Skew above a few degrees already flags 3-D behaviour.

type skew_max:

float, default 6.0

param cmap:

Override the colour map (default 'RdYlGn_r' for skew, else 'viridis').

type cmap:

str, optional

param period_subsample:

Keep at most this many periods (evenly along the log axis) to thin a very dense grid.

type period_subsample:

int, optional

param bar_scale:

Matplotlib quiver scale – larger makes shorter bars.

type bar_scale:

float, default 26.0

param show_legend:

Draw the director / streamline legend.

type show_legend:

bool, default True

param title:

Axes title.

type title:

str, optional

param figsize:

Standard emtools plotting arguments.

param recursive:

Standard emtools plotting arguments.

param on_dup:

Standard emtools plotting arguments.

param strict:

Standard emtools plotting arguments.

param verbose:

Standard emtools plotting arguments.

param ax:

Standard emtools plotting arguments.

rtype:

matplotlib.axes.Axes

See also

plot_theta_vs_period

the linear scatter this supplements.

plot_phase_tensor_psection

per-cell ellipses (magnitudes as well).

Parameters:
Return type:

Axes

pycsamt.emtools.plot_ellipticity_psection(sites, *, figsize=(8.5, 4.0), agg='median', recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_dimensionality_psection(sites, *, skew_th=3.0, ellipt_th=0.2, segmented_colors=True, cmap='viridis', colorbar=True, figsize=(8.5, 4.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_phase_tensor_rose(sites, *, style='pycsamt', band=None, freq_bands=None, band_labels=None, band_colors=None, bins=36, bar_style=<object object>, bar_color=<object object>, bar_alpha=<object object>, bar_edgecolor=<object object>, bar_edgelw=<object object>, cmap=<object object>, outer_ring_lw=<object object>, outer_ring_color=<object object>, n_rings=<object object>, ring_color=<object object>, ring_ls=<object object>, ring_lw=<object object>, ring_labels=<object object>, ring_label_angle=<object object>, ring_label_fontsize=<object object>, ring_label_color=<object object>, ring_label_fmt=<object object>, spoke_every=<object object>, spoke_color=<object object>, spoke_ls=<object object>, spoke_lw=<object object>, compass_labels=<object object>, compass_fontsize=<object object>, compass_color=<object object>, compass_fontweight=<object object>, show_mean=<object object>, mean_color=<object object>, mean_lw=<object object>, mean_ls=<object object>, show_secondary=<object object>, secondary_ls=<object object>, secondary_lw=<object object>, secondary_color=<object object>, show_annotation=<object object>, annotation_pos=<object object>, annotation_fontsize=<object object>, annotation_bg=<object object>, annotation_ec=<object object>, show_n=<object object>, figsize=(5.5, 5.5), title='', title_fontsize=10.0, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Publication-quality phase-tensor θ rose diagram.

Bars are drawn with axial symmetry — 0–180° mirrored to 180–360° — reflecting the inherent 180° ambiguity of the phase-tensor principal axis direction.

Parameters:
  • sites (any) – EDI paths, objects, or collection accepted by ensure_sites().

  • band ((lo_s, hi_s) or None) – Period window in seconds. None uses all available periods.

  • freq_bands (list of (lo_s, hi_s), optional) – Sub-bands for bar_style="bands" — one stacked bar colour per band.

  • band_labels (list[str], optional) – Legend labels for each entry in freq_bands.

  • band_colors (list, optional) – Colours for each freq_bands entry. Defaults to tab10.

  • bins (int) – Number of bins over 0–180°, mirrored to 360°. Default 36 gives 5° bins.

  • bar_style ({"gradient", "solid", "bands"}) – "gradient" colours bars by height via cmap; "solid" uses bar_color uniformly; "bands" stacks one colour per period sub-band.

  • bar_color (str) – Bar fill colour for bar_style="solid".

  • bar_edgecolor (str) – Bar edge colour ("none" → no edge).

  • bar_edgelw (float) – Bar edge line-width.

  • bar_alpha (float) – Bar opacity (0–1).

  • cmap (str) – Colormap for bar_style="gradient".

  • outer_ring_lw (float) – Line-width of the bold outer bounding circle.

  • outer_ring_color (str) – Colour of the outer circle.

  • n_rings (int) – Number of concentric reference rings.

  • ring_color (str) – Colour of concentric rings and spokes.

  • ring_ls (str) – Line-style of concentric rings.

  • ring_lw (float) – Line-width of concentric rings.

  • ring_labels (list[float], optional) – Explicit count values to annotate on the rings (e.g. [25, 50, 75, 100]). None → evenly spaced from rmax / n_rings to rmax.

  • ring_label_angle (float) – Clockwise angle from North (degrees) at which ring count labels are placed. Default 22.5.

  • ring_label_fontsize (float) – Font size for ring count labels.

  • ring_label_color (str) – Colour for ring count labels.

  • ring_label_fmt (str) – Format string for ring labels, e.g. "{:.0f}".

  • spoke_every (float) – Angular spacing (degrees) between radial spokes.

  • spoke_color (str) – Colour of radial spokes.

  • spoke_ls (str) – Line-style of radial spokes.

  • spoke_lw (float) – Line-width of radial spokes.

  • compass_labels ({"NESW", "degrees", "none"}) – Perimeter labels. "NESW" shows cardinal directions; "degrees" shows degree values; "none" hides all.

  • compass_fontsize (float) – Font size for perimeter labels.

  • compass_color (str) – Colour for perimeter labels.

  • compass_fontweight (str) – Font weight for perimeter labels.

  • show_mean (bool) – Draw the axial mean direction as a line through the centre.

  • mean_color (str) – Colour of the mean-direction line.

  • mean_lw (float) – Line-width of the mean-direction line.

  • mean_ls (str) – Line-style of the mean-direction line.

  • show_secondary (bool) – Draw the 180°-conjugate mean line.

  • secondary_ls (str) – Line-style of the conjugate line.

  • secondary_lw (float, optional) – Line-width of the conjugate line; defaults to mean_lw.

  • secondary_color (str, optional) – Colour of the conjugate line; defaults to mean_color.

  • show_annotation (bool) – Show a text box with the mean θ and station/pair count.

  • annotation_pos ((float, float)) – Axes-fraction (x, y) for the annotation box.

  • annotation_fontsize (float) – Font size of the annotation text.

  • annotation_bg (str) – Background colour of the annotation box.

  • annotation_ec (str) – Edge colour of the annotation box.

  • show_n (bool) – Append n = N to the annotation text.

  • figsize ((float, float)) – Figure size in inches.

  • title (str) – Axes title (set via ax.set_title).

  • title_fontsize (float) – Font size for title.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Passed to ensure_sites().

  • strict (bool) – Passed to ensure_sites().

  • verbose (int) – Passed to ensure_sites().

  • ax (matplotlib.axes.Axes, optional) – Pre-existing polar axes to draw into. Created when None.

  • style (str | RoseStyle | None)

Returns:

The polar axes containing the rose diagram.

Return type:

matplotlib.axes.Axes

Examples

Default gradient style, all periods:

>>> from pycsamt.emtools import plot_phase_tensor_rose
>>> ax = plot_phase_tensor_rose("path/to/edis/", figsize=(6, 6))

Frequency-band decomposition (stacked):

>>> ax = plot_phase_tensor_rose(
...     sites,
...     bar_style="bands",
...     freq_bands=[(1e-4, 1e-2), (1e-2, 1e0)],
...     band_labels=["Short period", "Long period"],
... )

Custom ring count labels:

>>> ax = plot_phase_tensor_rose(
...     sites,
...     ring_labels=[25, 50, 75, 100],
...     ring_label_angle=15.0,
... )
pycsamt.emtools.plot_phase_tensor_map(sites, *, period=10.0, scale=<object object>, normalise_by=<object object>, s1_ref=<object object>, min_aspect=<object object>, c_by=<object object>, cmap=<object object>, clim=None, clim_pct=<object object>, symmetric_clim=<object object>, alpha=<object object>, edgecolor=<object object>, linewidth=<object object>, skew_threshold=<object object>, mark_3d=<object object>, lw_3d_factor=<object object>, ref_ellipse=<object object>, show_tipper=True, tipper_convention='parkinson', tipper_component='real', tipper_scale=None, tipper_color='k', tipper_lw=1.4, bg_grid=None, station_labels=True, station_marker='v', station_ms=5.0, station_color='k', label_fontsize=7.0, title='', colorbar_label=None, coords=None, figsize=(9.0, 7.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Publication-quality phase-tensor map at a single period.

Renders each station as a phase-tensor ellipse positioned at its geographic coordinates. The ellipse shape encodes the phase-tensor principal values (φ_max, φ_min) and orientation (θ); fill colour encodes an additional scalar (default: skewness β). Induction arrows can be overlaid when tipper data are available, and an optional background field (gravity, resistivity, …) may be drawn behind the ellipses.

Parameters:
  • sites (any) – EDI paths, objects, or collection accepted by ensure_sites().

  • period (float) – Target period (s). The nearest available period in the data is used for each station independently.

  • scale (float or _UNSET) – Maximum ellipse semi-axis in geographic units (degrees). _UNSET → auto-derived from the median inter-station spacing.

  • normalise_by ("cell" | "unity" | "abs") – Size normalisation strategy, see PhaseTensorEllipseStyle.

  • s1_ref (float or None) – Manual reference s1 used with normalise_by="cell"/"unity".

  • min_aspect (float or _UNSET) – Minimum height/width ratio enforced on every ellipse; see plot_phase_tensor_psection(). Default: PYCSAMT_STYLE.pt_ellipse.min_aspect.

  • c_by (str) – Scalar quantity to map to fill colour. Recognised values: "skew", "beta", "|skew|", "theta", "ellipt", "phi_mean", "phi_max", "phi_min", "s1", "s2", "alpha". Defaults to PYCSAMT_STYLE.pt_ellipse.c_by.

  • cmap (str or None) – Matplotlib colormap. Auto-selected from c_by when None (same logic as PhaseTensorEllipseStyle).

  • clim ((vmin, vmax) or None) – Explicit colour limits. None → derived from clim_pct.

  • clim_pct ((lo, hi)) – Percentile window for automatic colour limits.

  • symmetric_clim (bool) – Force vmin = −vmax (useful for diverging quantities).

  • alpha (float) – Ellipse fill opacity.

  • edgecolor (str) – Ellipse border colour.

  • linewidth (float) – Normal ellipse border width (pts).

  • skew_threshold (float or None) – |β| above which a cell is flagged as 3-D.

  • mark_3d (bool) – Draw thicker borders on 3-D flagged ellipses.

  • lw_3d_factor (float) – Line-width multiplier for 3-D flagged borders.

  • ref_ellipse (bool) – Draw a reference circle in the lower-left corner as a scale bar.

  • show_tipper (bool) – Overlay induction arrows when tipper data are found.

  • tipper_convention ("parkinson" | "wiese") – Parkinson: arrow toward anomaly (negated real T); Wiese: arrow along real T.

  • tipper_component ("real" | "imag" | "both") – Which tipper component to draw.

  • tipper_scale (float or None) – Arrow length = tipper_scale × scale. Auto-derived when None.

  • tipper_color (str) – Arrow colour (real component). Imaginary component uses mcolors.to_rgba(tipper_color, 0.55).

  • tipper_lw (float) – Arrow line-width.

  • bg_grid (dict or None) –

    Optional background field drawn behind ellipses:

    bg_grid = dict(
        lons   = 1D or 2D longitude array,
        lats   = 1D or 2D latitude array,
        values = 2D array (shape matches meshgrid of lons × lats),
        cmap   = "RdYlGn",     # colormap
        clim   = (vmin, vmax), # or None → auto
        alpha  = 0.55,         # opacity
        label  = "Gravity (gu)",
    )
    

  • station_labels (bool) – Annotate each station position with its name.

  • station_marker (str) – Marker style for station positions (default "v").

  • station_ms (float) – Marker size.

  • station_color (str) – Marker and label colour.

  • label_fontsize (float) – Font size for station labels.

  • title (str) – Axes title.

  • colorbar_label (str or None) – Override the automatic colorbar label derived from c_by.

  • coords (dict[str, (lat, lon)] or None) – Explicit station coordinates. When None the function reads ed.coords (a (lat, lon, elev) tuple) from each Site object.

  • figsize ((float, float)) – Figure size in inches.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Passed to ensure_sites().

  • strict (bool) – Passed to ensure_sites().

  • verbose (int) – Passed to ensure_sites().

  • ax (matplotlib.axes.Axes or None) – Pre-existing axes to draw into.

Return type:

matplotlib.axes.Axes

Examples

Default skew-coloured map:

>>> from pycsamt.emtools import plot_phase_tensor_map
>>> ax = plot_phase_tensor_map(sites, period=10.0)

Ellipticity coloured, with tipper arrows:

>>> ax = plot_phase_tensor_map(
...     sites,
...     period=10.0,
...     c_by="ellipt",
...     cmap="viridis",
...     show_tipper=True,
...     tipper_convention="parkinson",
... )

With a gravity background:

>>> ax = plot_phase_tensor_map(
...     sites,
...     period=10.0,
...     bg_grid=dict(
...         lons=g_lon,
...         lats=g_lat,
...         values=g_bouguer,
...         cmap="RdYlGn",
...         alpha=0.45,
...         label="Gravity (gu)",
...     ),
... )
pycsamt.emtools.phase_tensor_legend(*, size=1.0, ellipt=0.45, theta_deg=20.0, figsize=(2.8, 2.8), ax=None)#

Draw a labeled reference phase-tensor ellipse.

Explains the ellipse convention shared by every phase-tensor plot in this module – plot_phase_tensor_psection(), plot_phase_tensor_map(), plot_phase_tensor_strip(), and friends – by drawing one annotated example: a major axis \(\phi_{\max}\), a minor axis \(\phi_{\min}\), and an orientation angle theta measured counterclockwise from the horizontal (dashed reference line), matching the width, height, angle convention Matplotlib’s own Ellipse patch uses.

Parameters:
  • size (float, default 1.0) – Semi-major axis length (\(\phi_{\max}\)) of the example ellipse, in axis data units.

  • ellipt (float, default 0.45) – Example ellipticity used only to make the minor axis visibly shorter than the major axis; \(\phi_{\min} = \text{size} \times (1-\text{ellipt})\). Purely illustrative – it does not come from real data.

  • theta_deg (float, default 20.0) – Example orientation angle, in degrees, used to draw the rotated ellipse and the theta arc.

  • figsize ((float, float), default (2.8, 2.8)) – Figure size when ax is not supplied.

  • ax (matplotlib.axes.Axes or None) – Axes to draw on; created if None.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_dimensionality_grid(sites, *, skew_th=3.0, ellipt_th=0.2, figsize=(8.5, 4.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_theta_stability_stripe(sites, *, win=5, figsize=(9.0, 4.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_skew_ellipt_density(sites, *, band=None, gridsize=40, figsize=(6.5, 5.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_theta_rose_grid(sites, *, n_bands=6, axes=None, figsize=(13.0, 3.8), bins=24, style='pycsamt', panel_title_fontsize=7.5, recursive=True, on_dup='replace', strict=False, verbose=0)#

Phase-tensor θ rose grid — one pycsamt-styled rose per frequency decade.

Draws n_bands polar rose diagrams side by side, each covering one equal-log-width period band. Each rose applies axial symmetry (0–180° mirrored to 180–360°) and is rendered using the active RoseStyle.

Parameters:
  • sites (any) – Input accepted by ensure_sites().

  • n_bands (int, default 6) – Number of equal-log-width period bands.

  • figsize ((width, height), default (13.0, 3.8)) – Figure size in inches. Constrained layout is used internally so an external fig.suptitle fits without producing blank space.

  • bins (int, default 24) – Number of bins over 0–180° (mirrored to 360°).

  • style (str, RoseStyle, or None) – Rose visual style. Strings resolved via resolve_rose_style().

  • panel_title_fontsize (float, default 7.5) – Font size for the period-band label above each panel.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Passed to ensure_sites().

  • strict (bool) – Passed to ensure_sites().

  • verbose (int) – Passed to ensure_sites().

Return type:

matplotlib.figure.Figure

pycsamt.emtools.plot_phase_tensor_strip(sites, *, station=None, period_range=None, scale=<object object>, normalise_by=<object object>, s1_ref=None, min_aspect=<object object>, cells_per_decade=7.0, c_by=<object object>, cmap=<object object>, clim=None, clim_pct=<object object>, symmetric_clim=<object object>, edgecolor=<object object>, linewidth=<object object>, alpha=<object object>, skew_threshold=<object object>, mark_3d=<object object>, phase_ticks=(0.0, 45.0, 90.0), ylabel='', station_label=True, station_label_fontsize=8.0, title='', xlabel='', figsize=(6.0, 1.4), show_colorbar=True, colorbar_label=None, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Single-station phase-tensor ellipse strip vs period.

Draws one horizontal row of phase-tensor ellipses for a single station, one ellipse per period, in the classic “ellipse timeseries” style used e.g. by WinGLink / mtpy single-station PT plots. The y-axis carries no per-point data — it is a schematic 0°–90° phase scale (phase_ticks) used only to calibrate ellipse size by eye; the physical information (φ_max, φ_min, θ, fill colour) is identical to plot_phase_tensor_psection().

Combine several stations/profiles into the small-multiples layout (columns = profiles, rows = stations, one shared colorbar) with plot_phase_tensor_strip_grid().

Parameters:
  • sites (any) – EDI paths, glob pattern, Sites, any input accepted by ensure_sites(), or a pre-built pandas.DataFrame from build_phase_tensor_table() (already filtered to one station — in that case station may be left None).

  • station (str or None) – Station to plot. Required when sites resolves to more than one station.

  • period_range ((T_min, T_max) in seconds or None) – Restrict the period range plotted.

  • scale (see) – plot_phase_tensor_psection(). Sizing controls; normalise_by="unity" (φ=45° 1-D reference fills scale of the row) makes phase_ticks an exact scale. min_aspect floors the ellipse height so near-degenerate cells stay visible.

  • normalise_by (see) – plot_phase_tensor_psection(). Sizing controls; normalise_by="unity" (φ=45° 1-D reference fills scale of the row) makes phase_ticks an exact scale. min_aspect floors the ellipse height so near-degenerate cells stay visible.

  • s1_ref (see) – plot_phase_tensor_psection(). Sizing controls; normalise_by="unity" (φ=45° 1-D reference fills scale of the row) makes phase_ticks an exact scale. min_aspect floors the ellipse height so near-degenerate cells stay visible.

  • min_aspect (see) – plot_phase_tensor_psection(). Sizing controls; normalise_by="unity" (φ=45° 1-D reference fills scale of the row) makes phase_ticks an exact scale. min_aspect floors the ellipse height so near-degenerate cells stay visible.

  • cells_per_decade (float, default 7.0) – Visual ellipse pitch along the period axis, expressed as the number of ellipse-widths per log10 decade. Unlike plot_phase_tensor_psection() (one ellipse per station column), a period axis is typically sampled far more densely than is useful for ellipse width — sizing each ellipse to its local sample spacing would shrink it to an invisible sliver wherever sampling is dense. This value is independent of how many periods were actually measured, so ellipses overlap (by design — the classic single-station “ellipse timeseries” look) rather than shrinking as sampling gets denser.

  • c_by – Fill-colour controls; see plot_phase_tensor_psection().

  • cmap – Fill-colour controls; see plot_phase_tensor_psection().

  • clim (tuple[float, float] | None) – Fill-colour controls; see plot_phase_tensor_psection().

  • clim_pct – Fill-colour controls; see plot_phase_tensor_psection().

  • symmetric_clim – Fill-colour controls; see plot_phase_tensor_psection().

  • edgecolor (ellipse border/opacity controls.)

  • linewidth (ellipse border/opacity controls.)

  • alpha (ellipse border/opacity controls.)

  • skew_threshold (3-D cell highlighting, see) – plot_phase_tensor_psection().

  • mark_3d (3-D cell highlighting, see) – plot_phase_tensor_psection().

  • phase_ticks ((lo, mid, hi) or None, default (0.0, 45.0, 90.0)) – Tick values drawn on the schematic y-scale. None hides the y-axis entirely.

  • ylabel (str) – Y-axis label; defaults to "Phase (°)" when phase_ticks is not None.

  • station_label (bool, default True) – Annotate the station name in the upper-left corner of the axes.

  • station_label_fontsize (float, default 8.0)

  • title (str) – Axes title / x-label. xlabel defaults to "Period (s)".

  • xlabel (str) – Axes title / x-label. xlabel defaults to "Period (s)".

  • figsize ((float, float), default (6.0, 1.4)) – Figure size (ignored when ax is provided).

  • show_colorbar (bool, default True) – Attach a right-side colorbar. Set False when composing a grid with a single shared colorbar (see plot_phase_tensor_strip_grid()).

  • colorbar_label (str or None) – Override the automatic colorbar label derived from c_by.

  • recursive (see ensure_sites().)

  • on_dup (see ensure_sites().)

  • strict (see ensure_sites().)

  • verbose (see ensure_sites().)

  • ax (Axes or None) – If provided, draw into this axes and return it; otherwise create a new figure.

Returns:

ax

Return type:

Axes

Examples

>>> from pycsamt.emtools import plot_phase_tensor_strip
>>> ax = plot_phase_tensor_strip(sites, station="S1")

Fixed colour scale (for visual consistency across several calls):

>>> ax = plot_phase_tensor_strip(
...     sites,
...     station="S1",
...     c_by="skew",
...     clim=(-9.0, 9.0),
... )

See also

plot_phase_tensor_strip_grid

Multi-station / multi-profile facet grid.

plot_phase_tensor_psection

Station × period pseudo-section.

pycsamt.emtools.plot_phase_tensor_strip_grid(sites, profiles, *, period_range=None, scale=<object object>, normalise_by=<object object>, s1_ref=None, min_aspect=<object object>, cells_per_decade=7.0, c_by=<object object>, cmap=<object object>, clim=None, clim_pct=<object object>, symmetric_clim=<object object>, edgecolor=<object object>, linewidth=<object object>, alpha=<object object>, skew_threshold=<object object>, mark_3d=<object object>, phase_ticks=(0.0, 45.0, 90.0), col_titles=None, xlabel='Period (s)', suptitle='', colorbar_label=None, panel_size=(4.4, 1.05), recursive=True, on_dup='replace', strict=False, verbose=0, axes=None)#

Phase-tensor ellipse strips for several stations, grouped by profile.

Reproduces the classic multi-panel figure — one plot_phase_tensor_strip() row per selected station, tiled in a grid where each column is a profile/line and each row is one of the stations picked for that profile — with a single shared colorbar for the whole figure (so fill colours are comparable across every panel).

Parameters:
  • sites (any) – Input accepted by ensure_sites() covering every station referenced in profiles.

  • profiles (dict[str, list[str]]) –

    Mapping {profile_label: [station, ...]}, e.g.:

    {
        "Profile L1": ["S1", "S15", "S30", "S45"],
        "Profile L3": ["S1", "S4", "S7", "S11"],
    }
    

    Each list becomes one column; rows are aligned by position, not by station identity (profiles may list a different number of stations — empty cells are left blank).

  • period_range ((T_min, T_max) or None) – Restrict the period range plotted in every panel.

  • scale

  • normalise_by

  • s1_ref (float | None)

  • min_aspect

  • cells_per_decade (float)

  • c_by

  • cmap

  • clim (tuple[float, float] | None)

  • phase_ticks (tuple[float, float, float] | None)

  • col_titles (dict[str, str] | None)

  • xlabel (str)

  • suptitle (str)

  • colorbar_label (str | None)

  • panel_size (tuple[float, float])

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

  • axes (Any | None)

Return type:

Figure

:param : :param clim: :param clim_pct: :param symmetric_clim: :param edgecolor: :param linewidth: :param alpha: :param : :param skew_threshold: Forwarded to every plot_phase_tensor_strip() call; see its

docstring (and plot_phase_tensor_psection()) for details. When clim is None it is derived once from the pooled data of every listed station, so all panels share one colour scale.

Parameters:
  • mark_3d – Forwarded to every plot_phase_tensor_strip() call; see its docstring (and plot_phase_tensor_psection()) for details. When clim is None it is derived once from the pooled data of every listed station, so all panels share one colour scale.

  • phase_ticks ((lo, mid, hi) or None, default (0.0, 45.0, 90.0)) – Schematic y-scale ticks drawn on every panel.

  • col_titles (dict[str, str] or None) – Override the column title text; defaults to the profiles keys.

  • xlabel (str, default "Period (s)") – Shown once under the bottom-most panel of each column.

  • suptitle (str) – Figure-level title.

  • colorbar_label (str or None) – Override the automatic colorbar label derived from c_by.

  • panel_size ((width, height), default (4.4, 1.05)) – Per-panel size in inches; the figure size is (n_cols * width, n_rows * height).

  • recursive (see ensure_sites().)

  • on_dup (see ensure_sites().)

  • strict (see ensure_sites().)

  • verbose (see ensure_sites().)

  • axes (2-D array of Axes or None) – Pre-existing (n_rows, n_cols) axes grid to draw into (n_rows = max(len(v) for v in profiles.values()), n_cols = len(profiles)). A new figure is created when None.

  • sites (Any)

  • profiles (dict[str, list[str]])

  • period_range (tuple[float, float] | None)

  • s1_ref (float | None)

  • cells_per_decade (float)

  • clim (tuple[float, float] | None)

Returns:

fig

Return type:

Figure

Examples

>>> from pycsamt.emtools import plot_phase_tensor_strip_grid
>>> fig = plot_phase_tensor_strip_grid(
...     sites,
...     profiles={
...         "Profile L1": ["S1", "S15", "S30", "S45"],
...         "Profile L3": ["S1", "S4", "S7", "S11"],
...     },
...     c_by="skew",
...     cmap="RdBu_r",
... )

See also

plot_phase_tensor_strip

Single-station ellipse strip (one panel).

plot_phase_tensor_psection

Station × period pseudo-section.

pycsamt.emtools.plot_phasor_wheel(sites, *, station=None, components=('xy', 'yx'), pband=None, radius='abs', cmap='viridis', colors=None, marker='o', ms=3.0, lw=1.0, connect=True, figsize=(4.8, 4.8), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_offdiag_antisym_residual(sites, *, vlim=None, cmap='magma', figsize=(9.0, 4.8), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_determinant_track(sites, *, station=None, pband=None, pcts=(10.0, 50.0, 90.0), n_draws=200, height_ratio=(2, 1), axes=None, figsize=(6.4, 3.8), color_mag='C0', color_phase='C3', fill_alpha=0.2, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.rho_spatial_gradient(sites, spacing_m=200.0, *, comp='det', recursive=True, on_dup='replace', strict=False, verbose=0)#

Transverse (along-line) apparent resistivity gradient.

Computes the first-order finite difference of \(\rho_a\) between adjacent stations at each frequency (eq. 11 of zhang2021):

\[\Delta\rho_a^x(j,\,f) \approx \rho_a(j,\,f) - \rho_a(j-1,\,f)\]

where station indices are ordered by their position along the survey line. The result is assigned to the spatial midpoint between the two stations.

Parameters:
  • sites (Sites | list) – EDI-like objects or a Sites container.

  • spacing_m (float, default 200) – Fall-back inter-station spacing [m] used when no coordinate metadata is available.

  • comp ({"det", "xy", "yx"}, default "det") – Impedance component for \(\rho_a\). "det" uses the geometric-mean determinant.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

One row per (station-pair, frequency). Columns:

station_a, station_b

Names of the left and right stations of each pair.

x_m

Midpoint position along the survey line [m].

dx_m

Spacing between the two stations [m].

freq_hz, period_s

Frequency [Hz] and period [s].

depth_m

Skin depth \(\delta = 503\,\sqrt{\rho_a/f}\) [m] at the midpoint \(\rho_a\) and the given frequency.

rho_a_ohmm

Mean \(\rho_a\) of the pair at that frequency [Ω·m].

delta_rho_x

\(\Delta\rho_a^x\) [Ω·m].

Return type:

pandas.DataFrame

References

Zhang et al. (2021), eq. (11).

pycsamt.emtools.rho_frequency_gradient(sites, *, comp='det', spacing_m=200.0, recursive=True, on_dup='replace', strict=False, verbose=0)#

Vertical (log-frequency) apparent resistivity gradient.

Computes the first-order finite difference of \(\rho_a\) between adjacent frequencies at each station (eq. 12 of zhang2021):

\[\Delta\rho_a^z(j,\,f_k) \approx \rho_a(j,\,f_k) - \rho_a(j,\,f_{k-1})\]

where \(f_k > f_{k-1}\) (ascending frequency, ascending skin-depth index). Because different frequencies probe different depths, \(\Delta\rho_a^z\) is associated with vertical changes in the subsurface.

Parameters:
  • sites (Sites | list) – EDI-like objects or a Sites container.

  • comp ({"det", "xy", "yx"}, default "det") – Impedance component for \(\rho_a\).

  • spacing_m (float, default 200) – Fall-back inter-station spacing [m].

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

One row per (station, adjacent-frequency pair). Columns:

station

Station name.

x_m

Station position along the survey line [m].

freq_hz, period_s

Higher frequency \(f_k\) of the pair [Hz] and its corresponding period [s].

depth_m

Skin depth at the mean \(\rho_a\) of the pair [m].

rho_a_ohmm

Mean \(\rho_a\) of the two adjacent frequencies [Ω·m].

delta_rho_z

\(\Delta\rho_a^z\) [Ω·m].

Return type:

pandas.DataFrame

References

Zhang et al. (2021), eq. (12).

pycsamt.emtools.rho_joint_gradient(sites, spacing_m=200.0, *, comp='det', recursive=True, on_dup='replace', strict=False, verbose=0)#

Joint vertical-transverse apparent resistivity gradient.

Computes the frequency difference of the spatial gradient (eq. 13 of zhang2021), which simultaneously resolves lateral and vertical boundaries while suppressing the spurious background interference in the spatial gradient over homogeneous regions:

\[\Delta\rho_a^{zx}(j,\,f_k) = \Delta\rho_a^x(j,\,f_k) - \Delta\rho_a^x(j,\,f_{k-1})\]

Expanding in terms of \(\rho_a\):

\[\Delta\rho_a^{zx}(j,\,f_k) = \bigl[\rho_a(j,\,f_k) - \rho_a(j-1,\,f_k)\bigr] - \bigl[\rho_a(j,\,f_{k-1}) - \rho_a(j-1,\,f_{k-1})\bigr]\]

where station j is to the right of station j-1 along the survey line and \(f_k > f_{k-1}\).

The result is non-zero only where \(\rho_a\) changes in both the lateral and vertical directions simultaneously, making it a sensitive indicator of target boundaries.

Parameters:
  • sites (Sites | list) – EDI-like objects or a Sites container.

  • spacing_m (float, default 200) – Fall-back inter-station spacing [m].

  • comp ({"det", "xy", "yx"}, default "det") – Impedance component for \(\rho_a\).

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Returns:

One row per (station-pair, adjacent-frequency pair). Columns:

station_a, station_b

Names of the left and right stations of each pair.

x_m

Midpoint position along the survey line [m].

dx_m

Spacing between the two stations [m].

freq_hz, period_s

Upper frequency \(f_k\) of the pair [Hz] and period [s].

depth_m

Skin depth [m] estimated from the median \(\rho_a\) of the four surrounding cells and the frequency \(f_k\).

delta_rho_zx

\(\Delta\rho_a^{zx}\) [Ω·m].

Return type:

pandas.DataFrame

References

Zhang et al. (2021), eqs. (1), (11), (13).

pycsamt.emtools.plot_gradient_section(sites, quantity='joint', spacing_m=200.0, *, comp='det', period_axis=True, log_y=True, figsize=(10.0, 5.0), cmap='RdBu_r', vlim=None, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Pseudo-section of a gradient apparent resistivity quantity.

Produces a colour-coded pseudo-section (position × period or frequency) for one of three gradient quantities from zhang2021:

  • "spatial" (\(\Delta\rho_a^x\)) — lateral boundaries.

  • "frequency" (\(\Delta\rho_a^z\)) — vertical boundaries.

  • "joint" (\(\Delta\rho_a^{zx}\)) — combined; best boundary delineation and suppressed background interference (default).

A diverging colour-map centred at zero is used so that positive and negative gradient values (resistivity increase vs. decrease) can be distinguished at a glance.

Parameters:
  • sites (Sites | list) – EDI-like objects or a Sites container.

  • quantity ({"joint", "spatial", "frequency"}, default "joint") – Which gradient pseudo-section to plot.

  • spacing_m (float, default 200) – Fall-back inter-station spacing [m].

  • comp ({"det", "xy", "yx"}, default "det") – Impedance component for \(\rho_a\).

  • period_axis (bool, default True) – Show period [s] on the y-axis when True; frequency [Hz] when False.

  • log_y (bool, default True) – Use a logarithmic y-axis.

  • figsize ((float, float), default (10, 5))

  • cmap (str, default "RdBu_r") – Diverging Matplotlib colour-map.

  • vlim ((vmin, vmax) or None) – Colour-scale limits [Ω·m]. If None, a symmetric range centred at zero is derived from the data.

  • ax (matplotlib.axes.Axes or None) – Draw on existing axes; a new figure is created if None.

  • recursive (bool) – Forwarded to ensure_sites().

  • on_dup (str) – Forwarded to ensure_sites().

  • strict (bool) – Forwarded to ensure_sites().

  • verbose (int) – Forwarded to ensure_sites().

Return type:

matplotlib.axes.Axes

References

Zhang et al. (2021), Geophysical Prospecting, doi:10.1111/1365-2478.13059.

pycsamt.emtools.sites_summary(sites, *, fields=('station', 'n_freq', 'has_tipper', 'period_min', 'period_max', 'lat', 'lon'), recursive=True, on_dup='replace', strict=False, verbose=0, api=None)#
Parameters:
Return type:

Any

pycsamt.emtools.list_missing_sections(sites, *, require=('mt', 'tipper'), recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

dict[str, list[str]]

pycsamt.emtools.frequency_coverage(sites, *, mode='per-site', recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
Return type:

Any

pycsamt.emtools.plot_coverage(sites, *, axis='period', show_mask=True, figsize=(7.0, 4.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.plot_survey_inventory_overview(sites, *, station_order=None, station_labels=None, count_kws=None, coverage_cmap='YlGnBu', station_grid=True, station_grid_kws=None, axes=None, figsize=None, title='Survey inventory overview', recursive=True, on_dup='replace', strict=False, verbose=0)#

Plot station inventory counts and the observed period coverage map.

The upper panel reports the number of usable frequency rows at each station. The lower panel shows where those rows occur on a common log-period axis, so equal row counts cannot conceal different bands or internal gaps. Both panels use the same station centres.

Parameters:
  • sites (any) – Any input accepted by ensure_sites().

  • station_order (list of str or None) – Explicit station order. Missing names are retained as empty columns.

  • station_labels (list of str or None) – Display labels corresponding to station_order. By default the resolved station names are used.

  • count_kws (dict or None) – Matplotlib line/marker overrides for the upper inventory profile. Useful keys include color, marker, markersize, linewidth, markerfacecolor, and markeredgecolor.

  • coverage_cmap (str, default="YlGnBu") – Colormap for absent/present samples in the lower map.

  • station_grid (bool, default=True) – Draw aligned vertical station guides in both panels.

  • station_grid_kws (dict or None) – Keyword arguments forwarded to Axes.axvline.

  • axes ((Axes, Axes) or None) – Existing (ax_count, ax_map) pair to draw into. When None (default), a new two-panel figure is created.

  • figsize (tuple[float, float] | None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

Figure containing the aligned inventory and coverage axes.

Return type:

matplotlib.figure.Figure

pycsamt.emtools.plot_rhoa_phi(sites, *, components=('xy', 'yx'), axis='period', errorbar=True, figsize=(7.5, 6.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax_r=None, ax_p=None)#
Parameters:
Return type:

tuple[Axes, Axes]

pycsamt.emtools.plot_tipper_components(sites, *, kind=('real', 'imag'), axis='period', figsize=(7.5, 4.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
Return type:

Axes

pycsamt.emtools.pseudosection(sites, *, quantity='rho_xy', axis_x='station', axis_y='period', period_range=None, vmin=None, vmax=None, figsize=(7.5, 4.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None, topo=None, dark=True)#

Draw a period-vs-station pseudosection.

Parameters:
  • sites (Sites or compatible) – Station data.

  • quantity (str) – Column name to plot (e.g. "rho_xy", "phi_xy").

  • topo (bool or None) – Override the global PYCSAMT_TOPO setting. None (default) reads the global singleton.

  • dark (bool) – Use dark-palette styling for the topo strip.

  • axis_x (str)

  • axis_y (str)

  • period_range (tuple[float, float] | None)

  • vmin (float | None)

  • vmax (float | None)

  • figsize (tuple[float, float])

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

  • ax (Axes | None)

Return type:

Axes

pycsamt.emtools.plot_station_response(sites, *, station=None, sites_model=None, components=('xx', 'xy', 'yx', 'yy'), period_range=None, rho_lim=None, phase_lim=None, tipper_lim=(-0.5, 0.5), show_tipper=True, show_error_bars=True, show_rms=True, title='', axes=None, figsize=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Full four-component impedance tensor + tipper response for one station.

Renders a 3-row × N-column figure (N = len(components)) following the MTComponentStyle colour scheme:

  • Row 0 — apparent resistivity ρa (Ω·m) vs period, log–log.

  • Row 1 — phase φ (°) vs period, semilog-x.

  • Row 2 — tipper magnitude: Re(Tx), Im(Tx), Re(Ty), Im(Ty) vs period, semilog-x. Hidden when show_tipper is False or when no tipper data are found.

Each column corresponds to one impedance tensor component (Zxx, Zxy, Zyx, Zyy). The tipper row always uses four fixed sub-panels regardless of which Z components are selected.

An optional sites_model argument overlays a second (model/forward) dataset on the same axes as dotted lines. When both observed and model data are present, a per-component RMS is computed in log10(ρa) space and appended to each column header.

Parameters:
  • sites (any) – Observed EDI data — path, SitesCollection, or anything accepted by ensure_sites().

  • station (str or None) – Name of the station to plot. None picks the first available.

  • sites_model (any or None) – Optional forward-model or inversion-response EDI data (same API as sites). When provided, overlay dashed lines and show RMS.

  • components (tuple of {"xx","xy","yx","yy"}, default all four) – Z-tensor components to display. Order determines column order.

  • period_range ((T_min, T_max) or None) – Clip period axis to this window (seconds).

  • rho_lim ((vmin, vmax) or None) – ρa y-axis limits. None → matplotlib auto.

  • phase_lim ((lo, hi) or None) – Phase y-axis limits in degrees. None → auto.

  • tipper_lim ((lo, hi), default (-0.5, 0.5)) – Tipper y-axis limits.

  • show_tipper (bool, default True) – Whether to add the tipper row.

  • show_error_bars (bool, default True) – Draw error bars on observed data.

  • show_rms (bool, default True) – Append per-component RMS to column titles when sites_model is set.

  • title (str) – Override the auto-derived figure title.

  • figsize ((float, float) or None) – Figure size. Auto-computed when None.

  • recursive (bool) – Passed to ensure_sites().

  • on_dup (str) – Passed to ensure_sites().

  • strict (bool) – Passed to ensure_sites().

  • verbose (int) – Passed to ensure_sites().

Return type:

matplotlib.figure.Figure

Examples

Observed data only:

>>> from pycsamt.emtools import plot_station_response
>>> fig = plot_station_response("path/to/edis/", station="S07")

With model overlay:

>>> fig = plot_station_response(
...     obs_edis,
...     station="HBH03_IMP",
...     sites_model=model_edis,
...     period_range=(1e-4, 1.0),
... )
pycsamt.emtools.plot_tipper_hodograms(sites, *, station=None, bands=None, n_bands=3, normalize=False, colors=None, marker='o', ms=3.0, lw=1.0, ls='-', unit_circle=True, axes=None, figsize=(6.4, 3.2), recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.plot_induction_arrows(sites, *, periods=(1.0,), convention='park', scale=1.0, normalize=True, strike_ticks=True, tick_len=0.25, figsize=(7.2, 3.4), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#
Parameters:
pycsamt.emtools.plot_induction_map(sites, *, period=1.0, convention='park', show_real=True, show_imag=True, scale=<object object>, cmap='plasma', clim=None, show_colorbar=True, reference_arrow=0.1, station_labels=True, title='', figsize=(8, 7), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Map-view induction arrows at one period.

Real (solid) and imaginary (dashed) Parkinson arrows at every station, coloured by |T| magnitude.

Parameters:
  • sites (Sites-like)

  • period (float) – Target period in seconds.

  • convention ({'park', 'wiese', 'real', 'imag'})

  • show_real (bool)

  • show_imag (bool)

  • scale (float or _UNSET) – Arrow scale factor (auto from station spacing).

  • cmap (str)

  • clim ((vmin, vmax) or None)

  • show_colorbar (bool)

  • reference_arrow (float) – Length of the scale-bar reference arrow.

  • station_labels (bool)

  • title (str)

  • figsize (standard)

  • ax (standard)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

ax

Return type:

Axes

pycsamt.emtools.plot_induction_section(sites, *, component='abs', n_periods=20, cmap='RdBu_r', clim=None, section='pseudosection', title='', figsize=None, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Period × station pseudo-section coloured by |T| magnitude.

Parameters:
  • sites (Sites-like) – Anything accepted by ensure_any_sites(): ground Sites (impedance/tipper) or tipper-only AirborneSites (ZTEM/AFMAG) transparently – both expose a (nf, 1, 2) tipper, the only shape this plot reads.

  • component ({'real', 'imag', 'abs'})

  • n_periods (int)

  • cmap (str)

  • clim ((vmin, vmax) or None)

  • section (str or SectionStyle)

  • title (standard)

  • figsize (standard)

  • ax (standard)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

ax

Return type:

Axes

pycsamt.emtools.plot_induction_convention(sites, *, period=1.0, scale=<object object>, station_labels=True, title='', axes=None, figsize=(11, 10), recursive=True, on_dup='replace', strict=False, verbose=0)#

2×2 panel: Parkinson/Wiese × Real/Imaginary conventions.

Parkinson — Real

Parkinson — Imaginary

Wiese — Real

Wiese — Imaginary

Parameters:
  • sites (Sites-like)

  • period (float)

  • scale (standard)

  • station_labels (standard)

  • title (standard)

  • figsize (standard)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

axes

Return type:

ndarray of Axes, shape (2, 2)

pycsamt.emtools.plot_tipper_polar(sites, *, station=None, component='real', cmap=<object object>, lw=<object object>, alpha=<object object>, title='', figsize=(5.5, 5.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Polar view: tipper azimuth (angle) and magnitude (radius) vs period.

Each frequency is one scatter point; colour encodes log₁₀(period). North (0°) = up, clockwise positive, following geomagnetic convention.

Parameters:
  • sites (Sites-like)

  • station (str or None)

  • component ({'real', 'imag', 'abs'})

  • cmap (str or _UNSET)

  • lw (float or _UNSET)

  • alpha (float or _UNSET)

  • title (standard)

  • figsize (standard)

  • ax (standard)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

ax

Return type:

polar Axes

pycsamt.emtools.plot_induction_rose(sites, *, component='real', pband=None, nbins=36, style=<object object>, title='', figsize=(5, 5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Rose diagram of induction arrow azimuths (all stations & periods).

Parameters:
  • sites (Sites-like)

  • component ({'real', 'imag', 'abs'})

  • pband ((T_min, T_max) or None)

  • nbins (int (default 36 → 10° bins))

  • style (RoseStyle or str or _UNSET)

  • title (standard)

  • figsize (standard)

  • ax (standard)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Returns:

ax

Return type:

polar Axes

pycsamt.emtools.plot_induction_map_from_spectra(sp_input, *, period=1.0, coords=None, show_real=True, show_imag=True, scale=<object object>, cmap='plasma', station_labels=True, title='', figsize=(8, 5), ax=None)#

Map-view induction arrows from Spectra.

Parameters:
  • sp_input (Spectra, list, or dict[str, Spectra])

  • period (float)

  • coords (dict[name → (x, y)] or None) – Station positions. Equidistant line when None.

  • show_real (bool)

  • show_imag (bool)

  • scale (standard)

  • cmap (standard)

  • station_labels (standard)

  • title (standard)

  • figsize (standard)

  • ax (standard)

Returns:

ax

Return type:

Axes

pycsamt.emtools.plot_tipper_polar_from_spectra(sp, *, component='real', cmap='viridis', title='', ax=None, figsize=(5.5, 5.5))#

Polar tipper from a Spectra object.

Parameters:
  • sp (Spectra)

  • component ({'real', 'imag', 'abs'})

  • cmap (standard)

  • title (standard)

  • figsize (standard)

Returns:

ax

Return type:

polar Axes

pycsamt.emtools.plot_induction_rose_from_spectra(sp_input, *, component='real', pband=None, nbins=36, style=<object object>, title='', figsize=(5, 5), ax=None)#

Rose diagram of induction arrow directions from Spectra objects.

Parameters:
  • sp_input (Spectra, list, or dict[str, Spectra])

  • component ({'real', 'imag', 'abs'})

  • pband ((T_min, T_max) or None)

  • nbins (int)

  • style (RoseStyle or str or _UNSET)

  • title (standard)

  • figsize (standard)

  • ax (standard)

Returns:

ax

Return type:

polar Axes

pycsamt.emtools.plot_induction_multiperiod_map(sites, *, periods=(1.0, 10.0, 100.0, 1000.0), tipper_data=None, convention='park', panel_labels=None, background=None, background_extent=None, background_cmap='terrain', background_alpha=0.75, background_clim=None, bg_colorbar_label='Elevation  (m)', bg_colorbar_side='right', show_background_cbar=True, arrow_color='black', arrow_scale=<object object>, arrow_lw=1.6, reference_arrow=0.1, reference_panel=0, reference_label=<object object>, show_stations=True, station_labels=False, annotations=None, annotation_color='navy', annotation_fontsize=8.0, title='', xlabel='x  (m)', ylabel='y  (m)', axes=None, figsize=<object object>, panel_height=3.0, panel_width=8.5, recursive=True, on_dup='replace', strict=False, verbose=0)#

Stacked multi-period induction vector map.

Produces one panel per period (stacked vertically), each showing the real Parkinson induction vectors on a background colour map (elevation, resistivity, or any 2-D raster). The layout mimics the style of Boukhalfa et al. (2020, GJI) Fig. 7 and the paper figure described in the session above.

2.14. Visual conventions#

  • Arrows: black solid vectors using the Parkinson (1962) convention (real component pointing toward the conductor).

  • Background: smooth terrain coloured with background_cmap; a synthetic gradient is auto-generated when background is None.

  • Reference vector: drawn in the first (or reference_panel) sub-plot; labelled with its normalised length.

  • Shared colorbar: placed on the right edge of the figure, spanning all panels.

  • Panel labels: "(A) 1 s", "(B) 10 s", … placed in the lower-left corner of each panel.

  • Geological / site annotations: optional blue text labels supplied via annotations.

param sites:

EDI collection accepted by ensure_sites(). When the EDIs carry no tipper (Tipper.tipper all-zero), pass synthetic tipper via tipper_data.

type sites:

Sites-like

param periods:

Target periods in seconds, one panel each.

type periods:

sequence of float

param tipper_data:

Explicit tipper override. Keys must match periods (nearest match is used). Column 0 = T_x, column 1 = T_y. When absent, the tipper is read from the EDIs.

type tipper_data:

dict {period → (n_sites, 2) complex array}, optional

param convention:

Arrow convention. "park" (Parkinson real) is the only one used in published induction-vector maps; "wiese" is supported.

type convention:

str

param panel_labels:

Panel corner labels (e.g. ["(A) 1 s", "(B) 10 s", ...]). Auto-generated when None.

type panel_labels:

sequence of str, optional

param background:

Pre-computed background raster. A smooth synthetic terrain is generated when None.

type background:

ndarray (ny, nx), optional

param background_extent:

Geographic extent for the background raster. Auto-inferred from station positions when None.

type background_extent:

(x_left, x_right, y_bottom, y_top), optional

param background_cmap:

Colormap for the background. Default "terrain" (green→brown elevation appearance).

type background_cmap:

str

param background_alpha:

Background opacity (0–1).

type background_alpha:

float

param background_clim:

type background_clim:

(vmin, vmax) or None

param bg_colorbar_label:

Label for the shared colorbar.

type bg_colorbar_label:

str

param bg_colorbar_side:

Side of the figure where the shared background colorbar is placed. Right-side placement is the package default because it keeps section axes and station labels visually grouped.

type bg_colorbar_side:

{‘right’, ‘left’, ‘top’, ‘bottom’}, default ‘right’

param show_background_cbar:

type show_background_cbar:

bool

param arrow_color:

Single colour for all induction vectors. Default "black".

type arrow_color:

str

param arrow_scale:

Multiplier for arrow length in data units. Auto-computed from the typical station spacing when _UNSET.

type arrow_scale:

float or _UNSET

param arrow_lw:

Arrow shaft line width in points.

type arrow_lw:

float

param reference_arrow:

Normalised length of the scale-reference arrow drawn in the reference_panel sub-plot. Default 0.1.

type reference_arrow:

float

param reference_panel:

Sub-plot index (0-based) where the reference arrow appears.

type reference_panel:

int

param reference_label:

Text for the reference arrow. Defaults to "Vector length {reference_arrow}".

type reference_label:

str or _UNSET

param show_stations:

Draw a small marker () at each station position.

type show_stations:

bool

param station_labels:

Annotate station names next to markers.

type station_labels:

bool

param annotations:

Geological or site annotations drawn in annotation_color on every panel.

type annotations:

dict {label: (x, y)} or {label: (x, y, {fontsize, color, …})}, optional

param annotation_color:

Default colour for annotations.

type annotation_color:

str

param annotation_fontsize:

type annotation_fontsize:

float

param title:

Figure suptitle.

type title:

str

param xlabel:

Coordinate-axis labels. Set these to longitude/latitude labels when geographic coordinates, rather than projected metres, are supplied.

type xlabel:

str

param ylabel:

Coordinate-axis labels. Set these to longitude/latitude labels when geographic coordinates, rather than projected metres, are supplied.

type ylabel:

str

param figsize:

Auto-computed from panel_height, panel_width, and number of panels when omitted.

type figsize:

(float, float) or _UNSET

param panel_height:

Per-panel size in inches used for auto figsize.

type panel_height:

float

param panel_width:

Per-panel size in inches used for auto figsize.

type panel_width:

float

param recursive:

type recursive:

standard ensure_sites kwargs.

param on_dup:

type on_dup:

standard ensure_sites kwargs.

param strict:

type strict:

standard ensure_sites kwargs.

param verbose:

type verbose:

standard ensure_sites kwargs.

returns:
  • fig (Figure)

  • axes (ndarray of Axes, shape (n_periods,))

Examples

Real data with tipper:

fig, axs = plot_induction_multiperiod_map(
    "site.edi",
    periods=[1, 10, 100, 1000],
)

Synthetic tipper supplied explicitly:

tipper = {1.0: st_tips_1s, 10.0: st_tips_10s, 100.0: st_tips_100s}
fig, axs = plot_induction_multiperiod_map(
    "profile/*.edi",
    periods=[1, 10, 100],
    tipper_data=tipper,
)
Parameters:
Return type:

tuple[Figure, ndarray]

pycsamt.emtools.plot_response_overview(sites, *, station=None, control=None, x_view='period', log_log_rho=<object object>, offdiag_components=('xy', 'yx'), diag_components=('xx', 'yy'), show_diag=True, phase_range=None, height_ratios=(2.0, 1.0, 1.4, 1.7), cbar_orientation='horizontal', cbar_height_ratio=0.12, cbar_pad_ratio=0.55, cbar_width_ratio=0.1, figsize=(11.0, 9.8), wspace=0.24, hspace=0.1, axes=None, colors=None, raw=False, force_style=False, show_error_bars=True, show_phase_error_bars=False, show_component_legend=True, title=None, show_arrows=True, arrow_colors=(None, None), arrow_tilt_decades=0.4, arrow_dy_scale=1.0, arrow_lw=1.3, arrow_mutation_scale=7.0, ylim_arrows=None, show_arrow_legend=True, show_ellipses=True, c_by=<object object>, cmap=<object object>, clim=None, clim_pct=<object object>, symmetric_clim=<object object>, ellipse_scale=1.5, min_aspect=<object object>, cells_per_decade=6.0, edgecolor=<object object>, linewidth=<object object>, ellipse_alpha=<object object>, skew_threshold=<object object>, mark_3d=<object object>, show_ellipse_colorbar=True, ellipse_colorbar_label=None, tick_fontsize=8, grid=True, recursive=True, on_dup='replace', strict=False, verbose=0)#

Single-station MT/CSAMT “full response” overview figure.

Reproduces the classic multi-panel layout used to QC a wideband MT sounding – apparent resistivity and phase for the off-diagonal (\(Z_{xy}, Z_{yx}\)) and diagonal (\(Z_{xx}, Z_{yy}\)) impedance components side by side, with induction arrows and phase-tensor ellipses spanning both columns underneath, all sharing one period/frequency axis – while going entirely through pyCSAMT’s own PYCSAMT_STYLE and PYCSAMT_CONTROL systems instead of hardcoded colours or axis conventions.

Layout (rows, top to bottom; all four data rows share the x-axis):

┌─────────────────┬─────────────────┐
│  App. Res. xy/yx │ App. Res. xx/yy │        height_ratios[0]
├─────────────────┼─────────────────┤
│  Phase xy/yx     │ Phase xx/yy     │        height_ratios[1]
├─────────────────┴─────────────────┤
│      Induction arrows (real/imag)  │        height_ratios[2]
├─────────────────────────────────────┤
│      Phase-tensor ellipse strip    │        height_ratios[3]
├─────────────────────────────────────┤
│           skew β (°) colourbar     │        cbar_height_ratio
└─────────────────────────────────────┘

The rho/phase/arrow/ellipse rows all span the full data width; the ellipse row’s colourbar lives in its own reserved row below it by default (see cbar_orientation) rather than a right-hand gutter, so it never narrows the ellipse row relative to the arrow row above it and the right margin stays free.

Every row shares one x-axis. By default (x_view = "period") that axis is a true Matplotlib log scale over raw period in seconds – the classic MT “log-log” quicklook, complete with Matplotlib’s own per-decade minor-tick grid. Apparent resistivity likewise defaults to a true log y-axis over raw \(\Omega\cdot\mathrm{m}\) values (log_log_rho), so the top row is genuinely log-log and the phase row below it (linear degrees over the same log x-axis) is genuinely semilog – both draw the dense reference-figure-style grid rather than sparse integer gridlines over pre-logged numbers. Pass x_view="log10_period" for the alternative \(\log_{10}T\,(\mathrm{s})\) linear-axis convention used elsewhere in emtools (e.g. plot_raw_sites_1d()), or x_view=None to defer entirely to control.x.view. Apparent resistivity similarly can be forced to the pre-logged \(\log_{10}\rho_a\) linear-axis convention with log_log_rho=False (then following control.rho.view), and phase follows control.phase throughout.

Parameters:
  • sites (Sites-like) – EDI path, glob pattern, Sites, or any input accepted by pycsamt.emtools.ensure_sites().

  • station (str or None) – Station to plot. Defaults to the first station (sorted by name) when sites resolves to more than one.

  • control (object, optional) – Plot view control. Defaults to pycsamt.api.control.PYCSAMT_CONTROL. Only control.phase (and, when log_log_rho is False, control.rho) is read directly from this object – the x-axis view is controlled separately by x_view so this function’s default log-log look does not depend on, or silently change, the shared global control’s x.view setting.

  • x_view (str or None, default "period") – X-axis convention for all four rows, applied as a local override on top of control (the shared control object is never mutated). One of "period", "log10_period", "frequency", "log10_frequency" (see pycsamt.api.control.FrequencyAxisControl), or None to use whatever control.x.view is already set to.

  • log_log_rho (bool, optional) – Force the apparent-resistivity row onto a true log y-axis over raw values (True) or the pre-logged \(\log_{10}\rho_a\)-on-linear-axis convention (False). Defaults to True when the resolved control.rho.view is "log10" (the package default) and False otherwise, so passing rho__view="linear" through control still works as expected without needing to also touch this flag.

  • offdiag_components ((str, str)) – Impedance components drawn (overlaid on the same axes pair) in the left and right column respectively. Any two-letter component keys accepted by PYCSAMT_STYLE.mt work here, so a TE/TM pair (("te", "tm")) is equally valid if that is how a survey was processed.

  • diag_components ((str, str)) – Impedance components drawn (overlaid on the same axes pair) in the left and right column respectively. Any two-letter component keys accepted by PYCSAMT_STYLE.mt work here, so a TE/TM pair (("te", "tm")) is equally valid if that is how a survey was processed.

  • show_diag (bool, default True) – Draw the right-hand (diag_components) column. When False the figure narrows to a single column and the arrow/ellipse rows span that one column’s width.

  • phase_range ((lo, hi) or None, optional) – Explicit phase display range shared by both columns. If omitted, the active control.phase policy is used (default \(\pm 180^\circ\)) – one consistent range for both columns, unlike tools that let the off-diagonal and diagonal phase axes drift to different ranges.

  • height_ratios ((rho, phase, arrows, ellipses), default (2.0, 1.0, 1.4, 1.7)) – Relative row heights. Apparent resistivity gets twice the phase row’s height by default (a 2:1 ratio, i.e. rho is 2/3 and phase 1/3 of the combined rho+phase height) – the conventional “big rho, small phase” look; pass e.g. (1.0, 1.0, ...) for equal rows instead.

  • cbar_orientation ({"horizontal", "vertical"}, default "horizontal") – Placement of the ellipse-row colourbar. "horizontal" (the default) adds it as its own thin row below the ellipse strip, spanning the full data width and freeing the right-hand margin entirely. "vertical" instead reserves a narrow gutter column to the right of every row (see cbar_width_ratio). Either way the space is reserved in the gridspec up front rather than carved out of the ellipse axes after the fact – the latter is what matplotlib.figure.Figure.colorbar()-via-divider approaches normally do, and it silently shrinks the ellipse row relative to the arrow row above it even though both still report “the same” x-limits, so a given period ends up at a different pixel column in each row. Reserving the space in advance keeps the arrow and ellipse rows pixel-aligned.

  • cbar_height_ratio (float, default 0.12) – Height of the horizontal colourbar row (cbar_orientation= "horizontal"), relative to the same units as height_ratios (e.g. relative to the ellipse row’s own height_ratios[3]).

  • cbar_pad_ratio (float, default 0.55) – Height of a blank spacer row inserted between the ellipse row and the horizontal colourbar row, in the same units as height_ratios. Needed because the ellipse row’s own x tick labels and “Period (s)” label are drawn outside its axes box, in the margin below it – with too little pad they collide with the colourbar’s own tick labels. Increase if your tick_fontsize or a custom x label still overlaps the colourbar.

  • cbar_width_ratio (float, default 0.10) – Width of the vertical colourbar column (cbar_orientation= "vertical"), as a fraction of one data column. Both cbar_height_ratio and cbar_width_ratio – like the rest of the reserved-space guarantee described under cbar_orientation – are ignored when axes is provided (the colourbar then falls back to narrowing the ellipse axes itself, since new gridspec rows/columns cannot be inserted into an already-built external layout).

  • figsize ((float, float), default (11.0, 9.8)) – Figure size (ignored when axes is provided).

  • wspace (float) – Column/row spacing (ignored when axes is provided).

  • hspace (float) – Column/row spacing (ignored when axes is provided).

  • axes (sequence of Axes or None) – Pre-built axes to draw into instead of creating a new figure and gridspec. Must supply, in order, the rho and phase axes for each column (2 * ncols axes, ncols=2 unless show_diag is False), followed by the arrow-row axes and the ellipse-row axes – e.g. for the default two-column layout: [ax_rho_off, ax_rho_diag, ax_phase_off, ax_phase_diag, ax_arrow, ax_ellipse]. Useful for composing this overview into a larger multi-station figure built with your own gridspec. Note the arrow/ellipse pixel-alignment guarantee described under cbar_width_ratio does not apply here – align them yourself if you supply axes.

  • colors (dict, optional) – Optional per-component colour overrides (keys are component letters, e.g. {"xy": "black"}). Omitted components keep their PYCSAMT_STYLE.mt colour – the package default is already the reference-figure convention (blue circles for \(Z_{xy}\), red squares for \(Z_{yx}\)).

  • raw (bool) – When raw=True, curves use PYCSAMT_STYLE.raw (a neutral diagnostic style) instead of per-component colours, unless force_style is also True. See plot_raw_sites_1d() for the same convention.

  • force_style (bool) – When raw=True, curves use PYCSAMT_STYLE.raw (a neutral diagnostic style) instead of per-component colours, unless force_style is also True. See plot_raw_sites_1d() for the same convention.

  • show_error_bars (bool, default True, False) – Toggle apparent-resistivity and phase error bars independently. Both use the same colour as their component’s curve (through style.errorbar_kwargs()), which is the “error bars based on that colour” behaviour.

  • show_phase_error_bars (bool, default True, False) – Toggle apparent-resistivity and phase error bars independently. Both use the same colour as their component’s curve (through style.errorbar_kwargs()), which is the “error bars based on that colour” behaviour.

  • show_component_legend (bool, default True) – Draw a small legend (component colour + marker) inside each top-row (apparent-resistivity) panel.

  • title (str or None) – Figure title. Defaults to the station name.

  • show_arrows (bool, default True) – Draw the induction-arrow row. Skipped (with a “no tipper” placeholder) when the station has no tipper data.

  • arrow_colors ((real, imag), default (None, None)) – Explicit colour override for the real- and imaginary-part arrows. None falls back to PYCSAMT_STYLE.mt.xy and PYCSAMT_STYLE.mt.yx respectively – the same real/imag colour convention already used by pycsamt.emtools.plot.plot_response_tipper().

  • arrow_tilt_decades (float, default 0.4) – Schematic horizontal fan applied to each arrow (see _draw_induction_arrow_row()); purely cosmetic separation, not a period shift.

  • arrow_dy_scale (float, default 1.0) – Scales the arrow’s vertical (physically meaningful) extent. Tipper magnitudes are typically O(0.01-1), so the default already matches a sensible row height; increase for a very “quiet” station or decrease if arrows overrun neighbouring rows.

  • arrow_lw (float) – Arrow line width and matplotlib mutation_scale (head size).

  • arrow_mutation_scale (float) – Arrow line width and matplotlib mutation_scale (head size).

  • ylim_arrows ((lo, hi) or None) – Explicit y-limits for the arrow row. Auto-scaled from the drawn arrow tips (with margin) when omitted.

  • show_arrow_legend (bool, default True) – Draw a “real”/”imag” legend below the arrow row.

  • show_ellipses (bool, default True) – Draw the phase-tensor ellipse row. Skipped (with a placeholder) when phase-tensor invariants cannot be computed for the station.

  • c_by

  • cmap

  • clim (tuple[float, float] | None)

  • clim_pct

  • symmetric_clim

  • ellipse_scale (float)

  • min_aspect

  • cells_per_decade (float)

  • show_ellipse_colorbar (bool)

  • ellipse_colorbar_label (str | None)

  • tick_fontsize (int)

  • grid (bool)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

Figure

:param : :param edgecolor: Phase-tensor ellipse controls – identical semantics to

pycsamt.emtools.tensor.plot_phase_tensor_strip(), defaulting to PYCSAMT_STYLE.pt_ellipse. To reproduce the classic MTpy “phimin” colouring (blue-to-orange-to-navy over \(0^\circ\)-\(90^\circ\)), pass c_by="phimin_deg", clim=(0.0, 90.0) together with a diverging colormap of your choice – note this differs from c_by="phi_min", which colours by the raw (tan-units) phase-tensor singular value instead of its arctan in degrees and saturates near one end of a 0-90 scale; the package default (c_by="skew") is the more diagnostic choice for flagging 3-D structure.

Parameters:
  • linewidth – Phase-tensor ellipse controls – identical semantics to pycsamt.emtools.tensor.plot_phase_tensor_strip(), defaulting to PYCSAMT_STYLE.pt_ellipse. To reproduce the classic MTpy “phimin” colouring (blue-to-orange-to-navy over \(0^\circ\)-\(90^\circ\)), pass c_by="phimin_deg", clim=(0.0, 90.0) together with a diverging colormap of your choice – note this differs from c_by="phi_min", which colours by the raw (tan-units) phase-tensor singular value instead of its arctan in degrees and saturates near one end of a 0-90 scale; the package default (c_by="skew") is the more diagnostic choice for flagging 3-D structure.

  • ellipse_alpha – Phase-tensor ellipse controls – identical semantics to pycsamt.emtools.tensor.plot_phase_tensor_strip(), defaulting to PYCSAMT_STYLE.pt_ellipse. To reproduce the classic MTpy “phimin” colouring (blue-to-orange-to-navy over \(0^\circ\)-\(90^\circ\)), pass c_by="phimin_deg", clim=(0.0, 90.0) together with a diverging colormap of your choice – note this differs from c_by="phi_min", which colours by the raw (tan-units) phase-tensor singular value instead of its arctan in degrees and saturates near one end of a 0-90 scale; the package default (c_by="skew") is the more diagnostic choice for flagging 3-D structure.

  • skew_threshold – Phase-tensor ellipse controls – identical semantics to pycsamt.emtools.tensor.plot_phase_tensor_strip(), defaulting to PYCSAMT_STYLE.pt_ellipse. To reproduce the classic MTpy “phimin” colouring (blue-to-orange-to-navy over \(0^\circ\)-\(90^\circ\)), pass c_by="phimin_deg", clim=(0.0, 90.0) together with a diverging colormap of your choice – note this differs from c_by="phi_min", which colours by the raw (tan-units) phase-tensor singular value instead of its arctan in degrees and saturates near one end of a 0-90 scale; the package default (c_by="skew") is the more diagnostic choice for flagging 3-D structure.

  • mark_3d – Phase-tensor ellipse controls – identical semantics to pycsamt.emtools.tensor.plot_phase_tensor_strip(), defaulting to PYCSAMT_STYLE.pt_ellipse. To reproduce the classic MTpy “phimin” colouring (blue-to-orange-to-navy over \(0^\circ\)-\(90^\circ\)), pass c_by="phimin_deg", clim=(0.0, 90.0) together with a diverging colormap of your choice – note this differs from c_by="phi_min", which colours by the raw (tan-units) phase-tensor singular value instead of its arctan in degrees and saturates near one end of a 0-90 scale; the package default (c_by="skew") is the more diagnostic choice for flagging 3-D structure.

  • cells_per_decade (float, default 8.0) – Visual ellipse pitch along the period axis (ellipse-widths per decade); see pycsamt.emtools.tensor.plot_phase_tensor_strip() for why this is independent of the actual sample spacing.

  • show_ellipse_colorbar (bool, default True) – Attach a colourbar to the ellipse row.

  • ellipse_colorbar_label (str or None) – Override the automatic colourbar label derived from c_by.

  • tick_fontsize (int, default 8) – Tick-label size shared by all rows.

  • grid (bool, default True) – Draw light panel grids on the rho/phase/arrow rows.

  • recursive (bool) – Forwarded to pycsamt.emtools.ensure_sites().

  • on_dup (str) – Forwarded to pycsamt.emtools.ensure_sites().

  • strict (bool) – Forwarded to pycsamt.emtools.ensure_sites().

  • verbose (int) – Forwarded to pycsamt.emtools.ensure_sites().

  • sites (Any)

  • station (str | None)

  • control (Any | None)

  • x_view (str | None)

  • offdiag_components (tuple[str, str])

  • diag_components (tuple[str, str])

  • show_diag (bool)

  • phase_range (tuple[float, float] | None)

  • height_ratios (tuple[float, float, float, float])

  • cbar_orientation (str)

  • cbar_height_ratio (float)

  • cbar_pad_ratio (float)

  • cbar_width_ratio (float)

  • figsize (tuple[float, float])

  • wspace (float)

  • hspace (float)

  • colors (dict[str, str] | None)

  • raw (bool)

  • force_style (bool)

  • show_error_bars (bool)

  • show_phase_error_bars (bool)

  • show_component_legend (bool)

  • title (str | None)

  • show_arrows (bool)

  • arrow_colors (tuple[str | None, str | None])

  • arrow_tilt_decades (float)

  • arrow_dy_scale (float)

  • arrow_lw (float)

  • arrow_mutation_scale (float)

  • ylim_arrows (tuple[float, float] | None)

  • show_arrow_legend (bool)

  • show_ellipses (bool)

  • clim (tuple[float, float] | None)

  • ellipse_scale (float)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools import plot_response_overview
>>> fig = plot_response_overview("data/gv_data/gv_final_edi", station="gv100")

Raw resistivity (not log10) and a “phimin”-style ellipse colouring:

>>> from pycsamt.api.control import PYCSAMT_CONTROL
>>> with PYCSAMT_CONTROL.context(rho__view="linear"):
...     fig = plot_response_overview(
...         "data/gv_data/gv_final_edi", station="gv100",
...         c_by="phimin_deg", clim=(0.0, 90.0), cmap="turbo",
...     )

See also

plot_response_tipper

Per-component grid across many stations.

plot_phase_tensor_strip

Standalone ellipse strip for one station.

plot_induction_arrows

Map-view induction arrows across a survey.

pycsamt.emtools.plot_response_tipper(sites, *, stations=None, components=('xy', 'yx'), tipper_components=('tx', 'ty'), raw=False, force_style=False, control=None, phase_range=None, ncols_groups=3, comp_wspace=0.12, group_hspace=0.32, height_ratios=(2.2, 1.1, 0.75, 0.75), axes=None, figsize_scale=(4.8, 4.6), colors=None, tipper_span_group=False, line_style=None, tipper_line_style=':', label_component_x=True, label_tipper_x=True, title_group_fmt='{station}', title_comp_fmt='Z{component}', shared_group_labels=True, shared_x_label_pad=0.074, x_tick_rotation=0.0, tick_fontsize=7, show_error_bars=True, show_tipper_error_bars=False, show_component_legend=True, show_tipper_legend=True, ylim_rhoa=None, ylim_phase=None, ylim_tipper=(-0.6, 0.6), grid=True, preserve_duplicates=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Plot impedance response panels with station-level tipper rows.

The figure is designed for MT/AMT quality control where impedance and tipper behaviour must be inspected together. Each station is a group: apparent resistivity and phase are shown per impedance component, while compact \(T_x\) and \(T_y\) panels span the full group width.

Parameters:
  • sites (Sites-like) – EDI path, collection of EDI files, Sites object, or iterable accepted by pycsamt.emtools._core.ensure_sites().

  • stations (list of str, optional) – Station names to display. When omitted, all stations are used.

  • components (tuple of str, default ("xy", "yx")) – Impedance tensor components plotted in the resistivity and phase rows. Values are component keys such as "xy", "yx", "xx", or "yy".

  • tipper_components (tuple of str, default ("tx", "ty")) – Tipper components to draw. The usual complete diagnostic uses both "tx" and "ty".

  • raw (bool, default False) – If True, impedance response curves use the package raw-data style unless force_style is also true. Tipper real/imaginary curves keep distinct package component colours for readability.

  • force_style (bool, default False) – Use component colours even when raw=True.

  • control (object, optional) – Plot view control. Defaults to pycsamt.api.control.PYCSAMT_CONTROL.

  • phase_range (tuple of float or None, optional) – Explicit phase display range. If omitted, the active control.phase policy is used.

  • ncols_groups (int, default 3) – Number of station groups per figure row.

  • comp_wspace (float) – Spacing inside and between station groups.

  • group_hspace (float) – Spacing inside and between station groups.

  • height_ratios (tuple of float, default (2.2, 1.1, 0.75, 0.75)) – Relative heights for rho, phase, Tx, and Ty rows. If only one tipper component is requested, the unused extra ratio is ignored.

  • figsize_scale (tuple of float, default (4.8, 4.6)) – Width and height multiplier for each station group row/column.

  • colors (dict, optional) – Optional impedance component colour overrides.

  • tipper_span_group (bool, default False) – If True, each tipper row spans all impedance component columns in a station group. If False, Tx and Ty are repeated under each component column, giving a compact grid such as rho/phase/Tx/Ty for every component.

  • line_style (str or None, optional) – Optional line style override for impedance curves.

  • tipper_line_style (str or None, default ":") – Optional line style override for real and imaginary tipper curves.

  • label_component_x (bool, default True) – Put the active x-axis label under each impedance-component stack and under the bottom tipper row.

  • label_tipper_x (bool, default True) – Put the active x-axis label under each impedance-component stack and under the bottom tipper row.

  • title_group_fmt (str) – Format strings for station and component labels.

  • title_comp_fmt (str) – Format strings for station and component labels.

  • shared_group_labels (bool, default True) – Use group-level rho/phase/tipper labels instead of repeating labels on every small axis.

  • shared_x_label_pad (float, default 0.074) – Figure-coordinate padding used for shared x labels.

  • x_tick_rotation (float, default 0) – Rotation for bottom x tick labels.

  • tick_fontsize (int, default 7) – Tick-label size for compact panels.

  • show_error_bars (bool) – Toggle impedance and tipper error bars independently.

  • show_tipper_error_bars (bool) – Toggle impedance and tipper error bars independently.

  • show_component_legend (bool) – Toggle global legends.

  • show_tipper_legend (bool) – Toggle global legends.

  • ylim_rhoa (tuple or None) – Optional y-axis limits.

  • ylim_phase (tuple or None) – Optional y-axis limits.

  • ylim_tipper (tuple or None) – Optional y-axis limits.

  • grid (bool, default True) – Draw light panel grids.

  • preserve_duplicates (bool, default False) – Preserve repeated in-memory EDI objects instead of normalizing them through ensure_sites(). This is useful for diagnostic demos or before/after comparisons where two display stations intentionally share the same source station name.

  • recursive (bool) – Forwarded to pycsamt.emtools._core.ensure_sites().

  • on_dup (str) – Forwarded to pycsamt.emtools._core.ensure_sites().

  • strict (bool) – Forwarded to pycsamt.emtools._core.ensure_sites().

  • verbose (int) – Forwarded to pycsamt.emtools._core.ensure_sites().

Returns:

The assembled response/tipper figure.

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.plot import plot_response_tipper
>>> fig = plot_response_tipper(
...     "data/AMT/TIPPER/HBH03_IMP.edi",
...     components=("xy", "yx"),
... )
pycsamt.emtools.plot_raw_sites_1d(sites, *, stations=None, components=('xx', 'xy', 'yx', 'yy'), raw=True, force_style=False, control=None, phase_range=None, ncols_groups=3, comp_wspace=0.12, group_hspace=0.25, height_ratio=(2, 1), axes=None, figsize_scale=(4.2, 3.1), colors=None, title_group_fmt='{station}', title_comp_fmt='Z{component}', shared_group_labels=True, label_mode=None, shared_x_label_pad=0.078, x_tick_rotation=None, tick_fontsize=7, show_error_bars=True, show_component_legend=True, legend_y=-0.14, ylim_rhoa=None, ylim_phase=None, grid=True, recursive=True, on_dup='replace', strict=False, verbose=0)#

Plot raw or processed 1-D rho/phase panels by station.

The layout mirrors diagnostic raw-data figures used in AMT/MT workflows: every selected station is a group, every component is a column, and each component column contains apparent resistivity above phase. When raw=True the raw-data style from pycsamt.api.style.PYCSAMT_STYLE.raw is used automatically, producing black diagnostic traces unless force_style=True or explicit colors are provided.

Parameters:
pycsamt.emtools.plot_sites_panels(sites, *, components=('xy', 'yx'), quantity='rhoa', x_axis='period', phase_range=(-90.0, 90.0), stations=None, ncols=6, wspace=0.2, hspace=0.08, height_ratio=(2, 1), axes=None, figsize_scale=(2.6, 2.6), colors=None, marker=<object object>, ms=<object object>, lw=<object object>, ls=<object object>, show_error_bars=True, show_legend=False, title_fmt='{station}', ylim_rhoa=None, ylim_phase=None, grid=True, preserve_duplicates=False, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.plot_sites_compare(sites, new_sites=None, *, components=('xy', 'yx'), quantity='rhoa', x_axis='period', phase_range=(-90.0, 90.0), stations=None, ncols_groups=3, group_gap=0.35, pair_wspace=0.06, hspace=0.06, height_ratio=(2, 1), axes=None, figsize_scale=(3.0, 3.0), colors=None, marker=<object object>, ms=<object object>, lw=<object object>, ls=<object object>, show_error_bars=True, labels=('raw', 'after'), title_group_fmt='{station}', title_col_fmt='{tag}', show_legend=False, ylim_rhoa=None, ylim_phase=None, grid=True, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.plot_sites_fit_grid(sites, pred_sites, *, components=('xx', 'xy', 'yx', 'yy'), quantity='rhoa', x_axis='period', phase_range=(-180.0, 180.0), stations=None, ncols_groups=2, comp_wspace=0.1, group_hspace=0.18, height_ratio=(2, 1), axes=None, figsize_scale=(4.0, 3.0), colors_meas=None, color_fit_te=<object object>, color_fit_tm=<object object>, marker=<object object>, ms=<object object>, lw=<object object>, ls_meas=<object object>, lw_fit=2.0, ls_fit='-', show_error_bars=True, show_mode_legend=True, title_group_fmt='{station}', ylim_rhoa=None, ylim_phase=None, grid=True, recursive=True, on_dup='replace', strict=False, verbose=0)#
Parameters:
pycsamt.emtools.lcurve_table(misfit, rough, lam=None, *, sort='auto', method='curvature', smooth=3, skip=1, return_dict=False)#
Parameters:
pycsamt.emtools.plot_lcurve(misfit, rough, lam=None, *, labels=None, colors=None, cmap='viridis', marker='o', ms=3.0, lw=1.4, alpha=0.9, show_points=True, show_path=True, arrow_every=0, method='curvature', smooth=3, skip=1, show_corner=True, corner_style=None, show_inset=True, inset_loc=(0.62, 0.12, 0.32, 0.32), label_every=0, label_prefix='', label_fontsize=7.0, target_misfit=None, target_label='target misfit', figsize=(6.0, 4.6), ax=None)#
Parameters:
class pycsamt.emtools.LCurveData(misfit, rough, lam, iterations, backend, source)#

Bases: object

Misfit/roughness/lambda sweep extracted from a real inversion log.

misfit[i], rough[i], lam[i], and iterations[i] all describe the same iteration, so the arrays can be passed straight into lcurve_table() or plot_lcurve() – which is exactly what table() and plot() do.

Parameters:
misfit: ndarray#
rough: ndarray#
lam: ndarray#
iterations: ndarray#
backend: str#
source: Path#
table(**kwargs)#

Call lcurve_table() on this sweep.

Parameters:

kwargs (Any)

plot(**kwargs)#

Call plot_lcurve() on this sweep.

Parameters:

kwargs (Any)

pycsamt.emtools.lcurve_from_occam2d(path, **kwargs)#

Build an LCurveData sweep from an Occam2D convergence log.

Reads the per-iteration LogFile.logfile written by the Occam2D Fortran binary with pycsamt.models.occam2d.log.OccamLog, which already parses accepted RMS misfit, roughness, and Lagrange multiplier for every completed iteration. Rows with a non-finite or non-positive misfit or roughness (for example a final iteration that stopped on “Convergence problems” before writing ROUGHNESS IS) are dropped, since lcurve_table() requires strictly positive values for its log-log scoring.

Parameters:
  • path (path-like) – Path to an Occam2D log file, typically LogFile.logfile.

  • **kwargs – Forwarded to OccamLog.read (for example verbose).

Returns:

rough holds Occam’s reported ROUGHNESS IS values, misfit the accepted RMS, and lam the accepted Lagrange multiplier (linear scale, not log10).

Return type:

LCurveData

Examples

>>> from pycsamt.emtools.lcurve import lcurve_from_occam2d
>>> sweep = lcurve_from_occam2d("data/occam2D/LogFile.logfile")
>>> sweep.backend
'occam2d'
>>> ax = sweep.plot()
pycsamt.emtools.lcurve_from_modem(path, **kwargs)#

Build an LCurveData sweep from a ModEM NLCG log.

Reads the per-iteration Completed NLCG iteration blocks written by ModEM with pycsamt.models.modem.log.ModEmLog. ModEM reports the model-regularization term directly as m2 rather than a roughness norm, so rough here is m2 – the same quantity \(\lambda\,\Phi_m(m)\) trades off against data misfit in ModEM’s objective function, and it plays the identical x-axis role on an L-curve.

Parameters:
  • path (path-like) – Path to a ModEM log file, typically Modular_NLCG.log.

  • **kwargs – Forwarded to ModEmLog.read (for example verbose).

Returns:

rough holds ModEM’s m2 model-norm term, misfit the reported rms, and lam the damping parameter lambda.

Return type:

LCurveData

Examples

>>> from pycsamt.emtools.lcurve import lcurve_from_modem
>>> sweep = lcurve_from_modem(
...     "data/modem/willy_27freq_watex_line02_sample/Modular_NLCG.log"
... )
>>> sweep.backend
'modem'
>>> ax = sweep.plot()
pycsamt.emtools.lcurve_from_mare2dem(path, **kwargs)#

Build an LCurveData sweep from a MARE2DEM convergence log.

Reads the per-iteration ** Iteration N ** blocks written by MARE2DEM with pycsamt.models.mare2dem.log.Mare2DEMLog, which already parses Model Misfit, Roughness, and Optimal Mu for every completed iteration. MARE2DEM reports Optimal Mu as \(\log_{10}\mu\), so it is converted back to linear scale here to match the convention used by the other two adapters.

Parameters:
  • path (path-like) – Path to a MARE2DEM log file, typically *.logfile.

  • **kwargs – Accepted for interface symmetry with the other adapters; Mare2DEMLog takes no extra keyword arguments.

Returns:

rough holds MARE2DEM’s reported roughness, misfit the model misfit, and lam the optimal mu converted to linear scale (10 ** log10_mu).

Return type:

LCurveData

Examples

>>> from pycsamt.emtools.lcurve import lcurve_from_mare2dem
>>> sweep = lcurve_from_mare2dem(
...     "data/mare2dem/demo_mt_inversion/demo.logfile"
... )
>>> sweep.backend
'mare2dem'
>>> ax = sweep.plot()
pycsamt.emtools.coherence_matrix(sp)#

Compute the inter-channel coherence matrix.

The squared coherence between channels i and j is:

\[\gamma^2_{ij}(f) = \frac{|S_{ij}(f)|^2}{S_{ii}(f)\,S_{jj}(f)}\]
Parameters:

sp (Spectra) – Cross-spectra container.

Returns:

coh – Real-valued coherence matrix per frequency, values in [0, 1].

Return type:

ndarray, shape (n_freq, n_chan, n_chan)

pycsamt.emtools.psd_table(sp_input, *, normalize=False, api=None)#

Power spectral density per channel as a tidy DataFrame.

Parameters:
  • sp_input (Spectra or list/dict of Spectra) – One or more cross-spectra containers.

  • normalize (bool) – If True, normalise each channel’s PSD by its maximum value.

  • api (bool | None)

Returns:

Columns: station, freq, period, channel, psd.

Return type:

pd.DataFrame

pycsamt.emtools.coherence_table(sp_input, *, pairs=None, api=None)#

Inter-channel squared coherence as a tidy DataFrame.

Parameters:
  • sp_input (Spectra or list/dict of Spectra)

  • pairs (list of (i, j), optional) – Channel index pairs. Default = all upper-triangle pairs.

  • api (bool | None)

Returns:

Columns: station, freq, period, ch_i, ch_j, pair, coherence.

Return type:

pd.DataFrame

pycsamt.emtools.snr_table(sp_input, *, pairs=None, api=None)#

Signal-to-noise ratio estimated from squared coherence.

Uses the coherence-based estimator:

\[\text{SNR} = \frac{\gamma^2}{1 - \gamma^2}\]

with the dB version SNR_dB = 10 log₁₀(SNR).

Parameters:
Returns:

Columns: station, freq, period, pair, coherence, snr, snr_db.

Return type:

pd.DataFrame

pycsamt.emtools.band_select(sp, f_min, f_max)#

Return a new Spectra restricted to the frequency band [f_min, f_max] Hz.

Parameters:
  • sp (Spectra)

  • f_min (float) – Frequency limits in Hz (inclusive).

  • f_max (float) – Frequency limits in Hz (inclusive).

Returns:

A shallow copy with arrays sliced to the band.

Return type:

Spectra

pycsamt.emtools.mask_low_coherence(sp, *, pairs=None, threshold=0.5, require_all=False)#

Boolean mask of frequencies with sufficient coherence.

Parameters:
  • sp (Spectra)

  • pairs (list of (i, j), optional) – Pairs to evaluate. Default = all upper-triangle pairs.

  • threshold (float) – Minimum coherence required. Default 0.5.

  • require_all (bool) – If True, all requested pairs must exceed threshold. If False (default), at least one pair suffices.

Returns:

maskTrue where coherence is acceptable.

Return type:

ndarray of bool, shape (n_freq,)

pycsamt.emtools.spectra_summary(sp, *, api=None)#

Compact per-frequency summary table.

Columns: freq, period, bw, avgt, rotspec, plus the diagonal PSD and mean off-diagonal coherence for each channel.

Return type:

pd.DataFrame

Parameters:
pycsamt.emtools.plot_psd(sp, *, channels=None, log_psd=True, lw=<object object>, alpha=<object object>, title='', figsize=(9, 5), ax=None)#

Plot the power spectral density per channel.

The x-axis follows PYCSAMT_CONTROL (log₁₀ period by default). Colours come from multiline.

Parameters:
  • sp (Spectra)

  • channels (sequence of int, optional) – Channel indices to plot. Default = all channels.

  • log_psd (bool) – Display log₁₀(PSD) when True.

  • lw (float) – Line width. Default: PYCSAMT_STYLE.multiline.lw.

  • alpha (float) – Line alpha. Default: PYCSAMT_STYLE.multiline.alpha.

  • title (str)

  • figsize ((float, float))

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.emtools.plot_coherence(sp, *, pairs=None, threshold=0.5, show_threshold=True, lw=<object object>, alpha=<object object>, title='', axes=None, figsize=None)#

Plot squared coherence for selected channel pairs.

Each pair gets its own sub-axis arranged in a grid. A dashed horizontal line marks threshold.

Parameters:
  • sp (Spectra)

  • pairs (list of (i, j), optional) – Default = all upper-triangle pairs.

  • threshold (float) – Quality threshold drawn as a dashed line. Default 0.5.

  • show_threshold (bool)

  • lw (float) – Style defaults from PYCSAMT_STYLE.multiline.

  • alpha (float) – Style defaults from PYCSAMT_STYLE.multiline.

  • title (str)

  • figsize ((float, float) or None) – Auto-computed from the number of pairs when None.

Returns:

axes

Return type:

ndarray of Axes, shape (n_pairs,)

pycsamt.emtools.plot_spectra_matrix(sp, *, freq_idx=0, quantity='abs', cmap=<object object>, log_scale=True, title='', ax=None, figsize=(7, 6))#

Visualise the full cross-spectral density matrix at one frequency.

The matrix is drawn as a colour image. The diagonal shows auto- spectra (PSD); the upper and lower triangles show the magnitude (or real/imaginary part) of the cross-spectra.

Parameters:
  • sp (Spectra)

  • freq_idx (int) – Index into sp.freq for the frequency slice.

  • quantity ({'abs', 'real', 'imag', 'phase'}) – Quantity to colour.

  • cmap (str or _UNSET) – Colour map. Defaults to "viridis" (abs) or "RdBu_r" (real/imag/phase).

  • log_scale (bool) – Apply log₁₀ to the absolute value when quantity="abs".

  • title (str)

  • figsize ((float, float))

Returns:

fig

Return type:

Figure

pycsamt.emtools.plot_z_from_spectra(sp, *, e_labels=('EX', 'EY'), h_labels=('HX', 'HY'), ridge=None, estimate_error=False, show_error=True, title='', axes=None, figsize=(10, 5))#

Plot apparent resistivity and phase recovered from spectra.

Calls to_Z() internally and renders the result with the standard MT component styling from PYCSAMT_STYLE.

Parameters:
  • sp (Spectra)

  • e_labels (tuple of str) – Channel type labels used for the E and H blocks in to_Z().

  • h_labels (tuple of str) – Channel type labels used for the E and H blocks in to_Z().

  • ridge (float or None) – Tikhonov regularization for S_HH inversion.

  • estimate_error (bool) – Estimate 1-σ errors in to_Z(). Default False (avoids DoF warnings when metadata is incomplete).

  • show_error (bool) – Shade error envelope when errors are available.

  • title (str)

  • figsize ((float, float))

Returns:

fig

Return type:

Figure

pycsamt.emtools.plot_tipper_from_spectra(sp, *, h_labels=('HX', 'HY'), ridge=None, estimate_error=False, show_error=True, title='', axes=None, figsize=(10, 5))#

Plot the induction tipper magnitude and phase from spectra.

Displays the real and imaginary parts of T_x and T_y as well as their magnitudes on a two-panel figure (amplitude | phase).

Parameters:
Returns:

axes[ax_amp, ax_phase]

Return type:

ndarray of Axes, shape (2,)

pycsamt.emtools.plot_psd_section(sp_input, *, channel=0, log_psd=True, cmap='viridis', vmin=None, vmax=None, section='pseudosection', title='', figsize=None, ax=None)#

Pseudo-section of PSD across stations (station × period).

Interpolates all Spectra objects to a common log-spaced frequency grid before assembling the 2-D colour map.

Parameters:
  • sp_input (Spectra or list/dict of Spectra)

  • channel (int) – Channel index to display. Default 0.

  • log_psd (bool) – Colour log₁₀(PSD) when True.

  • cmap (str)

  • vmin (float or None)

  • vmax (float or None)

  • section (str or SectionStyle) – Layout preset from PYCSAMT_SECTION.

  • title (str)

  • figsize ((float, float) or None)

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.emtools.plot_coherence_section(sp_input, *, pair=None, threshold=0.5, show_threshold=True, cmap='RdYlGn', section='pseudosection', title='', figsize=None, ax=None)#

Pseudo-section of coherence across stations (station × period).

Parameters:
  • sp_input (Spectra or list/dict of Spectra)

  • pair ((int, int) or None) – Single channel pair (i, j) to display. When None the mean over all upper-triangle pairs is shown.

  • threshold (float) – Value shown by the shared colorbar. Default 0.5.

  • show_threshold (bool) – Add a contour at threshold when True.

  • cmap (str) – Default "RdYlGn" — red=low, green=high coherence.

  • section (str or SectionStyle)

  • title (str)

  • figsize ((float, float) or None)

  • ax (Axes or None)

Returns:

ax

Return type:

Axes

pycsamt.emtools.check_em_kind(objs, /)#

Validate a collection of EM objects and return their common kind.

Ensures that all elements in objs are instances of either Edi or Z, but not a mix of both. Returns the common kind as "EDI" or "Z". Raises if the collection is empty, contains non-EM objects, or mixes kinds.

Parameters:

objs (iterable) – Iterable of objects expected to be all Edi or all Z. Strings and bytes are not valid inputs.

Returns:

"EDI" if all objects are EDI instances, else "Z" if all are Z instances.

Return type:

str

Raises:
  • TypeError – If objs is not an iterable, or is a string/bytes, or is empty.

  • EMError – If an element is neither Edi nor Z, or if the iterable mixes Edi and Z.

Notes

Uses is_instance_extended() to be robust to class reloads and alternate import paths.

Examples

>>> from pycsamt.utils.em import check_em_kind
>>> # assuming `eds` is a list of Edi instances
>>> check_em_kind(eds)
'EDI'
>>> # assuming `zs` is a list of Z instances
>>> check_em_kind(zs)
'Z'
pycsamt.emtools.extract_z_list(objs, /)#

Return a list of Z objects from EDI or Z inputs.

If objs is a collection of Edi, each element’s .Z attribute is extracted. If objs is already a collection of Z, it is returned as a plain list.

Parameters:

objs (iterable) – Iterable of EM objects. Must be all Edi or all Z. See check_em_kind().

Returns:

List of Z instances.

Return type:

list

Raises:
  • TypeError – If objs is not an iterable or is empty.

  • EMError – If objs mixes Edi and Z, contains non-EM objects, or an Edi is missing the .Z attribute.

Notes

Uses check_em_kind() to validate homogeneity.

Examples

>>> zs = extract_z_list(list_of_edi)  # from EDI inputs
>>> zs = extract_z_list(list_of_z)  # already Z
pycsamt.emtools.parse_tensor(out='resxy', *, tensor=None, component=None, kind='complex', **kws)#

Parse and validate a tensor request, returning name and component.

This helper normalizes shorthand like 'resxy' or explicit pairs like tensor='z', component='xy' and validates the desired numeric kind (e.g., complex, real, imag, modulus).

Parameters:
  • out (str, default='resxy') – Compact token specifying the tensor and component, e.g. 'resxy', 'zxy', 'phaseyx', or a frequency request such as 'freq'. When both tensor and component are given, they override out.

  • tensor (str, optional) – Tensor name or alias. Accepted values include: 'z', 'tensor', 'res', 'rho', 'rhoa', 'phase', 'phs', 'freq', 'frequency'.

  • component (str, optional) – EM component among 'xx', 'xy', 'yx', 'yy'. Required for 'z', 'resistivity' and 'phase'.

  • kind ({'complex','real','imag','modulus'}, default='complex') – Numeric form of the tensor to be later extracted. Aliases are accepted, e.g. 're'``→’real’, ``'im'``→’imag’, ``'abs'/'mod'``→’modulus’, ``'reel'``→’real’``.

  • **kws – Extra keywords ignored here. Kept for API symmetry with callers that also manage frequency expansion.

Returns:

(name, comp) – Normalized tensor name and its component. The name is one of 'z', 'resistivity', 'phase', '_freq', or includes the '_err' suffix for error arrays where applicable. comp is 'xx', 'xy', 'yx', 'yy' or None for frequency.

Return type:

tuple of (str, Optional[str])

Raises:
  • EMError – If only one of tensor or component is provided.

  • ValueError – If the parsed tokens are invalid, the component is missing for a tensor that requires it, or kind is unknown.

Examples

>>> parse_tensor("zxy")
('z', 'xy')
>>> parse_tensor(tensor="res", component="yx")
('resistivity', 'yx')
>>> parse_tensor("freq")
('_freq', None)
>>> parse_tensor("resx")
Traceback (most recent call last):
    ...
ValueError: 'Resistivity' component is missing...
pycsamt.emtools.compute_qc(z_or_edis_obj_list, /, tol=0.5, *, interpolate_freq=False, return_freq=False, tensor='res', return_data=False, to_log10=False, return_qco=False)#

Assess data quality across a collection of EDI/Z objects.

Computes a global completeness ratio \(1 - \#NaN / (N_{freq} N_{sta})\) from a 2-D tensor (freq × station). Frequencies whose per-row missing-data fraction exceeds tol are dropped. Optionally interpolate the retained frequencies and/or return a structured summary object.

Parameters:
  • z_or_edis_obj_list (list of Edi or Z) – Homogeneous collection of EM objects.

  • tol (float, default=0.5) – Tolerance threshold in [0, 1]. A frequency row is considered invalid and dropped when its fraction of missing values exceeds tol.

  • interpolate_freq (bool, default=False) – If True, interpolate the retained frequencies on a log-spaced grid spanning [min, max] with the same count.

  • return_freq (bool, default=False) – If True, return the retained (possibly interpolated) frequency vector.

  • tensor ({'z','res','rho','rhoa','phase','phs'}, default='res') – Tensor family used for QC. The function first attempts the TE component ('xy'); on failure, it falls back to TM ('yx').

  • return_data (bool, default=False) – If True, also return the subset of the tensor data corresponding to retained frequencies.

  • to_log10 (bool, default=False) – If True, return log10 of the retained frequencies. Applied after interpolation if interpolate_freq is set.

  • return_qco (bool, default=False) –

    If True, return a Bunch with the following attributes:

    • rate_: global completeness ratio

    • component_: selected component, 'xy' or 'yx'

    • mode_: EM mode, 'TE' or 'TM'

    • freqs_: retained (optionally interpolated) frequencies

    • invalid_freqs_: frequencies dropped by the QC

    • data_: tensor data at retained frequencies

    Setting this flag forces return_freq=True and return_data=True.

Returns:

Depending on the flags. rate is in [0, 1].

Return type:

(rate,) or (rate, freqs) or (rate, freqs, data) or Bunch

Notes

The 2-D tensor has shape (n_freq, n_stations). The per-row missing-data fraction is nan_count / n_stations.

Examples

>>> (rate,) = compute_qc(data)
>>> rate, freqs = compute_qc(data, return_freq=True)
>>> rep = compute_qc(data, return_qco=True)
>>> rep.rate_, rep.component_, rep.freqs_.shape
(0.75, 'xy', (56,))
pycsamt.emtools.full_freq(z_or_edis_obj_list, /, to_log10=False)#

Return the reference (clean) frequency grid for a collection.

The full frequency grid is taken from the site that contains the largest number of frequency samples (i.e., the most complete set). This is commonly used as the survey reference grid to which per-site tensors are aligned.

Parameters:
  • z_or_edis_obj_list (list of Edi or Z) – Homogeneous collection of Edi or Z objects.

  • to_log10 (bool, default=False) – If True, return log10(freqs). Frequencies must be strictly positive.

Returns:

The reference frequency vector.

Return type:

ndarray of shape (n_freq,)

Raises:
  • TypeError – If the input is empty or not iterable.

  • EMError – If the collection mixes Edi and Z, or any element is missing a frequency attribute.

  • ValueError – If to_log10=True and any frequency is non-positive.

Notes

For each object, the function looks for .Z._freq / .Z.freq (when Edi) or ._freq / .freq (when Z), using the first available attribute.

Examples

>>> f = full_freq(edi_data)
>>> f.shape
(56,)
>>> flog = full_freq(edi_data, to_log10=True)
pycsamt.emtools.tensor2d(z_or_edis_obj_list, /, tensor='z', component='xy', kind='modulus', return_freqs=False, freqs=None, **kws)#

Build a 2-D matrix (freq × station) from a tensor collection.

Converts a collection of Edi or Z objects into a 2-D array where rows are frequencies and columns are stations. Missing per-site frequencies are filled with NaN (no interpolation).

Parameters:
  • z_or_edis_obj_list (list of Edi or Z) – Collection of EM objects. All items must be Edi or all Z.

  • tensor (str, default='z') – Tensor name or alias. Examples include 'z', 'res'/'rho'/'rhoa', 'phase'/'phs'. Error arrays are supported (e.g., 'resistivity_err').

  • component ({'xx','xy','yx','yy'}, default='xy') – Component to extract for non-frequency tensors.

  • kind ({'modulus','real','imag','complex'}, default='modulus') – Numeric form for complex Z tensors. Ignored for real arrays (e.g., resistivity/phase).

  • return_freqs (bool, default=False) – If True, also return the reference frequency vector.

  • freqs (array-like, optional) – Precomputed reference frequency grid. If given, it is used directly and get_full_frequency is not called. The grid should include (or supersede) each site’s frequencies; any missing values will be filled with NaN during alignment.

  • **kws – Extra keywords forwarded to the frequency extractor when freqs is not provided.

Returns:

  • mat2d (ndarray (n_freq, n_stations)) – 2-D matrix of the requested tensor component.

  • (mat2d, freqs) (tuple) – If return_freqs=True, also returns the frequency vector.

Raises:
  • EMError – If inputs are missing, mixed, or illegal (e.g., frequency requested as the primary tensor here).

  • ValueError – If an unknown kind is used for complex Z.

Notes

Each item in the input provides a 3-D tensor of shape (n_freq, 2, 2). Index positions map as:

xx -> (0, 0)   xy -> (0, 1)
yx -> (1, 0)   yy -> (1, 1)

Examples

>>> phase_yx = tensor2d(data, tensor="phase", component="yx")
>>> phase_yx.shape
(56, 7)
pycsamt.emtools.align_tensor(ref_freq, site_freq, z, fill_value=nan)#

Align a tensor component to a reference frequency grid.

The reference frequency grid (ref_freq) is assumed to be the complete set of clean frequencies for the survey. Site-level measurements (site_freq) may be missing some frequencies due to interferences. This function maps the provided tensor values (z) to the reference grid and fills gaps with fill_value.

Parameters:
  • ref_freq (ArrayLike) – Reference frequency grid collected in the field. It should contain all survey frequencies.

  • site_freq (ArrayLike) – Frequencies measured at a site. All values must be present in ref_freq (i.e., no out-of-grid frequencies).

  • z (ndarray of complex) – Tensor component values measured at site_freq. Typically the real or imaginary part for one of xx, xy, yx, or yy. Length must match site_freq.

  • fill_value (float, default=nan) – Value used to fill positions in the reference grid where the site tensor is missing.

Returns:

Array aligned to ref_freq with gaps filled by fill_value. The dtype matches z.dtype.

Return type:

ndarray of complex

Raises:
  • EMError – If the number of mappable positions inferred from site_freq does not match the length of z.

  • ValueError – If input shapes are inconsistent.

Notes

Internally uses ismissing() to identify positions in ref_freq that correspond to site_freq. The function does not interpolate values; it only aligns and fills gaps.

Examples

>>> ref_freq = np.linspace(7e7, 1.0, 20)
>>> site_freq = np.hstack([ref_freq[:7], ref_freq[12:]])
>>> z = np.random.randn(len(site_freq)) + 1j * np.random.randn(
...     len(site_freq)
... )
>>> z_aligned = align_tensor(ref_freq, site_freq, z)
>>> np.isnan(z_aligned).sum()  # gaps inserted
5
pycsamt.emtools.export_edis(edi_objs, new_z, savepath=None, **kws)#

Export new EDI files from a batch of EDI objects and Z tensors.

Applies updated impedance tensors to each input EDI object and writes new EDI files. This is typically used after applying corrections or replacements to the impedance tensor.

Parameters:
  • edi_objs (list of Edi) – Collection of EDI objects. All elements must be instances of Edi (no Z objects are allowed here).

  • new_z (list of ndarray (nfreq, 2, 2), complex) – Collection of impedance tensors matching edi_objs one-to- one. Each tensor is a 3-D complex array with shape (n_freq, 2, 2).

  • savepath (str, optional) – Directory to write the new EDI files. If None, the EDI writer decides (often the current directory).

  • **kws – Extra arguments forwarded to the EDI writer method Edi.write_new_edifile (e.g., naming options).

Returns:

Files are written as a side effect. The underlying writer may return paths, but this function does not collect them.

Return type:

None

Raises:
  • EdIDataError – If edi_objs does not contain exclusively EDI objects.

  • ValueError – If the lengths of edi_objs and new_z differ.

See also

exportedi

Helper for exporting a single EDI (if available in the API).

Examples

>>> # edi_objs: list[Edi], z_list: list[np.ndarray]
>>> export_edis(edi_objs, z_list, savepath="out/")
pycsamt.emtools.plot_confidence(z_or_edis_obj_list, /, tensor='res', view='1d', drop_outliers=True, distance=None, c_line=False, view_ci=True, figsize=(6.0, 2.0), fontsize=4.0, dpi=300, top_label='Stations', rotate_xlabel=90.0, fbtw=True, savefig=None, **plot_kws)#

Plot confidence diagnostics from tensor errors for EM data.

The default tensor for confidence evaluation is the resistivity error at TE mode ('xy'). This plot helps decide which frequencies (and stations) are reliable, recoverable, or should be discarded before further processing.

Three confidence levels are highlighted:

  • High: \(conf \ge 0.95\)

  • Soft: \(0.5 \le conf < 0.95\) (often recoverable)

  • Bad: \(conf < 0.5\) (usually discard)

Parameters:
  • z_or_edis_obj_list (list of EDI or Z) – Collection of Edi or Z objects.

  • tensor (str, default='res') – Tensor selector. Accepted aliases include resistivity ('res', 'rho', 'rhoa'), phase ('phase', 'phs'), or 'z'. Error arrays are used for resistivity/phase automatically.

  • view ({'1d', '2d'}, default='1d') – Plot as a 1-D profile (by station) or as a 2-D map (frequency vs. station).

  • drop_outliers (bool, default=True) – If True, suppress outliers in the error tensor before plotting (filled with nan).

  • distance (float, optional) – Inter-station distance. Used to scale the x-axis in 1-D view. If None, a unit spacing of 1 is used.

  • c_line (bool, default=False) – If True and view='2d', overlay the confidence line.

  • view_ci (bool, default=True) – If True, show markers indicating confidence classes.

  • figsize (tuple of float, default=(6.0, 2.0)) – Matplotlib figure size.

  • fontsize (float, default=4.0) – Base font size used for labels and ticks.

  • dpi (int, default=300) – Figure resolution in dots per inch.

  • top_label (str, default='Stations') – Title used for the top x-axis (station labels).

  • rotate_xlabel (float, default=90.0) – Rotation angle for station labels on the top x-axis.

  • fbtw (bool, default=True) – In 1-D view, fill between the curve and confidence bands.

  • savefig (str, optional) – Path to save the figure. If None, the figure is shown.

  • plot_kws (Any)

Returns:

The Matplotlib Axes with the plotted content.

Return type:

matplotlib.axes.Axes

Notes

Internally, the function computes an error tensor for the chosen tensor (resistivity/phase use error arrays). Confidence is aggregated across stations and displayed either as a 1-D line or a 2-D image with categorical markers.

Examples

>>> ax = plot_confidence(
...     emobj.ediObjs_, distance=20, view="2d", figsize=(6, 2)
... )
>>> ax = plot_confidence(
...     emobj.ediObjs_, distance=20, view="1d", figsize=(6, 3), fontsize=5
... )
pycsamt.emtools.plot_strike(list_of_edis, /, kind=2, period_tolerance=0.05, text_pad=1.65, rot_z=0.0, **kws)#

Plot strike angles from invariants and phase tensor as rose/polar diagrams.

Accepts a single .edi file path, a directory containing .edi files, or an iterable of .edi paths. Files are validated before plotting. Output is produced by mtpy.imaging.plotstrike. PlotStrike, with console output muted.

Parameters:
  • list_of_edis (str or iterable of str) – Path to an .edi file, a directory of .edi files, or a list/tuple of .edi file paths.

  • kind ({1, 2}, default=2) –

    Plot style for PlotStrike: - 1: plot individual decades in one plot. - 2: plot all period ranges in a polar diagram for each

    strike estimate.

  • period_tolerance (float, default=0.05) – Tolerance to match periods across different EDI files.

  • text_pad (float, default=1.65) – Padding of the angle label at the bottom of each polar diagram.

  • rot_z (float, default=0.0) – Clockwise rotation (degrees) applied to the tensor.

  • **kws – Extra keyword arguments forwarded to PlotStrike (e.g., plot_range, plot_tipper, fold, plot_orientation, color settings, etc.).

Returns:

Plots are created as a side effect.

Return type:

None

Notes

  • Files are validated with IsEdi._assert_edi.

  • Third-party output is muted via nullify_output().

Examples

>>> plot_strike("/path/to/edis_dir")
>>> plot_strike("/path/to/site.edi", kind=1)
>>> plot_strike(["a.edi", "b.edi"], rot_z=10.0)
pycsamt.emtools.plot_tensors(z_or_edis_obj_list, /, station='S00', zplot=False, show_error_bars=False, **kwargs)#

Plot tensors for one station (compat wrapper).

This is a compatibility wrapper around plot_station_tensors(). It preserves the legacy API—station, zplot (for impedance vs. app. resistivity/ phase), and show_error_bars—and forwards any additional styling options to the underlying plotter.

Parameters:
  • z_or_edis_obj_list (list of Edi or Z) – Collection of EM objects containing either Edi (from which the embedded Z is extracted) or Z directly.

  • station (int or str, default='S00') – Target station index or label. Strings such as 'S00' are parsed to 0-based indices.

  • zplot (bool, default=False) – If True, plot real/imag parts of the impedance tensor (Z). If False, plot apparent resistivity and phase.

  • show_error_bars (bool, default=False) – Whether to display error bars.

  • **kwargs – Additional plotting options passed to plot_station_tensors() (e.g., color_mode, markers, line widths, legend style, font sizes, etc.).

Returns:

The Z object for the selected station.

Return type:

Z

Notes

The heavy lifting (layout, filtering, styling) is handled by plot_station_tensors(). This wrapper exists to maintain source compatibility with v1.x code.

Examples

>>> z = plot_tensors(edi_list, station="S03", zplot=True)
>>> z = plot_tensors(edi_list, station=0, show_error_bars=True)
pycsamt.emtools.plot_station_tensors(z_or_edis_obj_list, /, station='S00', *, plot_z=False, show_error_bars=True, **kwargs)#

Plot tensors for one station: resistivity/phase or Z real/imag.

By default, the function plots apparent resistivity and phase panels for the four components (xx, xy, yx, yy). If plot_z is True, it plots the real and imaginary parts of the impedance tensor instead. Error bars can be displayed or hidden.

Parameters:
  • z_or_edis_obj_list (list of Edi or Z) – Collection containing either Edi or Z objects. When EDI objects are provided, the embedded Z is extracted.

  • station (int or str, default='S00') – Target station index or label. Strings like 'S00' are parsed to an integer index (0-based).

  • plot_z (bool, default=False) – If True, plot real/imag parts of Z. Otherwise, plot apparent resistivity and phase.

  • show_error_bars (bool, default=True) – Whether to include error bars on each panel.

  • **kwargs

    Plot customization such as: - fig_size (tuple, default=(6, 6)) - dpi (int, default=300) - subplot_wspace (float, default=0.3) - phase_limits (tuple[min,max] in deg) - freq_limits (tuple[min,max] in Hz) - period_limits (tuple[min,max] in s) - mod_base (int, default=360) - style: color_mode (‘color’|’bw’), markers, line widths,

    legend style, font sizes, etc.

Returns:

The Z object for the selected station.

Return type:

Z

Notes

The function expects the station’s Z object to provide (or compute on demand) the following arrays with shape (n_freq, 2, 2): - resistivity, resistivity_err - phase, phase_err - z (complex), z_err (complex) and a frequency vector _freq (Hz).

Examples

>>> z = plot_station_tensors(edi_list, station=3)
>>> z = plot_station_tensors(
...     edi_list,
...     station="S00",
...     plot_z=True,
...     show_error_bars=False,
...     color_mode="bw",
... )
pycsamt.emtools.wrap_phase(phase, value_range=None, mod_base=360)#

Wrap phase values to a target range with a given periodic base.

By default, phases are wrapped into the interval [0, mod_base). When value_range is provided, the wrapped phases are linearly remapped from [0, mod_base) to the desired interval.

Parameters:
  • phase (array-like) – Phase values (any shape), possibly negative or outside the target range.

  • value_range ({None, scalar, (min, max)}, optional) – Target interval for the output. - None: return values in [0, mod_base). - scalar: treated as (0, scalar). - tuple/list (min, max): custom interval; min < max.

  • mod_base ({90, 180, 270, 360}, default=360) – Periodicity used for wrapping (degrees).

Returns:

Wrapped (and optionally remapped) phase values with the same shape as the input and dtype float.

Return type:

np.ndarray

Notes

  • For the common symmetric range (-180, 180], set value_range=(-180, 180) with mod_base=360.

  • The remapping is affine: values in [0, mod_base) are scaled to [min, max).

Examples

>>> x = np.array([-540, -180, 0, 180, 360, 540])
>>> # Default: [0, 360)
>>> wrap_phase(x, mod_base=360)
array([180., 180.,   0., 180.,   0., 180.])
>>> # Symmetric range (-180, 180)
>>> wrap_phase(x, value_range=(-180, 180), mod_base=360)
array([-180., -180.,    0.,  180.,    0.,  180.])
>>> # Custom half-range [0, 180)
>>> wrap_phase(x, value_range=180, mod_base=360)
array([90., 90.,  0., 90.,  0., 90.])
pycsamt.emtools.plot_apparent_anisotropy_section(sites, *, period_range=None, show_pt_arrows=False, arrow_every=4, cmap='RdBu_r', vmax=1.0, station_order=None, ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Apparent-anisotropy pseudosection: log₁₀(ρa_XY / ρa_YX).

Positive (warm) cells indicate ρa_XY > ρa_YX; negative (cool) cells the reverse. Zero corresponds to isotropic response at that station and period.

Parameters:
  • sites (any)

  • period_range ((T_min, T_max) or None)

  • show_pt_arrows (bool) – Overlay phase-tensor principal-axis arrows.

  • arrow_every (int) – Draw PT arrows every this many stations.

  • cmap (str, default "RdBu_r")

  • vmax (float) – Symmetric colour limit in log₁₀ units.

  • station_order (list of str or None)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_apparent_anisotropy_section
>>> fig = plot_apparent_anisotropy_section(sites, period_range=(1e-4, 1.0))
pycsamt.emtools.plot_apparent_resistivity_polar(sites, *, station=None, n_periods=8, period_range=None, normalize=True, cmap='plasma', ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Apparent-resistivity polar diagram: ρa(θ) petals per period.

The impedance tensor is rotated through θ ∈ [0°, 360°) and ρa_xy(θ) is computed at each angle. One petal per period is drawn on a polar axes, colour-coded by log(period).

Parameters:
  • sites (any)

  • station (str or None)

  • n_periods (int, default 8)

  • period_range ((T_min, T_max) or None)

  • normalize (bool) – Normalise each petal to its maximum so shapes are comparable.

  • cmap (str)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_apparent_resistivity_polar
>>> fig = plot_apparent_resistivity_polar(
...     sites, n_periods=8, normalize=True
... )
pycsamt.emtools.plot_dimensionality_depth_profile(sites, *, component='xy', beta_thresh=5.0, ellipt_thresh=0.1, period_range=None, depth_max=None, depth_unit='km', cmap='RdYlGn_r', station_order=None, ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Dimensionality classification mapped to Bostick depth space.

Each (station, period) datum is placed at its Bostick penetration depth and coloured by the 3-D membership u₃D ∈ [0, 1] (red = 3-D, green = 1-D/2-D).

Parameters:
  • sites (any)

  • component ({"xy", "yx"}, default "xy")

  • beta_thresh (float)

  • ellipt_thresh (float)

  • period_range ((T_min, T_max) or None)

  • depth_max (float or None)

  • depth_unit ({"km", "m"})

  • cmap (str)

  • station_order (list of str or None)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_dimensionality_depth_profile
>>> fig = plot_dimensionality_depth_profile(sites, depth_max=5.0)
pycsamt.emtools.plot_dimensionality_ternary(sites, *, beta_thresh=5.0, ellipt_thresh=0.1, period_range=None, color_by='period', cmap='plasma', ms=4.0, alpha=0.65, add_density=True, ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

1-D / 2-D / 3-D ternary classification diagram.

Every (station, period) observation is mapped to a ternary triangle whose vertices represent pure 1-D, 2-D, and 3-D character. The position within the triangle is determined by two continuous membership functions derived from the phase tensor:

\[ \begin{align}\begin{aligned}u_{3D} = \min(1, |\beta| / \beta_{thresh})\\u_{1D} = (1 - u_{3D}) \cdot \max(0,\, 1 - \lambda / \lambda_{thresh})\\u_{2D} = 1 - u_{1D} - u_{3D}\end{aligned}\end{align} \]

where β is the phase-tensor skewness and λ is the ellipticity.

Unlike the standard traffic-light grid, the ternary diagram reveals the continuous spread of the dataset’s dimensionality: where the cloud sits, how tightly clustered it is, and whether it bridges two regimes.

Parameters:
  • sites (any)

  • beta_thresh (float, default 5.0) – Skewness |β| (°) above which the 3-D membership saturates to 1.

  • ellipt_thresh (float, default 0.1) – Ellipticity λ above which the 2-D membership saturates to 1.

  • period_range ((T_min, T_max) or None)

  • color_by ({"period", "station", "skew", "ellipt"}) – Quantity mapped to point colour.

  • cmap (str, default "plasma")

  • ms (float) – Scatter marker size.

  • alpha (float) – Scatter opacity.

  • add_density (bool) – Overlay a hexbin density map behind the scatter.

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_dimensionality_ternary
>>> fig = plot_dimensionality_ternary(
...     sites, beta_thresh=5.0, ellipt_thresh=0.1
... )
pycsamt.emtools.plot_distortion_radar(sites, *, stations=None, max_stations=8, period_range=None, fill_alpha=0.18, line_alpha=0.85, lw=1.5, cmap='tab10', ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Galvanic-distortion decomposition radar chart.

Each station is rendered as a filled polygon on a six-axis radar (spider) chart. The axes encode six independent distortion proxies derived from the impedance tensor and apparent resistivity:

  1. Swift ν|(Zxx + Zyy)| / |(Zxy − Zyx)|, normalised. Measures departure from the 2-D “anti-diagonal” structure.

  2. Bahr η|(Zxy + Zyx)| / |(Zxy − Zyx)|, normalised. Quantifies the symmetric off-diagonal contamination.

  3. Phase asymmetry|φ_xy + φ_yx − 180°| / 90°. Zero for 1-D/2-D; increases with galvanic mixing.

  4. |β| skewness — scaled phase-tensor skewness.

  5. 1 − λ — ellipticity complement; high = near-isotropic (1-D).

  6. Strike IQR — interquartile range of the sweep-optimal strike over all frequencies; large = unstable strike = 3-D/distorted.

Pure 2-D galvanic distortion (twist + shear only) produces a characteristic narrow polygon aligned with axes 1–3; true 3-D structures also inflate axes 4–5.

Parameters:
  • sites (any)

  • stations (list of str or None) – Station names to display. Auto-selects max_stations when None.

  • max_stations (int, default 8)

  • period_range ((T_min, T_max) or None)

  • fill_alpha (visual parameters.)

  • line_alpha (visual parameters.)

  • lw (visual parameters.)

  • cmap (str, default "tab10")

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_distortion_radar
>>> fig = plot_distortion_radar(sites, stations=["S05", "S12", "S20"])
pycsamt.emtools.plot_impedance_mohr_circles(sites, *, station=None, periods=None, n_periods=8, n_theta=360, components=('xx', 'xy'), cmap='plasma', alpha=0.75, mark_zero=True, axes=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Double-Mohr-circle diagram of the MT impedance tensor.

For each period, the impedance tensor Z is rotated through all angles θ ∈ [0°, 360°) and the trajectory of a chosen pair of components is drawn in the complex plane — one circle per period. The two panels show the real and imaginary trajectories separately.

Physical interpretation (Lilley 1998; Weaver et al. 2000):

  • 1-D — every circle degenerates to a single point; all circles are centred at the same location (no off-diagonal trace).

  • 2-D — circles are distinct but all pass through the origin.

  • 3-D — circles do not pass through the origin; the distance of the centre from the origin indicates the degree of 3-D character.

Parameters:
  • sites (any) – EDI path(s) or Sites collection.

  • station (str or None) – Station name. None picks the first available.

  • periods (list of float or None) – Target periods (s) to draw. When None, n_periods logarithmically spaced periods spanning the data range are chosen.

  • n_periods (int, default 8) – Number of auto-selected periods.

  • n_theta (int, default 360) – Angular resolution of each circle.

  • components ((str, str), default ("xx", "xy")) – The two Z components traced on (x, y) axes of each panel.

  • cmap (str, default "plasma") – Colormap for period colour-coding.

  • alpha (float, default 0.75) – Circle opacity.

  • mark_zero (bool) – Mark the origin and θ=0° starting point on each circle.

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

References

Lilley F.E.M., 1998. Magnetotelluric tensor decomposition: Part I, Theory for a basic procedure. Geophysics, 63, 1885–1897.

Examples

>>> from pycsamt.emtools.advanced import plot_impedance_mohr_circles
>>> fig = plot_impedance_mohr_circles(sites, station="S12")
pycsamt.emtools.plot_mt_composite_section(sites, *, component='xy', quantities=None, period_range=None, station_order=None, axes=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Multi-row aligned pseudosection: ρa, φ, |β|, θ, SNR.

Up to five aligned rows share the same station axis. Station labels and site-triangle markers appear at the top of the first row via the package API.

Parameters:
  • sites (any)

  • component ({"xy", "yx"}, default "xy")

  • quantities (list of {"rho","phase","skew","theta","snr"} or None) – Default: all five.

  • period_range ((T_min, T_max) or None)

  • station_order (list of str or None)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_mt_composite_section
>>> fig = plot_mt_composite_section(
...     sites, component="xy", quantities=["rho", "phase", "skew"]
... )
pycsamt.emtools.plot_pt_period_clock(sites, *, station=None, n_rings=6, period_range=None, cmap='plasma', ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Phase-tensor period clock: PT ellipses on concentric rings.

Each concentric ring = one period (inner = short = shallow; outer = long = deep). The PT ellipse is placed at the top of each ring, oriented by θ (strike) and elongated by λ (ellipticity). When station is None, the survey-wide median θ and λ are used.

Parameters:
  • sites (any)

  • station (str or None)

  • n_rings (int, default 6)

  • period_range ((T_min, T_max) or None)

  • cmap (str)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_pt_period_clock
>>> fig = plot_pt_period_clock(all_sites, n_rings=6)
pycsamt.emtools.plot_rho_phase_bode(sites, *, station=None, component='xy', period_range=None, smooth_window=0, axes=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Bode consistency diagram: observed ρa/φ vs Bostick-predicted φ.

For a minimum-phase medium the phase can be predicted from ρa via

\[\phi_{Bode}(T) \approx \frac{\pi}{4} \left(1 + \frac{d\,\ln\rho_a}{d\,\ln T}\right)\]

Significant departure of observed φ from φ_Bode indicates galvanic distortion or near-field source effects.

Parameters:
  • sites (any)

  • station (str or None)

  • component ({"xy", "yx"}, default "xy")

  • period_range ((T_min, T_max) or None)

  • smooth_window (int) – Half-width (points) of a centred moving-average smoothing kernel.

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_rho_phase_bode
>>> fig = plot_rho_phase_bode(sites, component="xy")
pycsamt.emtools.plot_sensitivity_depth_section(sites, *, component='xy', period_range=None, depth_max=None, depth_unit='km', cmap='jet_r', alpha_bar=0.55, bar_width_fraction=0.7, rho_lim=None, station_order=None, show_bostick_depth=True, ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Bostick-sensitivity-kernel pseudosection.

For every (station, period) cell the Bostick penetration depth is computed and the cell is rendered as a vertical bar centred on that depth:

\[d_B = \sqrt{\frac{\rho_a}{\mu_0 \, 2 \pi f}}\]

The bar’s colour encodes the apparent resistivity ρa; its vertical extent reflects the sensitivity window Δd ≈ d_B × (d log ρa / d log T + 1) / 2. Overlapping bars from multiple periods build a natural depth-smoothed image.

Unlike a simple Bostick pseudosection (which maps period → depth but loses the sensitivity context), this plot explicitly shows where in depth and how broadly each datum is sensitive.

Parameters:
  • sites (any)

  • component ({"xy", "yx"}, default "xy") – Impedance component used for ρa and penetration depth.

  • period_range ((T_min, T_max) or None)

  • depth_max (float or None) – Clip the depth axis at this value (km or m depending on depth_unit).

  • depth_unit ({"km", "m"}, default "km")

  • cmap (str, default "jet_r") – Colormap for ρa colour-coding.

  • alpha_bar (float) – Opacity of each kernel bar.

  • bar_width_fraction (float) – Fraction of the inter-station spacing used as bar width.

  • rho_lim ((vmin, vmax) or None) – Explicit ρa colour limits. None → 5th–95th percentile.

  • station_order (list of str or None)

  • show_bostick_depth (bool) – Overlay a thin line at the Bostick depth (without the bar width).

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_sensitivity_depth_section
>>> fig = plot_sensitivity_depth_section(
...     sites, component="xy", depth_max=5.0
... )
pycsamt.emtools.plot_snr_section(sites, *, components=('xy', 'yx'), period_range=None, snr_thresh=3.0, cmap='RdYlGn', vmax=10.0, station_order=None, axes=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Signal-to-noise ratio pseudosection: SNR = |Z| / |Z_err|.

Each panel shows one impedance component. A contour at snr_thresh separates acceptable (green) from poor (red) quality cells.

Parameters:
  • sites (any)

  • components (tuple, default ("xy", "yx"))

  • period_range ((T_min, T_max) or None)

  • snr_thresh (float, default 3.0)

  • cmap (str)

  • vmax (float) – Upper SNR colour limit.

  • station_order (list of str or None)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_snr_section
>>> fig = plot_snr_section(sites, components=("xy", "yx"), snr_thresh=3.0)
pycsamt.emtools.plot_strike_stability_bands(sites, *, methods=('sweep', 'pt', 'tipper'), period_range=None, n_period_bins=30, agreement_tol=10.0, smooth_window=3, method_colors=None, fill_alpha=0.25, line_alpha=0.9, consensus_alpha=0.18, ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Multi-method strike stability band diagram.

Three strike-estimation methods are evaluated at every period and displayed as coloured ribbons (median ± 0.5 × IQR across all stations). Where all active methods agree within agreement_tol degrees, a grey consensus zone is shaded.

The plot answers: “At which periods is the estimated strike reliable, and do different methods agree?”

Methods:

  • "sweep" — impedance-rotation sweep (Z-based), per frequency.

  • "pt" — phase-tensor principal axis θ.

  • "tipper" — induction-arrow azimuth, real component (skipped silently when no tipper data are found).

Parameters:
  • sites (any)

  • methods (tuple, default ("sweep", "pt", "tipper"))

  • period_range ((T_min, T_max) or None)

  • n_period_bins (int, default 30) – Number of log-spaced period bins used for ribbon statistics.

  • agreement_tol (float, default 10.0) – Strike agreement window in degrees (consensus shading threshold).

  • smooth_window (int, default 3) – Moving-average smoothing kernel applied to the median ribbon.

  • method_colors (dict or None) – {"sweep": color, "pt": color, "tipper": color}.

  • fill_alpha (float) – Opacity parameters.

  • line_alpha (float) – Opacity parameters.

  • consensus_alpha (float) – Opacity parameters.

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_strike_stability_bands
>>> fig = plot_strike_stability_bands(all_sites, agreement_tol=10.0)
pycsamt.emtools.plot_survey_fingerprint(sites, *, quantities=None, period_range=None, station_order=None, render='pcolormesh', cmaps=None, plot_kws=None, quantity_kws=None, contours=None, contour_kws=None, station_grid=False, station_grid_kws=None, cell_aspect=1.0, axes=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Compact multi-metric survey fingerprint grid.

Plots all stations × periods as a colour-coded image for several simultaneous physical quantities derived from the phase tensor. The result is a compact “fingerprint” that reveals spatial and frequency patterns across the entire survey on a single page.

Each row of panels corresponds to one quantity; each column of pixels within a panel is one station; each row of pixels is one period (log-spaced, top = short period = shallow, bottom = long period = deep).

Quantities available (all derived from build_phase_tensor_table()):

  • "skew" — skewness β: the 3-D indicator

  • "ellipt" — ellipticity λ = φ_min/φ_max

  • "theta" — principal-axis strike angle

  • "s1" — φ_max (maximum phase)

  • "s2" — φ_min (minimum phase)

  • "beta"|β| (absolute skew)

Parameters:
  • sites (any)

  • quantities (sequence of str, str, or None) – Quantities to plot, in panel order. A single string produces one panel. Aliases include "ellipticity" and "phi_max". The default is ("skew", "ellipt", "s1").

  • period_range ((T_min, T_max) or None)

  • station_order (list of str or None) – Explicit station order along the x-axis. Auto from data when None.

  • render ({"pcolormesh", "imshow"}, default="pcolormesh") – Matplotlib renderer used for every panel.

  • cmaps (str, mapping, or None) – One colormap for every panel, or a mapping from quantity name to colormap. Missing mapping entries use the quantity defaults.

  • plot_kws (mapping or None) – Renderer keyword arguments applied to every panel.

  • quantity_kws (mapping of mappings or None) – Per-quantity renderer overrides. Explicit vmin, vmax, or norm values replace the robust percentile limits.

  • contours (bool or None, default=None) – Overlay contour lines on each rendered quantity. None resolves through pycsamt.api.PYCSAMT_CONTOUR.

  • contour_kws (mapping or None) – Keyword arguments forwarded to matplotlib.axes.Axes.contour(). Defaults are levels=7, thin dark lines, and partial transparency.

  • station_grid (bool, default=False) – Draw a vertical guide through every station centre on every panel. Because the panels share their station geometry, these guides make it easier to compare a feature at the same station across quantities.

  • station_grid_kws (mapping or None) – Keyword arguments forwarded to matplotlib.axes.Axes.axvline(). Defaults produce restrained white dotted guides. Common controls are color, linewidth, linestyle, alpha, and zorder.

  • cell_aspect (float) – Aspect ratio of individual cells (width / height per cell).

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_survey_fingerprint
>>> fig = plot_survey_fingerprint(all_sites, period_range=(1e-4, 1.0))
>>> fig = plot_survey_fingerprint(
...     all_sites,
...     quantities=["skew", "phi_max"],
...     render="imshow",
...     cmaps={"skew": "coolwarm", "phi_max": "magma"},
... )
pycsamt.emtools.plot_tf_coherence_network(sites, *, component='xy', period_range=None, threshold=0.85, max_edges=120, node_c_by='skew', node_cmap='RdBu_r', edge_cmap='YlOrRd', node_ms=8.0, lw_max=2.5, alpha_edge=0.65, ax=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Inter-station transfer-function coherence network map.

Stations are placed at their geographic positions. Pairs of stations whose log₁₀(ρa) curves are correlated above threshold (Pearson r) are connected by an edge: edge width ∝ r, edge colour ∝ r. Isolated nodes (no connections above threshold) represent data outliers or strongly localised 3-D anomalies.

The node colour encodes a per-station summary quantity (node_c_by):

  • "skew" — median |β| (high = 3-D)

  • "ellipt" — median ellipticity

  • "rho" — median log₁₀(ρa)

Parameters:
  • sites (any)

  • component ({"xy", "yx"}, default "xy") – ρa component used to compute pairwise correlation.

  • period_range ((T_min, T_max) or None)

  • threshold (float, default 0.85) – Minimum Pearson r to draw an edge.

  • max_edges (int, default 120) – Maximum edges drawn (highest-r first).

  • node_c_by ({"skew", "ellipt", "rho"})

  • node_cmap (str, default "RdBu_r")

  • edge_cmap (str, default "YlOrRd")

  • node_ms (float)

  • lw_max (edge visual parameters.)

  • alpha_edge (edge visual parameters.)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_tf_coherence_network
>>> fig = plot_tf_coherence_network(all_sites, threshold=0.90)
pycsamt.emtools.plot_z_invariants_section(sites, *, period_range=None, station_order=None, axes=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Four-panel impedance rotation-invariants pseudosection.

Each panel is a station × log-period image of one invariant:

  1. Swift ν = |Zxx + Zyy| / |Zxy − Zyx| (0 = ideal 2-D)

  2. Bahr μ = |Zxy + Zyx| / |Zxy − Zyx| (0 = no galvanic mixing)

  3. |det Z|^½ = √|ZxxZyy − ZxyZyx| (distortion-invariant ρa proxy)

  4. |tr Z| / ||Zxy| − |Zyx|| (anisotropy proxy: small when the two off-diagonal magnitudes are close, large as they diverge)

Parameters:
  • sites (any)

  • period_range ((T_min, T_max) or None)

  • station_order (list of str or None)

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_z_invariants_section
>>> fig = plot_z_invariants_section(sites, period_range=(1e-4, 1.0))
pycsamt.emtools.plot_zt_argand(sites, *, station=None, components=('xy', 'yx'), period_range=None, cmap='viridis', lw=1.6, ms=4.5, arrow_every=4, normalize=False, axes=None, figsize=None, title='', recursive=True, on_dup='replace', strict=False, verbose=0)#

Argand-space trajectory of MT impedance components.

Each component Z_ij is plotted as a curve in the complex plane (Re vs Im), parametrised by period. The curve is colour-coded from short periods (shallow) to long periods (deep) using cmap. Arrows along the curve show the direction of increasing period.

Physical interpretation:

  • A 1-D subsurface produces a straight line at 45° through the origin for Z_xy (pure resistive + inductive).

  • 2-D structures bend and rotate the trajectory.

  • 3-D structures produce strongly curved or looping trajectories.

  • The winding number of the trajectory around the origin is related to the number of layer interfaces penetrated.

Parameters:
  • sites (any) – EDI path(s) or Sites collection.

  • station (str or None) – Station name. None → first station.

  • components (tuple of {"xx","xy","yx","yy"}, default ("xy","yx")) – Which Z components to trace. One sub-panel per component.

  • period_range ((T_min, T_max) or None)

  • cmap (str, default "viridis") – Colormap for period (shallow = dark, deep = bright).

  • lw (float) – Trajectory line-width.

  • ms (float) – Marker size at each period point.

  • arrow_every (int) – Draw a direction arrow every arrow_every points.

  • normalize (bool) – If True, normalise each trajectory to unit magnitude for shape comparison.

  • figsize ((float, float) or None)

  • title (str)

  • recursive (bool)

  • on_dup (str)

  • strict (bool)

  • verbose (int)

Return type:

matplotlib.figure.Figure

Examples

>>> from pycsamt.emtools.advanced import plot_zt_argand
>>> fig = plot_zt_argand(
...     sites, station="S12", components=("xy", "yx"), normalize=False
... )
pycsamt.emtools.ztem_crossover_diagnostics(sites, *, frequency_hz=None, period_s=None, component='tzx', spacing_m=200.0, recursive=True, on_dup='replace', strict=False, verbose=0)#

Legault et al. (2012, Fig. 6)-style raw in-phase/quadrature crossover.

Their own synthetic forward-model example over a mushroom-shaped epithermal target reads a negative-to-positive in-phase crossover directly above the target, generally accompanied by a (noisier) negative-to-positive quadrature crossover at every frequency – the qualitative, single-frequency, pre-processing read of a flight line before any derivative or transform is applied (contrast total_divergence_table()/ phase_rotate_table(), both of which operate on this same raw tipper but convert the crossover into a peak). This function finds those two crossovers and reports the peak-to-peak swing of each, the same crossover/amplitude measurements original_afmag_conductor_diagnostics() reports for the AFMAG comparator, applied here to the real/ imaginary parts of one tipper component instead of two hardware frequencies.

Warning

Like total_divergence_table(), sites is assumed to be one flight line; pre-filter a multi-line survey to one line first (see that function’s own warning for why).

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites().

  • frequency_hz (float, optional) – Reference frequency/period; nearest available value is used. At most one may be given; the median frequency across the profile is used when neither is given.

  • period_s (float, optional) – Reference frequency/period; nearest available value is used. At most one may be given; the median frequency across the profile is used when neither is given.

  • component ({"tzx", "tzy"}, default "tzx") – "tzx" is the classical in-line choice (Legault et al. 2012); "tzy" highlights cross-line structure (Sattel and Witherly 2012).

  • spacing_m (float, default 200.0) – Forwarded to _station_positions().

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

Returns:

Keys: freq_hz, crossover_real_m, crossover_imag_m (along-profile position, nan if that part never changes sign between its own max and min), peak_to_peak_real, peak_to_peak_imag, and profile – a pandas.DataFrame with columns station, position_m, real, imag (the raw, dimensionless tipper values; multiply by 100 for the percent convention Legault et al. 2012 and Sattel and Witherly 2012 both plot).

Return type:

dict

Raises:

ValueError – If component is not "tzx"/"tzy", or fewer than 2 stations have a usable value at the resolved frequency.

pycsamt.emtools.total_divergence_table(sites, spacing_m=200.0, *, component='tzx', recursive=True, on_dup='replace', strict=False, verbose=0)#

Along-profile ZTEM total-divergence / Peaker table.

Computes the along-line horizontal derivative of the selected tipper component by first-order central-in-space finite differences between adjacent stations, at every frequency:

\[DT(j,\,f) \approx \frac{T(j+1,\,f) - T(j,\,f)}{x(j+1) - x(j)}\]

where stations are ordered by chainage along the profile (see _station_positions()). Per Sattel and Witherly (2012), this single along-line derivative is both the “Total Divergence” (Lo and Zang 2008) and the VLF-style “Peaker” (Pedersen et al. 1994) in the 2D/profile case – the full 3-D map-grid divergence (\(\partial T_{zx}/\partial x + \partial T_{zy}/\partial y\)) would additionally require a genuine cross-line (y) sampling that a single Sites profile does not carry, and is not attempted here.

Warning

sites is assumed to be one flight line. Chainage comes from _station_positions(), which projects every station onto a single bearing; passing a multi-line survey directly differentiates across line boundaries too, producing a physically meaningless value at every line-to-line join. Pre-filter to one line first (e.g. select() on a per-line predicate) before calling this function on a multi-line dataset – see plot_ztem_map()’s own quantity="divergence" branch for a worked example that does this per-line grouping automatically.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(): ground Sites when the data carries an impedance channel too, or AirborneSites for genuine tipper-only ZTEM/AFMAG data (a path/directory of EMTF-XML is routed automatically based on what it contains).

  • spacing_m (float, default 200.0) – Fall-back inter-station spacing [m] used only when no station coordinates are available; see _station_positions().

  • component ({"tzx", "tzy"}, default "tzx") – Tipper component to differentiate. "tzx" is the classical in-line (flight-direction) choice (Legault et al. 2012); "tzy" highlights structures striking across the line (Sattel and Witherly 2012).

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

Returns:

One row per (adjacent-station pair, frequency). Columns: station_a, station_b (left/right station of the pair), x_m (pair midpoint chainage), dx_m (station spacing), freq_hz, period_s, divergence_real, divergence_imag [each in units of tipper per metre], divergence_abs. Pairs/frequencies with a missing tipper value on either side are omitted, not filled with zero.

Return type:

pandas.DataFrame

Raises:

ValueError – If component is not "tzx" or "tzy".

pycsamt.emtools.phase_rotate_table(sites, *, frequency_hz=None, period_s=None, component='tzx', part='real', spacing_m=200.0, n_resample=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Hilbert-transform “phase-rotated” ZTEM profile at one frequency.

Reproduces the Hilbert-transform half of the phase-rotation image product described by Sattel and Witherly (2012, Fig. 2): the tipper component’s along-profile crossover anomaly (odd about the causative contact) is converted into a peak anomaly (even about it) by taking the imaginary part of its spatial analytic signal (scipy.signal.hilbert()). Because the Hilbert transform assumes uniform sampling, the selected component is first linearly interpolated onto a uniform grid along chainage; the returned table is indexed by that uniform grid (with the nearest real station attached for reference), not by the original, generally unevenly spaced, station positions.

Warning

Like total_divergence_table(), sites is assumed to be one flight line: chainage is a single-bearing projection of every station, so a multi-line survey passed directly gets interpolated across line boundaries too. Pre-filter to one line first for a multi-line dataset.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(): ground Sites when the data carries an impedance channel too, or AirborneSites for genuine tipper-only ZTEM/AFMAG data (a path/directory of EMTF-XML is routed automatically based on what it contains).

  • frequency_hz (float, optional) – Target frequency/period; the nearest available frequency is used. At most one may be given; the median frequency across the profile is used when neither is given.

  • period_s (float, optional) – Target frequency/period; the nearest available frequency is used. At most one may be given; the median frequency across the profile is used when neither is given.

  • component ({"tzx", "tzy"}, default "tzx")

  • part ({"real", "imag"}, default "real") – Which part of the complex tipper to phase-rotate. "real" (in-phase) matches the classical VLF/ZTEM crossover-to-peak image product.

  • spacing_m (float, default 200.0) – Fall-back inter-station spacing [m]; see _station_positions().

  • n_resample (int, optional) – Number of points on the uniform resampling grid. Defaults to the number of stations with a valid value (minimum 64).

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

Returns:

Columns: x_m (uniform-grid chainage), nearest_station, freq_hz, period_s, raw (the interpolated, un- rotated component), rotated (its Hilbert transform), envelope (the analytic-signal magnitude \(\sqrt{raw^2 + rotated^2}\)).

Return type:

pandas.DataFrame

Raises:

ValueError – If component is not "tzx"/"tzy", if part is not "real"/"imag", or if both frequency_hz and period_s are given.

pycsamt.emtools.mask_outside_ztem_band(sites, *, band_hz=None, system_spec=None, action='mask', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Mask/drop tipper frequencies outside the usable ZTEM band.

Reuses the published usable bandwidth already carried by pycsamt.airborne.ztem.ZTEMSystemSpec (default practical_frequency_range_hz of 22-720 Hz) rather than inventing a new band definition. For ground Sites input, this mirrors the same ensure_sites -> _apply_each mutation contract used by flag_motion_susceptible_band() and notch_powerline(): this is the one function in this module meant to sit inside a processing pipeline (container in, container out) rather than only produce a diagnostic table. For AirborneSites input, action="drop" is refused (see Raises) because it would leave the EMTF document’s shared period axis inconsistent with the tipper transfer function’s own periods; only action="mask" is offered there, matching mask_outside_mobilemt_band()’s identical restriction for the identical reason.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(): ground Sites when the data carries an impedance channel too, or AirborneSites for genuine tipper-only ZTEM/AFMAG data (a path/directory of EMTF-XML is routed automatically based on what it contains).

  • band_hz ((float, float), optional) – Explicit (low, high) band in Hz. Mutually exclusive with system_spec; when neither is given, a default ZTEMSystemSpec’s practical_frequency_range_hz is used.

  • system_spec (pycsamt.airborne.ztem.ZTEMSystemSpec, optional) – Survey-specific system metadata to read the band from.

  • action ({"mask", "drop"}, default "mask") – "mask" sets out-of-band tipper values to nan in place; "drop" removes the corresponding frequency rows entirely.

  • inplace (bool) – Standard emtools processing-function tail; see flag_motion_susceptible_band() for the established convention this mirrors.

  • recursive (bool) – Standard emtools processing-function tail; see flag_motion_susceptible_band() for the established convention this mirrors.

  • on_dup (str) – Standard emtools processing-function tail; see flag_motion_susceptible_band() for the established convention this mirrors.

  • strict (bool) – Standard emtools processing-function tail; see flag_motion_susceptible_band() for the established convention this mirrors.

  • verbose (int) – Standard emtools processing-function tail; see flag_motion_susceptible_band() for the established convention this mirrors.

Returns:

The (optionally new) sites collection with out-of-band tipper frequencies masked or dropped.

Return type:

Sites

Raises:
  • ValueError – If action is not "mask" or "drop"; if both band_hz and system_spec are given; or if action is "drop" and sites resolves to AirborneSites.

  • TypeError – If system_spec is given and is not a ZTEMSystemSpec.

pycsamt.emtools.plot_ztem_tipper_profile(sites, *, frequency_hz=None, period_s=None, component='tzx', as_percent=True, figsize=(9.5, 4.2), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Legault et al. (2012, Fig. 6)-style raw in-phase/quadrature profile.

The classic ZTEM field-presentation figure: real (in-phase) and imaginary (quadrature) tipper plotted together at one frequency, in percent, along real flight-line chainage – Fig. 6’s own “METERS” x-axis, not a discrete station index – with each part’s crossover marked; see ztem_crossover_diagnostics().

Parameters:
Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_ztem_divergence_profile(sites, *, component='tzx', part='real', frequency_hz=None, period_s=None, spacing_m=200.0, figsize=(9.5, 4.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)#

Plot the ZTEM total-divergence / Peaker flight-line profile.

One value per adjacent-station pair at a single reference frequency/period – the along-line, pre-gridding form of the “Total Divergence” / “Peaker” image product (see total_divergence_table()).

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(): ground Sites when the data carries an impedance channel too, or AirborneSites for genuine tipper-only ZTEM/AFMAG data (a path/directory of EMTF-XML is routed automatically based on what it contains).

  • component ({"tzx", "tzy"}, default "tzx")

  • part ({"real", "imag"}, default "real")

  • frequency_hz (float, optional) – Reference frequency/period; nearest available value is used per station pair. At most one may be given; the median frequency across all pairs is used when neither is given.

  • period_s (float, optional) – Reference frequency/period; nearest available value is used per station pair. At most one may be given; the median frequency across all pairs is used when neither is given.

  • spacing_m (float, default 200.0) – Forwarded to total_divergence_table().

  • figsize ((float, float), default (9.5, 4.0)) – Used only when ax is not supplied.

  • station_label_step (int | None) – Forwarded to style_for() via the shared top-of-section station convention.

  • station_preset (str) – Forwarded to style_for() via the shared top-of-section station convention.

  • station_style (Any | None) – Forwarded to style_for() via the shared top-of-section station convention.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_ztem_divergence_psection(sites, *, component='tzx', part='real', spacing_m=200.0, cmap='RdBu_r', clim=None, clim_pct=95.0, show_grid=True, show_contour=True, n_contour_levels=3, figsize=(9.0, 5.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)#

Plot a ZTEM total-divergence pseudosection (station x log-period).

A diverging, zero-centred colour scale is used, matching the physical sign convention of a spatial derivative (positive on one side of an anomaly, negative on the other – see total_divergence_table()). Optional cell-boundary gridlines and a contour overlay (default n_contour_levels=3, one interior level – here the physically meaningful zero-divergence line itself) match the same imshow/contour convention used by plot_airmt_tilt_psection() and pycsamt.emtools.fieldzone’s own pseudosections. For several flight lines compared side by side on one shared colour scale, see plot_ztem_divergence_psection_grid().

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(): ground Sites when the data carries an impedance channel too, or AirborneSites for genuine tipper-only ZTEM/AFMAG data (a path/directory of EMTF-XML is routed automatically based on what it contains).

  • component ({"tzx", "tzy"}, default "tzx")

  • part ({"real", "imag"}, default "real")

  • spacing_m (float, default 200.0) – Forwarded to total_divergence_table().

  • cmap (str, default "RdBu_r") – Diverging colormap name.

  • clim ((float, float), optional) – Explicit, zero-centred color limits; overrides clim_pct.

  • clim_pct (float, default 95.0) – Percentile of |divergence| used to size a symmetric colour range when clim is not given.

  • show_grid (bool, default True) – Draw thin gridlines at every station-pair/period cell boundary.

  • show_contour (bool, default True) – Overlay n_contour_levels - 2 evenly-spaced contour lines with inline labels; with the default zero-centred colour scale and 3 levels, the single interior level drawn is the zero-divergence contour itself, i.e. the crossover/conductor axis at every period simultaneously.

  • n_contour_levels (int, default 3) – Number of evenly-spaced levels spanning clim before dropping the two outermost; must be at least 3 for any line to be drawn. Kept low deliberately – a coarse station/period grid does not support many contour levels without the lines tangling into visual noise (see plot_airmt_tilt_psection()’s docstring for the same reasoning).

  • figsize ((float, float), default (9.0, 5.0)) – Used only when ax is not supplied.

  • station_label_step (int | None) – See plot_ztem_divergence_profile().

  • station_preset (str) – See plot_ztem_divergence_profile().

  • station_style (Any | None) – See plot_ztem_divergence_profile().

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_ztem_divergence_psection_grid(sites, *, component='tzx', part='real', spacing_m=200.0, max_lines=6, n_cols=3, cmap='seismic', clim=None, clim_pct=95.0, show_grid=True, show_contour=True, n_contour_levels=3, panel_size=(4.3, 3.4), station_label_step=2, station_preset='pseudosection', station_style=None, axes=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Compare several flight lines’ divergence pseudosections at once.

Every panel shares one colour scale (unlike calling plot_ztem_divergence_psection() once per line, where each panel would size its own), so colour differences between lines are directly comparable – the multi-line counterpart of a single plot_ztem_divergence_psection() call, laid out on a grid with n_cols columns. Flight lines are detected from station geometry (see _detect_line_groups()); when more than max_lines are found, a spatially even subset is kept rather than just the first max_lines encountered.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(), spanning several flight lines.

  • component (str) – Forwarded to total_divergence_table() for every line.

  • part (str) – Forwarded to total_divergence_table() for every line.

  • spacing_m (float) – Forwarded to total_divergence_table() for every line.

  • max_lines (int, default 6) – Maximum number of lines to draw.

  • n_cols (int, default 3) – Number of grid columns; rows are added as needed.

  • cmap (str, default "seismic") – Diverging colormap name.

  • clim ((float, float), optional) – Explicit, zero-centred color limits shared by every panel; overrides clim_pct.

  • clim_pct (float, default 95.0) – Percentile of |divergence|, pooled across every drawn line, used to size the shared symmetric colour range when clim is not given.

  • show_grid (bool) – See plot_ztem_divergence_psection().

  • show_contour (bool) – See plot_ztem_divergence_psection().

  • n_contour_levels (int) – See plot_ztem_divergence_psection().

  • panel_size ((float, float), default (4.3, 3.4)) – Per-panel figure size in inches; the full figure scales with the number of rows/columns actually used. Ignored when axes is supplied.

  • station_label_step (int | None) – See plot_ztem_divergence_profile().

  • station_preset (str) – See plot_ztem_divergence_profile().

  • station_style (Any | None) – See plot_ztem_divergence_profile().

  • axes (sequence of Axes, optional) – Existing axes to draw the (up to max_lines) panels on, flattened in the same row-major order the auto-created grid would use; must provide at least as many axes as lines are actually drawn. When not given, a new figure and grid of axes is created.

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

Return type:

matplotlib.Figure

pycsamt.emtools.plot_ztem_phase_rotation_profile(sites, *, component='tzx', part='real', frequency_hz=None, period_s=None, spacing_m=200.0, n_resample=None, figsize=(9.5, 4.2), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot raw vs. Hilbert-phase-rotated ZTEM response at one frequency.

Direct reproduction of the crossover-to-peak comparison in Sattel and Witherly (2012, Fig. 2); see phase_rotate_table().

Parameters:
Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_ztem_band_mask_psection(sites, *, band_hz=None, system_spec=None, component='abs', cmap='RdBu_r', figsize=(9.5, 8.0), axes=None, recursive=True, on_dup='replace', strict=False, verbose=0)#

Plot before/after \(|T|\) pseudosections around the ZTEM usable band.

Reuses plot_induction_section() for both panels rather than re-implementing pseudosection gridding, and mask_outside_ztem_band() to compute the “after” sites.

Parameters:
Return type:

matplotlib.Figure

pycsamt.emtools.plot_ztem_flight_lines(sites, *, figsize=(7.0, 6.0), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot a Sattel and Witherly (2012, Fig. 7)-style flight-line map.

Every detected flight line (see _detect_line_groups()) is drawn as its own connected navigation trace, coloured distinctly (a viridis sample per line) and labelled near its first station – with the real flight-line identifier when a technology note carries one (e.g. ZTEM’s own metadata["notes"]["ZTEM"]["LineId"]), or else a generic L1, L2, … in detected-group order, which need not match any real line numbering – with station markers. The plan-view counterpart of every other function in this module, which reads a single profile at a time. Longitude tick labels are rotated 45 degrees to avoid overlapping.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites().

  • figsize ((float, float), default (7.0, 6.0)) – Used only when ax is not supplied.

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_ztem_map(sites, *, quantity='tipper', part='real', component='tzx', frequency_hz=None, period_s=None, n_grid=120, cmap='RdBu_r', clim=None, clim_pct=95.0, show_stations=True, figsize=(8.0, 6.5), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Legault et al. (2012, Fig. 7) and Sattel and Witherly (2012, Fig. 8-11)-style map-view grid.

Interpolates one scalar field at one frequency across every flight line in sites onto a regular map grid (scipy.interpolate.griddata(), linear inside the convex hull of the stations, unfilled – left nan – outside it rather than extrapolated) and images it with a diverging, zero-centred colour scale – the genuine multi-line map product both papers show (their “DT map”/”XIP grid”/”phase-rotated grid”), as opposed to every other function in this module, which reads one flight line as a profile or pseudosection.

Parameters:
  • sites (Sites-like or AirborneSites-like) – Anything accepted by ensure_any_sites(). A genuine map needs several roughly-parallel flight lines; a single line still renders, as a thin interpolated strip along it.

  • quantity ({"tipper", "divergence"}, default "tipper") – "tipper" images the raw, un-processed tipper component (Legault et al. 2012, Fig. 7’s own “In-Phase” map); "divergence" images the along-line total-divergence / Peaker value (total_divergence_table()) at each station’s own flight line, matching Sattel and Witherly (2012)’s “DT” grid.

  • part ({"real", "imag"}, default "real")

  • component ({"tzx", "tzy"}, default "tzx")

  • frequency_hz (float, optional) – Reference frequency/period; nearest available value is used per station. At most one may be given; the median frequency is used when neither is given.

  • period_s (float, optional) – Reference frequency/period; nearest available value is used per station. At most one may be given; the median frequency is used when neither is given.

  • n_grid (int, default 120) – Number of grid points along the longer map axis; the shorter axis is scaled to preserve the survey’s aspect ratio.

  • cmap (str, default "RdBu_r")

  • clim ((float, float), optional) – Explicit, zero-centred color limits; overrides clim_pct.

  • clim_pct (float, default 95.0) – Percentile of |value| used to size a symmetric colour range when clim is not given.

  • show_stations (bool, default True) – Overlay the actual station positions as small markers.

  • figsize ((float, float), default (8.0, 6.5)) – Used only when ax is not supplied.

  • recursive (bool) – Forwarded to ensure_any_sites().

  • on_dup (str) – Forwarded to ensure_any_sites().

  • strict (bool) – Forwarded to ensure_any_sites().

  • verbose (int) – Forwarded to ensure_any_sites().

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

Raises:

ValueError – If quantity is not "tipper"/"divergence", or part/component is invalid.

pycsamt.emtools.ensure_mobilemt_dataset(obj)#

Normalize a dataset or single line to an AirborneEMDataset.

The single entry-point validator for every public function in this module, mirroring the role ensure_sites() plays for the rest of emtools.

Parameters:

obj (AirborneEMDataset or AirborneEMLine or AirborneSites or AirborneSite or str or pathlib.Path) – Accepted as-is when already a dataset; a single line is wrapped in a new one-line dataset. An AirborneSites/ AirborneSite, or a path to a single EMTF-XML file or a directory of them, is first coerced via ensure_asites() and then regrouped into flight lines by line_id (see _dataset_from_asites()).

Return type:

AirborneEMDataset

Raises:

TypeError – If obj is none of the accepted types.

pycsamt.emtools.admittance_table(dataset)#

Return a tidy per-(line, sample, frequency) admittance table.

Parameters:

dataset (AirborneEMDataset or AirborneEMLine) – Anything accepted by ensure_mobilemt_dataset().

Returns:

Columns: line_id, sample_id, x_m (chainage along the flight line, see _station_positions()’s along-profile convention), freq_hz, period_s, the real/imaginary parts of every entry of the horizontal 2x2 admittance (Yxx, Yxy, Yyx, Yyy) and of the vertical-field row (Yhzx, Yhzy), and apparent_conductivity_native_Sm – the vendor-delivered processed field (MOBILEMT_APPARENT_CONDUCTIVITY_FIELD) when present, NaN otherwise.

Return type:

pandas.DataFrame

pycsamt.emtools.admittance_determinant_table(dataset)#

Return the theoretical Berdichevsky-determinant admittance table.

See the module docstring for the full derivation. In brief, using the horizontal 2x2 admittance submatrix \(Y = \begin{pmatrix}Y_{xx}&Y_{xy}\\Y_{yx}&Y_{yy}\end{pmatrix}\) and the co-located-sensor identity \(Y=Z^{-1}\) (Zhdanov et al. 2024; Sattel et al. 2019), applying pyCSAMT’s own \(Z\)-determinant convention (pycsamt.z.resphase.ResPhase) by substitution gives:

\[Y_{\mathrm{eff}} = \sqrt{\det Y}, \qquad \sigma_a = 5\,f\,|Y_{\mathrm{eff}}|^2, \qquad \varphi_a = -\arg(Y_{\mathrm{eff}})\]
Parameters:

dataset (AirborneEMDataset or AirborneEMLine) – Anything accepted by ensure_mobilemt_dataset().

Returns:

Columns: line_id, sample_id, x_m, freq_hz, period_s, det_abs (\(|\det Y|\)), theoretical_sigma_a_Sm, theoretical_rho_a_ohm_m (\(1/\sigma_a\)), theoretical_phase_deg, and apparent_conductivity_native_Sm (the vendor-delivered field, for direct comparison, NaN when absent). Samples with a non-finite determinant are omitted.

Return type:

pandas.DataFrame

Notes

The theoretical_* columns are a derived quantity assuming ideal co-located sensors; they are not a reproduction of MobileMT’s proprietary processed apparent-conductivity output. Prefer apparent_conductivity_native_Sm whenever it is present.

pycsamt.emtools.admittance_skew_table(dataset)#

Return a Swift (1967)-style skew table for the admittance tensor.

\[\mathrm{skew} = \frac{|Y_{xx} + Y_{yy}|}{|Y_{xy} - Y_{yx}|}\]

applied to the horizontal 2x2 admittance submatrix by direct algebraic analogy to the identical ratio already used for the impedance tensor elsewhere in pyCSAMT. Being a ratio of magnitudes, it needs no absolute physical constant and is safe to compute directly, unlike admittance_determinant_table()’s theoretical_* columns. Large values flag departures from an ideal 1D/2D-consistent admittance tensor (instrument coupling, cultural noise, genuinely 3-D structure).

Parameters:

dataset (AirborneEMDataset or AirborneEMLine) – Anything accepted by ensure_mobilemt_dataset().

Returns:

Columns: line_id, sample_id, x_m, freq_hz, period_s, skew. Non-finite values are omitted.

Return type:

pandas.DataFrame

pycsamt.emtools.mask_outside_mobilemt_band(dataset, *, band_hz=None, system_spec=None, inplace=False)#

Mask admittance/conductivity outside the usable MobileMT band.

Reuses the published usable bandwidth already carried by MobileMTSystemSpec (default nominal_frequency_range_hz of 19-26,000 Hz) rather than inventing a new band definition. This is the one function in this module meant to sit inside a processing pipeline (dataset in, dataset out) rather than only produce a diagnostic table – the closest analogue here to flag_motion_susceptible_band() and mask_outside_ztem_band().

Unlike those two functions, only masking is offered (no action="drop"): each AirborneEMRecord packages its admittance transfer function and any auxiliary per-frequency fields (variance, covariances, native apparent conductivity) around one shared period axis, and safely dropping frequencies would require rebuilding all of them consistently. Masking with nan needs no such reconstruction and never confuses “known bad” with a physical zero.

Parameters:
  • dataset (AirborneEMDataset or AirborneEMLine) – Anything accepted by ensure_mobilemt_dataset().

  • band_hz ((float, float), optional) – Explicit (low, high) band in Hz. Mutually exclusive with system_spec; when neither is given, a default MobileMTSystemSpec’s nominal_frequency_range_hz is used.

  • system_spec (MobileMTSystemSpec, optional) – Survey-specific system metadata to read the band from.

  • inplace (bool, default False) – When False (default), a deep copy of dataset is masked and returned, leaving the input untouched.

Returns:

The (optionally new) dataset with out-of-band admittance values and native apparent-conductivity samples set to nan.

Return type:

AirborneEMDataset

Raises:
  • ValueError – If both band_hz and system_spec are given.

  • TypeError – If system_spec is given and is not a MobileMTSystemSpec.

pycsamt.emtools.plot_mobilemt_admittance_profile(dataset, *, line_id=None, component='det', part='abs', frequency_hz=None, period_s=None, figsize=(9.5, 4.0), ax=None)#

Plot one admittance component along one flight line.

Parameters:
  • dataset (AirborneEMDataset or AirborneEMLine) – Anything accepted by ensure_mobilemt_dataset().

  • line_id (str, optional) – Flight line to plot; defaults to the first line in dataset.

  • component ({"xx", "xy", "yx", "yy", "hzx", "hzy", "det"}, default "det") – Admittance entry to plot, or "det" for the horizontal 2x2 determinant (see admittance_determinant_table()).

  • part ({"real", "imag", "abs"}, default "abs")

  • frequency_hz (float, optional) – Reference frequency/period; nearest available value is used per sample. At most one may be given; the median frequency is used when neither is given.

  • period_s (float, optional) – Reference frequency/period; nearest available value is used per sample. At most one may be given; the median frequency is used when neither is given.

  • figsize ((float, float), default (9.5, 4.0)) – Used only when ax is not supplied.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

pycsamt.emtools.plot_mobilemt_conductivity_psection(dataset, *, line_id=None, source='theoretical', cmap='viridis', clim=None, clim_pct=(2.0, 98.0), figsize=(9.0, 5.0), ax=None)#

Plot an apparent-conductivity pseudosection for one flight line.

Parameters:
  • dataset (AirborneEMDataset or AirborneEMLine) – Anything accepted by ensure_mobilemt_dataset().

  • line_id (str, optional) – Flight line to plot; defaults to the first line in dataset.

  • source ({"theoretical", "native"}, default "theoretical") – "theoretical" plots admittance_determinant_table()’s derived theoretical_sigma_a_Sm (see the module docstring for the caveat); "native" plots the vendor-delivered apparent_conductivity_native_Sm field, when present.

  • cmap (str, default "viridis")

  • clim ((float, float), optional) – Explicit color limits; overrides clim_pct.

  • clim_pct ((float, float), default (2.0, 98.0)) – Percentile color limits when clim is not given.

  • figsize ((float, float), default (9.0, 5.0)) – Used only when ax is not supplied.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

Raises:

ValueError – If source is not "theoretical" or "native".

pycsamt.emtools.plot_mobilemt_skew_profile(dataset, *, line_id=None, frequency_hz=None, period_s=None, figsize=(9.5, 4.0), ax=None)#

Plot the admittance skew profile along one flight line.

See admittance_skew_table() for the underlying formula.

Parameters:
  • dataset (AirborneEMDataset or AirborneEMLine) – Anything accepted by ensure_mobilemt_dataset().

  • line_id (str, optional) – Flight line to plot; defaults to the first line in dataset.

  • frequency_hz (float, optional) – Reference frequency/period; nearest available value is used per sample. At most one may be given; the median frequency is used when neither is given.

  • period_s (float, optional) – Reference frequency/period; nearest available value is used per sample. At most one may be given; the median frequency is used when neither is given.

  • figsize ((float, float), default (9.5, 4.0)) – Used only when ax is not supplied.

  • ax (matplotlib.axes.Axes, optional) – Existing axes to draw on.

Return type:

matplotlib.axes.Axes

2.14.2. Processing and QC#

pycsamt.emtools.afmag

AFMAG-specific processing, diagnostics, and plotting.

pycsamt.emtools.ztem

ZTEM-specific spatial-domain processing, diagnostics, and plotting.

pycsamt.emtools.mobilemt

MobileMT-specific processing, diagnostics, and plotting.

pycsamt.emtools.inspect

pycsamt.emtools.qc

Quality-control confidence ratios for EM transfer functions.

pycsamt.emtools.frequency

pycsamt.emtools.ss

pycsamt.emtools.remove_noise

pycsamt.emtools.diag

Polar uncertainty diagnostics for CSAMT impedance data.

pycsamt.emtools.spectra

pycsamt.emtools.spectra

pycsamt.emtools.plot

pycsamt.emtools.overview

Combined single-station MT/CSAMT response overview.

2.14.3. Tensor and Dimensionality#

pycsamt.emtools.impedance

pycsamt.emtools.tensor

pycsamt.emtools.tf

pycsamt.emtools.gb

pycsamt.emtools.skew

pycsamt.emtools.strike

pycsamt.emtools.dimensionality

pycsamt.emtools.anisotropy

3-D axial anisotropy analysis for CSAMT impedance tensor data.

pycsamt.emtools.gradient_imaging

Gradient-based apparent resistivity pseudo-sections for CSAMT.

2.14.4. Source and Field-Zone Effects#

pycsamt.emtools.fieldzone

pycsamt.emtools.source_array

Phased-array (PAS) transmitter design and radiation pattern analysis for CSAMT.

pycsamt.emtools.source_effects

CSAMT source overprint and shadow effect analysis.

pycsamt.emtools.csumt

Controlled-source ultra-audio MT depth and survey-planning tools.

pycsamt.emtools.lcurve

pycsamt.emtools.legacy

2.14.5. Rose Diagram Styling#

class pycsamt.emtools.RoseStyle(bar_style='gradient', bar_color='#e53935', bar_alpha=0.88, bar_edgecolor='none', bar_edgelw=0.4, cmap='YlOrRd', outer_ring_lw=2.5, outer_ring_color='0.12', n_rings=3, ring_color='0.75', ring_ls=':', ring_lw=0.7, ring_labels=None, ring_label_angle=22.5, ring_label_fontsize=7.0, ring_label_color='0.30', ring_label_fmt='{:.0f}', spoke_every=45.0, spoke_color='0.72', spoke_ls=':', spoke_lw=0.7, compass_labels='NESW', compass_fontsize=8.5, compass_color='0.15', compass_fontweight='bold', show_mean=True, mean_color='crimson', mean_lw=2.2, mean_ls='-', show_secondary=True, secondary_color=None, secondary_ls='--', secondary_lw=None, show_annotation=True, annotation_pos=(0.05, 0.93), annotation_fontsize=8.0, annotation_bg='white', annotation_ec='0.25', show_n=True)#

Bases: object

Visual style bundle shared by all pycsamt rose diagram functions.

Every attribute maps 1-to-1 to a keyword argument accepted by plot_strike_rose() and plot_phase_tensor_rose(). Pass an instance via the style= parameter of either function; individual kwargs still override the style.

Variables:
  • bar_style ({"gradient", "solid", "bands"}) – "gradient" — bar height mapped to cmap; "solid" — uniform bar_color; "bands" — one colour per period sub-band (stacked).

  • bar_color (str) – Bar fill colour for bar_style="solid".

  • bar_alpha (float) – Bar opacity (0–1).

  • bar_edgecolor (str) – Bar edge colour. "none" disables edges.

  • bar_edgelw (float) – Bar edge line-width.

  • cmap (str) – Matplotlib colormap name for bar_style="gradient".

  • outer_ring_lw (float) – Line-width of the bold outer bounding circle.

  • outer_ring_color (str) – Colour of the outer bounding circle.

  • n_rings (int) – Number of concentric reference rings inside the plot.

  • ring_color (str) – Colour of concentric rings.

  • ring_ls (str) – Line-style of concentric rings (":" dotted, "--" dashed, …).

  • ring_lw (float) – Line-width of concentric rings.

  • ring_labels (list[float] or None) – Explicit count values to annotate on the rings (e.g. [25, 50, 75, 100]). None → evenly auto-spaced.

  • ring_label_angle (float) – Clockwise degrees from North where count labels appear.

  • ring_label_fontsize (float) – Font size for ring count annotations.

  • ring_label_color (str) – Colour for ring count annotations.

  • ring_label_fmt (str) – Python format string for ring labels (e.g. "{:.0f}").

  • spoke_every (float) – Angular spacing (degrees) between radial spokes / tick marks.

  • spoke_color (str) – Colour of radial spokes.

  • spoke_ls (str) – Line-style of radial spokes.

  • spoke_lw (float) – Line-width of radial spokes.

  • compass_labels ({"NESW", "degrees", "none"}) – Perimeter labels. "NESW" → N/E/S/W; "degrees" → 0°/45°/…; "none" → hidden.

  • compass_fontsize (float) – Font size for compass / degree perimeter labels.

  • compass_color (str) – Colour for perimeter labels.

  • compass_fontweight (str) – Font weight for perimeter labels ("bold", "normal", …).

  • show_mean (bool) – Draw the mean direction as a diameter line through the centre.

  • mean_color (str) – Colour of the mean direction line.

  • mean_lw (float) – Line-width of the mean direction line.

  • mean_ls (str) – Line-style of the mean direction line.

  • show_secondary (bool) – Draw the 180°-conjugate (axial-symmetry) mean line.

  • secondary_color (str or None) – Colour of the conjugate line. None → same as mean_color.

  • secondary_ls (str) – Line-style of the conjugate line.

  • secondary_lw (float or None) – Line-width of the conjugate line. None → same as mean_lw.

  • show_annotation (bool) – Show the text box with mean angle and count.

  • annotation_pos ((float, float)) – Axes-fraction (x, y) position of the annotation box.

  • annotation_fontsize (float) – Font size of the annotation text.

  • annotation_bg (str) – Background colour of the annotation box.

  • annotation_ec (str) – Edge colour of the annotation box.

  • show_n (bool) – Append n = N (data-point count) to the annotation text.

Parameters:
  • bar_style (str)

  • bar_color (str)

  • bar_alpha (float)

  • bar_edgecolor (str)

  • bar_edgelw (float)

  • cmap (str)

  • outer_ring_lw (float)

  • outer_ring_color (str)

  • n_rings (int)

  • ring_color (str)

  • ring_ls (str)

  • ring_lw (float)

  • ring_labels (list[float] | None)

  • ring_label_angle (float)

  • ring_label_fontsize (float)

  • ring_label_color (str)

  • ring_label_fmt (str)

  • spoke_every (float)

  • spoke_color (str)

  • spoke_ls (str)

  • spoke_lw (float)

  • compass_labels (str)

  • compass_fontsize (float)

  • compass_color (str)

  • compass_fontweight (str)

  • show_mean (bool)

  • mean_color (str)

  • mean_lw (float)

  • mean_ls (str)

  • show_secondary (bool)

  • secondary_color (str | None)

  • secondary_ls (str)

  • secondary_lw (float | None)

  • show_annotation (bool)

  • annotation_pos (tuple)

  • annotation_fontsize (float)

  • annotation_bg (str)

  • annotation_ec (str)

  • show_n (bool)

bar_style: str = 'gradient'#
bar_color: str = '#e53935'#
bar_alpha: float = 0.88#
bar_edgecolor: str = 'none'#
bar_edgelw: float = 0.4#
cmap: str = 'YlOrRd'#
outer_ring_lw: float = 2.5#
outer_ring_color: str = '0.12'#
n_rings: int = 3#
ring_color: str = '0.75'#
ring_ls: str = ':'#
ring_lw: float = 0.7#
ring_labels: list[float] | None = None#
ring_label_angle: float = 22.5#
ring_label_fontsize: float = 7.0#
ring_label_color: str = '0.30'#
ring_label_fmt: str = '{:.0f}'#
spoke_every: float = 45.0#
spoke_color: str = '0.72'#
spoke_ls: str = ':'#
spoke_lw: float = 0.7#
compass_labels: str = 'NESW'#
compass_fontsize: float = 8.5#
compass_color: str = '0.15'#
compass_fontweight: str = 'bold'#
show_mean: bool = True#
mean_color: str = 'crimson'#
mean_lw: float = 2.2#
mean_ls: str = '-'#
show_secondary: bool = True#
secondary_color: str | None = None#
secondary_ls: str = '--'#
secondary_lw: float | None = None#
show_annotation: bool = True#
annotation_pos: tuple = (0.05, 0.93)#
annotation_fontsize: float = 8.0#
annotation_bg: str = 'white'#
annotation_ec: str = '0.25'#
show_n: bool = True#
copy(**overrides)#

Return a shallow copy with overrides applied.

Parameters:

**overrides (Any) – Any RoseStyle attribute name and its new value.

Returns:

A new RoseStyle instance.

Return type:

RoseStyle

Raises:

ValueError – If an unknown attribute name is passed.

Examples

>>> rs = RoseStyle()
>>> rs2 = rs.copy(compass_labels="degrees", show_secondary=False)

2.14.6. Public QC Plot Functions#

pycsamt.emtools.overlay_noise_cone(ax, period, lo, hi, *, color='0.6', alpha=0.18)#

Overlay lower and upper noise envelopes on an existing period axis.

Parameters:
pycsamt.emtools.overlay_spectral_holes(ax, sites, *, thresh_dec=0.3, recursive=True, on_dup='replace', strict=False, verbose=0)#

Highlight gaps in spectral coverage on an existing QC plot.

Parameters:
pycsamt.emtools.plot_consistency_fan(sites, *, station=None, other=None, comps=('xy', 'yx'), pcts=(10.0, 50.0, 90.0), n_draws=200, figsize=(8.6, 4.2), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot cross-station response consistency as a fan diagram.

Parameters:
Return type:

Axes

pycsamt.emtools.plot_coverage_psection(sites, *, metric='presence', alpha_by='none', section='dynamic', figsize=None, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot frequency coverage and data availability as a pseudosection.

Parameters:
Return type:

Axes

pycsamt.emtools.plot_qc_quicklook(sites, *, axes=None, figsize=(10.0, 8.0), recursive=True, on_dup='replace', strict=False, verbose=0)#

Create a compact multi-panel quality-control summary for a survey.

Parameters:
pycsamt.emtools.plot_snr_hist(sites, *, bins=40, figsize=(7.2, 3.6), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Plot the distribution of signal-to-noise ratios across survey data.

Parameters:
Return type:

Axes

pycsamt.emtools.plot_xyyx_crossover_map(sites, *, figsize=(9.0, 4.6), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)#

Map XY/YX crossover behaviour across stations and frequencies.

Parameters:
Return type:

Axes