Note
Go to the end to download the full example code.
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)",
)

<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$")

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