pycsamt.emtools#

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

See also

../emtools/index for narrative, runnable examples built module by module (currently: ../emtools/tf).

pycsamt.emtools — public API#

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

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)
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', 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.

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.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.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='name', 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 ({"name", "lon", "lat"}) – Station ordering along the x-axis.

  • 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.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='name', 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 ({"name", "lon", "lat"}) – Station ordering along the x-axis.

  • 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 ≤ ρ_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 |ε| 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.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, 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.

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, 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.

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_profile(sites, *, method='presence', ci_hi=0.95, ci_lo=0.85, shade_recoverable=True, shade_mode='score', annotate_low=True, station_labels=True, station_label_step=None, show_errorbars=True, smart_ylim=True, ylim=None, weights=None, spacing_m=200.0, 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.

  • 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) – Fallback station spacing [m] used when no coordinate metadata is available on the EDI objects.

  • 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)

  • annotate_low (bool)

  • 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.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_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_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_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_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

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.notch_powerline(sites, *, mains_hz=50.0, n_harm=30, tol_hz=0.08, mode='interp', also='both', inplace=False, recursive=True, on_dup='replace', strict=False, verbose=0)#

Suppress mains-frequency harmonics in impedance and tipper data.

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='lon', 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, default 'lon') – Along-line order axis. 'lon', 'lat', or 'name'.

  • 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='lon', 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, default 'lon') – Along-line order axis for AMA estimation.

  • 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, 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.

  • 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='lon', 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 ({"lon", "lat", "name"}, default="lon") – Station ordering for the AMA spatial trend.

  • 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='lon', 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 ({"lon", "lat", "name"})

  • 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='auto', 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)#

Three-panel rose diagram: Strike (Z), PT Azimuth, and Tipper Strike.

Produces a publication-quality figure with one polar rose per analysis type, analogous to the MTPy StrikeAnalysis plot. All three panels share the same RoseStyle so colours remain visually consistent. Each panel carries a coloured title box to distinguish the three quantities at a glance.

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 three polar axes: Strike (Z), PT Azimuth, 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=True, 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>, edgecolor=<object object>, linewidth=<object object>, alpha=<object object>, 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 True) – When True (MT convention) long period (low frequency) is at the top of the figure; False flips the y-axis.

  • 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 ("cell" | "unity" | "abs") –

    Sizing strategy:

    "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.

  • 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.

  • skew_threshold (float or None, default 3.0) – |β| threshold (degrees) separating 1-D/2-D from 3-D structure. Used to cap clim symmetrically when clim is None, and to highlight 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°) in a reserved legend strip above the data, as a scale indicator.

  • 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.

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, 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, figsize=(2.5, 2.5), ax=None)#
Parameters:
Return type:

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_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)

  • 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='', 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.

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 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_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, 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), figsize=(6.0, 4.6), ax=None)#
Parameters:
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.])

Processing and QC#

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

Tensor and Dimensionality#

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

Public QC Plot Functions#

pycsamt.emtools.qc.overlay_noise_cone(ax, period, lo, hi, *, color='0.6', alpha=0.18)[source]#

Overlay lower and upper noise envelopes on an existing period axis.

Parameters:
pycsamt.emtools.qc.overlay_spectral_holes(ax, sites, *, thresh_dec=0.3, recursive=True, on_dup='replace', strict=False, verbose=0)[source]#

Highlight gaps in spectral coverage on an existing QC plot.

Parameters:
pycsamt.emtools.qc.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)[source]#

Plot cross-station response consistency as a fan diagram.

Parameters:
Return type:

Axes

pycsamt.emtools.qc.plot_coverage_psection(sites, *, metric='presence', alpha_by='none', section='dynamic', figsize=None, recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)[source]#

Plot frequency coverage and data availability as a pseudosection.

Parameters:
Return type:

Axes

pycsamt.emtools.qc.plot_qc_quicklook(sites, *, axes=None, figsize=(10.0, 8.0), recursive=True, on_dup='replace', strict=False, verbose=0)[source]#

Create a compact multi-panel quality-control summary for a survey.

Parameters:
pycsamt.emtools.qc.plot_snr_hist(sites, *, bins=40, figsize=(7.2, 3.6), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)[source]#

Plot the distribution of signal-to-noise ratios across survey data.

Parameters:
Return type:

Axes

pycsamt.emtools.qc.plot_xyyx_crossover_map(sites, *, figsize=(9.0, 4.6), recursive=True, on_dup='replace', strict=False, verbose=0, ax=None)[source]#

Map XY/YX crossover behaviour across stations and frequencies.

Parameters:
Return type:

Axes