Source effects (CSAMT)#

A controlled-source survey adds a distortion that natural-source MT never sees: near the transmitter, at low frequency, the field is near-field — the plane-wave assumption breaks down and apparent resistivity rises with a tell-tale 45° slope. pycsamt.emtools detects this source overprint and corrects it. This wave diagnoses the overprint first, then removes it.

Detect the overprint#

detect_source_overprint() flags, per station, the low-frequency band where the response departs from plane-wave behaviour — the diagnostic you check before deciding to correct.

import numpy as np
from _corr_data import curves, demo_line, plot_before_after

from pycsamt.emtools import (
    correct_near_field,
    detect_source_overprint,
    source_overprint_table,
)

S = demo_line("L18PLT")
raw = curves(S, "rho")

flags = detect_source_overprint(S, recursive=False)
print("detect_source_overprint ->", type(flags).__name__)
print(flags.head(10).to_string(index=False))
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-015U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-008U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-003A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-016A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-025A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-023A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-018A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-010U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-002U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-012A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-021B'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-017U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-022U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-019U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-011A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-023V'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-009A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-024U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-005U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-006A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-007U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-004A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-001A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-014A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-021U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-020A'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-022V'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
/opt/build/repo/docs/examples/corrections/plot_3_source_effects.py:31: UserWarning: detect_source_overprint: no source offset for '18-013U'; beta_pct / kr will be NaN.
  flags = detect_source_overprint(S, recursive=False)
detect_source_overprint -> DataFrame
station  freq_hz  period_s  offset_m  rho_a_ohmm  kr  beta_pct  overprint_flag
18-015U  10400.0  0.000096       NaN  226.111249 NaN       NaN           False
18-015U   8707.0  0.000115       NaN  260.583966 NaN       NaN           False
18-015U   7289.0  0.000137       NaN  277.396794 NaN       NaN           False
18-015U   6102.0  0.000164       NaN  280.267837 NaN       NaN           False
18-015U   5108.0  0.000196       NaN  282.055133 NaN       NaN           False
18-015U   4277.0  0.000234       NaN  186.347747 NaN       NaN           False
18-015U   3580.0  0.000279       NaN  242.552580 NaN       NaN           False
18-015U   2997.0  0.000334       NaN  151.089907 NaN       NaN           False
18-015U   2509.0  0.000399       NaN  379.216051 NaN       NaN           False
18-015U   2101.0  0.000476       NaN  469.839144 NaN       NaN           False

A survey-wide overprint table#

source_overprint_table() summarises the diagnostic across the whole line — how many stations are affected and over what period band — so you can judge whether a near-field correction is warranted.

table = source_overprint_table(S, recursive=False)
print(table.head(12).to_string(index=False))
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-015U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-008U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-003A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-016A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-025A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-023A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-018A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-010U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-002U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-012A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-021B'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-017U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-022U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-019U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-011A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-023V'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-009A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-024U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-005U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-006A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-007U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-004A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-001A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-014A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-021U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-020A'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-022V'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
/opt/build/repo/pycsamt/emtools/source_effects.py:409: UserWarning: detect_source_overprint: no source offset for '18-013U'; beta_pct / kr will be NaN.
  detail = detect_source_overprint(
station  n_freq  offset_m  beta_max_pct  beta_mean_pct  n_overprint  overprint_frac  lf_slope  hf_slope  slope_delta  overprint_flag
18-015U      53       NaN           NaN            NaN            0             0.0       NaN -0.058501          NaN           False
18-008U      53       NaN           NaN            NaN            0             0.0       NaN -0.550839          NaN           False
18-003A      53       NaN           NaN            NaN            0             0.0       NaN -0.321321          NaN           False
18-016A      53       NaN           NaN            NaN            0             0.0       NaN -0.384529          NaN           False
18-025A      53       NaN           NaN            NaN            0             0.0       NaN -0.298242          NaN           False
18-023A      53       NaN           NaN            NaN            0             0.0       NaN -0.099550          NaN           False
18-018A      53       NaN           NaN            NaN            0             0.0       NaN -0.304880          NaN           False
18-010U      53       NaN           NaN            NaN            0             0.0       NaN -0.250613          NaN           False
18-002U      53       NaN           NaN            NaN            0             0.0       NaN -0.382845          NaN           False
18-012A      53       NaN           NaN            NaN            0             0.0       NaN -0.416019          NaN           False
18-021B      53       NaN           NaN            NaN            0             0.0       NaN -0.139347          NaN           False
18-017U      53       NaN           NaN            NaN            0             0.0       NaN -0.405574          NaN           False

Correct the near field#

correct_near_field() removes the near-field rise given the transmitter-receiver offset (source_offset, metres). It rescales the affected low-frequency band back toward the plane-wave response, leaving the far-field high frequencies untouched.

S_nf = correct_near_field(S, source_offset=5000.0, recursive=False)
nf = curves(S_nf, "rho")

stations = list(raw)
pick = [stations[2], stations[len(stations) // 2], stations[-3]]
plot_before_after(
    raw,
    nf,
    pick,
    quantity="rho",
    labels=("raw", "near-field corrected"),
    title="Near-field / source-overprint correction (source_offset = 5 km)",
)
Near-field / source-overprint correction (source_offset = 5 km), 18-003A, 18-011A, 18-020A
<Figure size 1200x420 with 3 Axes>

Where the correction acted#

Imaging the change station-by-period confirms the correction is confined to the low-frequency (long-period) near-field band and leaves the far field alone — the signature of a physically-sensible source correction.

names = [s for s in raw if np.array_equal(raw[s][0], nf[s][0])]
periods = raw[names[0]][0]
delta = np.column_stack(
    [np.log10(nf[s][1]) - np.log10(raw[s][1]) for s in names]
)
vmax = float(np.nanpercentile(np.abs(delta), 98)) or 0.1
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(10, 4.6), constrained_layout=True)
im = ax.pcolormesh(
    np.arange(len(names)),
    np.log10(periods),
    delta,
    cmap="PuOr_r",
    vmin=-vmax,
    vmax=vmax,
    shading="auto",
)
ax.set_xticks(range(len(names)))
ax.set_xticklabels(names, rotation=90, fontsize=6)
ax.set_ylabel(r"$\log_{10}$ period (s)")
ax.set_title(r"$\Delta\log_{10}\rho_a$ from the near-field correction")
fig.colorbar(im, ax=ax, label=r"$\Delta\log_{10}\rho_a$")
$\Delta\log_{10}\rho_a$ from the near-field correction
<matplotlib.colorbar.Colorbar object at 0x7f2a998c2150>

Takeaway. Always detect the source overprint before correcting, set source_offset from the real survey geometry, and confirm the change is confined to the low-frequency band. With the source distortion removed, the tensor is ready to be rotated onto strike — tensor rotation.

Total running time of the script: (0 minutes 1.013 seconds)

Gallery generated by Sphinx-Gallery