2.14.2.1. pycsamt.emtools.afmag#

AFMAG-specific processing, diagnostics, and plotting.

AFMAG (Audio-Frequency Magnetics) is a passive, magnetic-field-only method: there is no electric-field channel, so the usual impedance tensor Z is not part of its data model. This module’s own “Tilt-angle diagnostics” and “Motion-coupling QC” sections below are built, like pycsamt.emtools.tf, entirely on Site.tipper (the complex (Tzx, Tzy) pair, Hz = Tzx*Hx + Tzy*Hy) — they never touch Site.z, and apply equally to ground data and to a ZTEM-style airborne tipper (see pycsamt.airborne.ztem, which documents that equivalence explicitly).

That is deliberately not a claim that every real AFMAG measurement is a (Tzx, Tzy) tipper pair, though. pycsamt.airborne.afmag keeps two genuinely different response families, neither of them tipper-shaped:

  • the historical two-coil comparator (Ward 1959) reports a bare scalar tilt/deflection angle – one real number per frequency, no input/output channels, no polarization-ellipse decomposition possible at all;

  • modern tensor AFMAG/AirMt reports a complex (nf, 3, 2) interstation magnetic transfer function (ground-reference Hx,Hy -> airborne Hx,Hy,Hz) plus a derived rotation-invariant amplification parameter – six components, not two.

Section “AFMAG-family tilt diagnostics” below handles those two real shapes explicitly and separately (original_afmag_tilt_table(), airmt_tilt_angles()), reading them via AirborneSite’s afmag_tilt_deg/ interstation_tensor rather than forcing either into the tipper-shaped functions above.

Three genuinely different things live here side by side:

  • Sections “Tilt-angle diagnostics” and “Motion-coupling QC” are ordinary emtools functions built on the ZTEM-style tipper: they take sites (coerced through ensure_any_sites(), like pycsamt.emtools.ztem) and either return a Sites-or- AirborneSites container (the one mutating function, flag_motion_susceptible_band()) or a tidy pandas.DataFrame (matching the convention already used by psd_table(), coherence_table(), and friends — not every emtools “processing” function returns a container; only the data-mutating ones do).

  • Section “AFMAG-family tilt diagnostics” is built on the two real AFMAG-family shapes described above instead, and therefore only ever accepts AirborneSites (there is no ground-EDI equivalent of either shape).

  • Section “Motion-coupling physics” implements the rotation-matrix / Euler-angle motion-induced-noise method of Liu et al. (2018) directly from their Eq. 1-14. These functions are deliberately not Sites-based: they operate on raw attitude (yaw/pitch/roll) time series and geomagnetic field geometry, neither of which Site carries (it is strictly frequency-domain, with no time axis and no attitude field — the only attitude container in pyCSAMT today is NavigationTrack, which belongs to the unrelated pycsamt.airborne line/dataset model). They are included here, as pure reusable physics, because the paper’s actual deliverable is exactly this math; forcing it onto Sites would require either fabricating time-series storage that does not exist or silently doing nothing. The one bridge between the two halves is motion_susceptibility_table() / flag_motion_susceptible_band(), which use the Section-1 math together with a nominal attitude-amplitude envelope to flag which stations are most exposed to motion noise in their low-frequency band — the paper’s own finding is that this noise concentrates at low frequency and depends on attitude amplitude and geomagnetic geometry, not on a raw time series.

No apparent-resistivity/conductivity formula is derived from tilt angle here: unlike full MT, classical AFMAG tilt data has no simple closed form for that (nothing in Ward 1959 or Liu et al. 2018 provides one), so none is invented.

References

[Ward1959]

Ward, S. H. (1959). AFMAG – Airborne and ground. Geophysics, 24(4), 761-787.

[Liu2018]

Liu, F., Huang, L., Pang, Y., Shi, Z., Xiao, P., & Fang, G. (2018). Airborne AFMAG method motion-induced noise simulation and suppression. Journal of Applied Geophysics, 158, 129-138.

Functions

afmag_tilt_angles(sites, *[, recursive, ...])

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

airmt_tilt_angles(sites, *[, recursive, ...])

Per-station, per-frequency tensor AFMAG/AirMt tilt angles.

coil_normal_direction(yaw, pitch, roll, *[, ...])

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

correct_motion_induced_noise(measured, ...)

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

euler_rotation_matrix(yaw, pitch, roll, *[, ...])

Return the ZYX "airline convention" attitude rotation matrix.

flag_motion_susceptible_band(sites, *, ...)

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

geomagnetic_field_direction(inclination, ...)

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

motion_coupling_angle(yaw, pitch, roll, ...)

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

motion_coupling_cosine(yaw, pitch, roll, ...)

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

motion_susceptibility_table(sites, *, ...[, ...])

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

original_afmag_conductor_diagnostics(sites, *)

Ward (1959)-style dual-frequency conductor diagnostics.

original_afmag_tilt_table(sites, *[, ...])

Per-station, per-frequency original comparator AFMAG tilt table.

plot_afmag_correction_comparison(before_sites)

Plot before/after/delta AFMAG tilt pseudosections.

plot_afmag_tilt_polar(sites, *[, station, ...])

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

plot_afmag_tilt_profile(sites, *[, ...])

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

plot_afmag_tilt_psection(sites, *[, ...])

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

plot_airmt_tilt_profile(sites, *[, ...])

Plot the tensor AFMAG/AirMt flight-line tilt-angle profile.

plot_airmt_tilt_psection(sites, *[, ...])

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

plot_motion_coupling(yaw, pitch, roll, ...)

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

plot_motion_susceptibility_map(sites, *, ...)

Map each station's motion-noise susceptibility score.

plot_original_afmag_dual_frequency_profile(...)

Ward (1959) Fig.

plot_original_afmag_tilt_profile(sites, *[, ...])

Plot the original comparator AFMAG flight-line tilt profile.

simulate_motion_induced_voltage(cos_theta, *, dt)

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

pycsamt.emtools.afmag.euler_rotation_matrix(yaw, pitch, roll, *, degrees=True)[source]

Return the ZYX “airline convention” attitude rotation matrix.

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

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

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

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

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

Returns:

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

Return type:

ndarray of shape (…, 3, 3)

Notes

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

Examples

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

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

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

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

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

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

Returns:

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

Return type:

ndarray of shape (…, 3)

Examples

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

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

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

Parameters:
  • yaw (array-like) – Forwarded to euler_rotation_matrix().

  • pitch (array-like) – Forwarded to euler_rotation_matrix().

  • roll (array-like) – Forwarded to euler_rotation_matrix().

  • degrees (bool, default True) – Forwarded to euler_rotation_matrix().

Returns:

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

Return type:

ndarray of shape (…, 3)

pycsamt.emtools.afmag.motion_coupling_cosine(yaw, pitch, roll, inclination, declination, *, degrees=True)[source]

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

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

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

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

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

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

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

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

Returns:

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

Return type:

ndarray

Examples

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

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

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

See motion_coupling_cosine() for the underlying computation.

Parameters:
Return type:

ndarray

pycsamt.emtools.afmag.simulate_motion_induced_voltage(cos_theta, *, dt, gain=1.0, axis=-1)[source]

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

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

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

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

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

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

Returns:

Simulated noise voltage, same shape as cos_theta.

Return type:

ndarray

pycsamt.emtools.afmag.correct_motion_induced_noise(measured, predicted_noise)[source]

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

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

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

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

Returns:

measured - predicted_noise.

Return type:

ndarray

Raises:

ValueError – If measured and predicted_noise have different shapes.

pycsamt.emtools.afmag.afmag_tilt_angles(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

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

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

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

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

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

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

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

Returns:

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

Return type:

pandas.DataFrame

pycsamt.emtools.afmag.airmt_tilt_angles(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

Per-station, per-frequency tensor AFMAG/AirMt tilt angles.

Applies the exact same real/imaginary tilt-angle decomposition as afmag_tilt_angles() (_tilt_components()) – but to the (Hzx, Hzy) row of interstation_tensor rather than to tipper. That row is physically the same Hz-to-horizontal relation a ZTEM tipper describes; the other two rows (Hxx, Hxy, Hyx, Hyy, the horizontal-to-horizontal part of the tensor) are not used here.

Parameters:
  • sites (AirborneSites-like) – Anything accepted by ensure_asites(). Sites with no interstation_transfer_functions transfer function attached (including original-comparator AFMAG sites – see original_afmag_tilt_table() for those) are silently omitted, not filled with a fabricated zero.

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

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

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

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

Returns:

Same columns as afmag_tilt_angles(): station, freq, period, tilt_real_deg, tilt_real_azimuth_deg, tilt_imag_deg, tilt_imag_azimuth_deg, tilt_resultant_deg.

Return type:

pandas.DataFrame

pycsamt.emtools.afmag.original_afmag_tilt_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

Per-station, per-frequency original comparator AFMAG tilt table.

Reads afmag_tilt_deg directly – the historical comparator measurement already is a tilt angle in degrees, not a complex tipper pair, so there is no arctan/decomposition step here the way there is in afmag_tilt_angles()/airmt_tilt_angles(): a single real number per frequency is all the instrument ever reported (see the module docstring).

Parameters:
  • sites (AirborneSites-like) – Anything accepted by ensure_asites(). Sites with no afmag_tilt transfer function attached are silently omitted, not filled with a fabricated zero.

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

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

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

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

Returns:

Columns: station, freq, period, tilt_deg.

Return type:

pandas.DataFrame

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

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

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

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

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

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

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

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

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

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

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

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

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

Returns:

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

Return type:

pandas.DataFrame

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Returns:

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

Return type:

Sites

Raises:

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

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

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

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

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

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

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

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

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

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

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

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

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

Return type:

matplotlib.axes.Axes

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

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

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

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

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

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

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

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

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

  • station_preset (str) – See plot_afmag_tilt_profile().

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

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

Return type:

matplotlib.axes.Axes

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

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

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

Parameters:
  • sites (Sites-like)

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

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

  • cmap (str, default "viridis")

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

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

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

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

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

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

Return type:

matplotlib.axes.Axes

pycsamt.emtools.afmag.plot_airmt_tilt_profile(sites, *, component='real', frequency_hz=None, period_s=None, figsize=(9.5, 4.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)[source]

Plot the tensor AFMAG/AirMt flight-line tilt-angle profile.

Direct AirMt-tensor analogue of plot_afmag_tilt_profile(); see airmt_tilt_angles() for the underlying table.

Parameters:
  • sites (AirborneSites-like) – Anything accepted by ensure_asites().

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

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

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

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

  • station_label_step (int | None) – Forwarded to style_for() via _apply_station_rendering().

  • station_preset (str) – Forwarded to style_for() via _apply_station_rendering().

  • station_style (Any | None) – Forwarded to style_for() via _apply_station_rendering().

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

Return type:

matplotlib.axes.Axes

pycsamt.emtools.afmag.plot_airmt_tilt_psection(sites, *, component='resultant', cmap='RdYlBu_r', clim=None, clim_pct=(2.0, 98.0), show_grid=True, show_contour=True, n_contour_levels=3, figsize=(9.0, 5.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)[source]

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

Direct AirMt-tensor analogue of plot_afmag_tilt_psection(), reading airmt_tilt_angles() instead – the same station-x-log-period layout every other airborne technology in this project already gets (plot_ztem_divergence_psection(), plot_mobilemt_conductivity_psection()), closing the one real gap left when only a single-frequency profile (plot_airmt_tilt_profile()) existed for this technology. The underlying image stays a plain imshow() grid (matching the rest of this module’s psection functions), with two optional overlays matching the cell-boundary/level-line convention already used for pycsamt.emtools.fieldzone’s own imshow/contour pseudosections: cell-boundary gridlines (show_grid) and white contour level lines with inline labels (show_contour) tracing the same imaged field.

Parameters:
  • sites (AirborneSites-like) – Anything accepted by ensure_asites().

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

  • cmap (str, default "RdYlBu_r")

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

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

  • show_grid (bool, default True) – Draw thin gridlines at every station/period cell boundary.

  • show_contour (bool, default True) – Overlay n_contour_levels - 2 evenly-spaced contour lines (dropping the two outermost levels, which would otherwise hug the colour-scale edges) with inline value labels, tracing the same imaged field – a visual aid for the broad trend on top of a necessarily coarse, noisy station/period grid, not a claim of smooth structure between stations.

  • n_contour_levels (int, default 3) – Number of evenly-spaced levels spanning the image’s own colour range before dropping the two outermost; must be at least 3 for any contour line to be drawn.

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

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

  • station_preset (str) – See plot_afmag_tilt_profile().

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

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

Return type:

matplotlib.axes.Axes

pycsamt.emtools.afmag.plot_original_afmag_tilt_profile(sites, *, frequency_hz=None, period_s=None, figsize=(9.5, 4.0), station_label_step=1, station_preset='pseudosection', station_style=None, ax=None)[source]

Plot the original comparator AFMAG flight-line tilt profile.

Direct original-comparator analogue of plot_afmag_tilt_profile(); see original_afmag_tilt_table() for the underlying table. There is no component argument here (unlike plot_afmag_tilt_profile()/plot_airmt_tilt_profile()): the historical measurement is a single real number per frequency, not a complex pair with a real/imaginary/resultant split.

Parameters:
  • sites (AirborneSites-like) – Anything accepted by ensure_asites().

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

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

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

  • station_label_step (int | None) – Forwarded to style_for() via _apply_station_rendering().

  • station_preset (str) – Forwarded to style_for() via _apply_station_rendering().

  • station_style (Any | None) – Forwarded to style_for() via _apply_station_rendering().

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

Return type:

matplotlib.axes.Axes

pycsamt.emtools.afmag.original_afmag_conductor_diagnostics(sites, *, freq_low_hz=None, freq_high_hz=None, recursive=True, on_dup='replace', strict=False, verbose=0)[source]

Ward (1959)-style dual-frequency conductor diagnostics.

Reproduces the two semi-quantitative measurements Ward’s own worked example computes from a dual-frequency tilt profile (Ward 1959, Fig. 16 and Results section (a)-(b)):

  • the along-line crossover position at each frequency – the station-to-station zero-crossing of the signed original_afmag_tilt_table() tilt angle nearest the profile’s own peak-to-trough swing, i.e. the conductor axis – and the shift between the two frequencies’ crossovers, which Ward reads as evidence of inhomogeneity along the conductor;

  • the peak-to-peak amplitude ratio between the two frequencies, \((\max-\min)_{\text{low}} / (\max-\min)_{\text{high}}\), Ward’s semi-quantitative conductivity indicator (his own worked example on a pyrite-pyrrhotite body gives \(0.74\); values range from 0, very poor, toward 1, excellent).

Ward’s further estimates – depth to the top of the conductor from the crossover slope, depth extent from the profile’s beyond-peak slope, and dip from profile asymmetry – all require a scale-model calibration curve that the paper does not give in closed form, so, matching this module’s stated policy of not inventing a formula the cited literature does not itself supply, they are not computed here.

Parameters:
  • sites (AirborneSites-like) – Anything accepted by ensure_asites().

  • freq_low_hz (float, optional) – The two frequencies to compare. Default to the minimum and maximum frequency actually present (Ward’s own instrument always operated 150 and 510 Hz together, but this stays general rather than hardcoding that pair).

  • freq_high_hz (float, optional) – The two frequencies to compare. Default to the minimum and maximum frequency actually present (Ward’s own instrument always operated 150 and 510 Hz together, but this stays general rather than hardcoding that pair).

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

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

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

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

Returns:

Keys: freq_low_hz, freq_high_hz, crossover_low_m, crossover_high_m (along-line position in metres, nan if the profile does not change sign), crossover_shift_m (crossover_high_m - crossover_low_m), peak_to_peak_low, peak_to_peak_high, peak_to_peak_ratio (degrees; low / high), and profile – a pandas.DataFrame with columns station, position_m, tilt_low_deg, tilt_high_deg, the shared input the companion plot function draws from.

Return type:

dict

Raises:

ValueError – If fewer than 2 distinct frequencies are present, or a requested freq_low_hz/freq_high_hz is not found.

pycsamt.emtools.afmag.plot_original_afmag_dual_frequency_profile(sites, *, freq_low_hz=None, freq_high_hz=None, figsize=(9.5, 4.5), ax=None)[source]

Ward (1959) Fig. 16-style dual-frequency tilt-angle profile.

Plots both frequencies together along real chainage – the classic AFMAG field product, not a single-frequency curve – and marks the crossover (“axis of conductor”) at each frequency using original_afmag_conductor_diagnostics(), the same interpretation Ward’s own worked example applies.

Parameters:
  • sites (AirborneSites-like) – Anything accepted by ensure_asites().

  • freq_low_hz (float, optional) – Forwarded to original_afmag_conductor_diagnostics().

  • freq_high_hz (float, optional) – Forwarded to original_afmag_conductor_diagnostics().

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

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

Return type:

matplotlib.axes.Axes

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

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

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

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

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

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

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

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

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

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

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

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

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

Returns:

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

Return type:

matplotlib.axes.Axes

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

Map each station’s motion-noise susceptibility score.

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

Parameters:
  • sites (Sites-like)

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

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

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

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

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

  • cmap (str, default "magma_r")

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

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

Return type:

matplotlib.axes.Axes

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

Plot before/after/delta AFMAG tilt pseudosections.

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

Parameters:
  • before_sites (Sites-like)

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

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

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

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

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

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

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

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

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

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

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

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

Return type:

matplotlib.Figure

Raises:

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