Correct Static Shift#
This tutorial shows a conservative static-shift correction workflow for EDI surveys. It is written for the common profile-processing case: you have already loaded and inspected a survey, you suspect that some apparent-resistivity curves are shifted vertically, and you want to estimate station-level correction factors before preparing inversion files.
Static-shift correction should be handled carefully. It changes impedance amplitudes and apparent-resistivity levels. It does not fix bad phases, frequency-dependent source effects, wrong station coordinates, incorrect component orientation, or a poorly converted EDI file. Always inspect before and after correction.
What You Will Learn#
After this tutorial you should be able to:
diagnose when static-shift correction is worth trying
estimate station-level correction factors with AMA
inspect the meaning of
delta_log10_rho,fac_rho, andfac_zapply the correction without mutating the original survey
compare before and after correction using static-shift QC plots
export corrected EDI files and a correction manifest
choose between AMA, LOESS, reference-median, and bilateral correction styles
run static-shift correction from a pipeline configuration when needed
Prerequisites#
Run first-pass survey inspection before correcting static shift:
1from pycsamt.api import read_edis
2from pycsamt.emtools.qc import build_qc_table, station_confidence_table
3
4survey = read_edis(
5 "data/AMT/WILLY_DATA/L18PLT",
6 recursive=False,
7 strict=False,
8 progress=False,
9)
10sites = survey.collection
11
12qc = build_qc_table(sites, api=True)
13confidence = station_confidence_table(sites, method="composite", api=True)
14
15print(qc)
16print(confidence)
Static-shift correction is usually a second step. If the survey did not pass basic loading, station-order, frequency-coverage, and quality checks, solve those problems first. See Inspect and QC a Survey.
This tutorial uses the bundled WILLY L18PLT line so the printed output and
figures are reproducible. Replace the path with your own EDI directory when
you repeat the workflow on another survey.
When Static Shift Is a Good Candidate#
Static shift is plausible when:
one station is shifted up or down relative to neighboring stations;
the apparent-resistivity curve shape is similar to neighboring curves;
phase does not show an equivalent anomaly;
the offset is approximately frequency independent over the selected band;
the station belongs to a profile where neighboring stations can define a local spatial trend.
Static-shift correction is risky when:
the anomaly is frequency dependent;
phase changes strongly at the same station;
the station order or coordinates are wrong;
several neighboring stations are shifted together because the geology is genuinely different;
the data contain strong CSAMT source effects or near-field behavior;
the EDI file came from another software package and still needs recomputation or component normalization.
For the scientific background, read Static Shift.
Create a Review Folder#
Keep factors, plots, and corrected files together.
1from pathlib import Path
2
3review_dir = Path("static_shift_review")
4plot_dir = review_dir / "plots"
5corrected_dir = review_dir / "corrected_edis"
6
7plot_dir.mkdir(parents=True, exist_ok=True)
8corrected_dir.mkdir(parents=True, exist_ok=True)
Estimate AMA Correction Factors#
The standard interactive correction path uses
pycsamt.emtools.ss.estimate_ss_ama(). AMA means adaptive moving average:
each station is compared with its neighbors in log apparent-resistivity space.
1from pycsamt.emtools.ss import estimate_ss_ama
2
3factors = estimate_ss_ama(
4 sites,
5 sort_by="lon",
6 half_window=3,
7 weights="tri",
8 pband=None,
9 max_skew=6.0,
10 robust_freq="median",
11 robust_overall="median",
12 recursive=False,
13 api=True,
14)
15
16factor_df = factors.to_pandas(copy=True)
17factor_df.to_csv(review_dir / "static_shift_factors_ama.csv", index=False)
18print(factors)
19print(factor_df)
On L18PLT, the conservative skew filter still returns factors, but only a
small number of frequency samples survive at many stations:
station delta_log10_rho fac_rho fac_z n_used
18-001A 1.026469 0.094087 0.306736 2
18-002U 0.393467 0.404141 0.635721 1
18-003A -0.473762 2.976881 1.725364 1
18-004A -0.203818 1.598887 1.264471 2
18-005U 0.317344 0.481566 0.693950 3
count 26.000000
mean 2.538462
min 1.000000
max 7.000000
That is a warning, not a failure. For this line, the first-pass QC tutorial already showed high skew across the profile. A strict skew mask leaves too few samples for a comfortable automatic correction, so the factors above should be treated as review evidence.
The factor table contains:
stationStation identifier used to match the factor back to the EDI object.
delta_log10_rhoEstimated log10 apparent-resistivity offset before correction. Positive values mean the station is high relative to its local trend. Negative values mean it is low.
fac_rhoApparent-resistivity scale factor. This is the multiplicative correction for apparent resistivity.
fac_zImpedance scale factor. pyCSAMT applies this to the impedance tensor because apparent resistivity is proportional to impedance amplitude squared.
n_usedNumber of frequency samples used to estimate the station factor.
Choose the Spatial Order#
AMA needs the profile order to make physical sense. The sort_by parameter
controls that order:
sort_by="lon"Use longitude. This is often reasonable for east-west lines.
sort_by="lat"Use latitude. This is often reasonable for north-south lines.
sort_by="name"Use station name order. This is useful when station names already encode line order and coordinates are missing or unreliable.
If the line is oblique, inspect the coordinates before deciding. A wrong order can make the algorithm compare stations that are not neighbors in the field.
Use a Period Band#
Sometimes only part of the spectrum is appropriate for estimating static
shift. Use pband=(min_period, max_period) in seconds to restrict the
factor estimate.
1factors_mid_band = estimate_ss_ama(
2 sites,
3 sort_by="name",
4 half_window=3,
5 weights="tri",
6 pband=(0.01, 10.0),
7 max_skew=6.0,
8 recursive=False,
9 api=True,
10)
11
12factors_mid_band.to_pandas(copy=True).to_csv(
13 review_dir / "static_shift_factors_ama_0p01s_10s.csv",
14 index=False,
15)
Choose a band where the curves appear shifted but not strongly distorted. Avoid using a band dominated by dead-band noise, transmitter source effects, or obvious phase jumps.
For the remaining figures in this tutorial, we use max_skew=None as an
exploratory setting so every frequency contributes to the demonstration. In a
production workflow, keep the stricter setting unless your dimensionality
review justifies relaxing it.
1factors_full_band = estimate_ss_ama(
2 sites,
3 sort_by="name",
4 half_window=3,
5 weights="tri",
6 pband=None,
7 max_skew=None,
8 recursive=False,
9 api=True,
10)
11
12factor_df = factors_full_band.to_pandas(copy=True)
13print(factor_df.head(6))
Example output:
station delta_log10_rho fac_rho fac_z n_used
18-001A 0.672558 0.212541 0.461021 53
18-002U 0.625887 0.236654 0.486471 53
18-003A 0.291283 0.511349 0.715087 53
18-004A 0.432026 0.369806 0.608117 53
18-005U 0.634951 0.231766 0.481421 53
18-006A 0.293025 0.509302 0.713654 53
The tutorial figures are generated by
docs/scripts/generate_tutorial_static_shift.py. The first figure shows the
estimated station offsets. Blue bars have positive
delta_log10_rho and therefore receive a downward resistivity correction;
red bars move upward.
Inspect Large Corrections#
Large static-shift factors should be reviewed before they are applied.
1large = factor_df[
2 (factor_df["fac_rho"] < 0.5)
3 | (factor_df["fac_rho"] > 2.0)
4 | (factor_df["n_used"] < 5)
5]
6
7print("stations requiring manual review")
8print(large[["station", "delta_log10_rho", "fac_rho", "fac_z", "n_used"]])
Example output from the exploratory full-band estimate:
stations requiring manual review
station delta_log10_rho fac_rho fac_z n_used
18-001A 0.672558 0.212541 0.461021 53
18-002U 0.625887 0.236654 0.486471 53
18-004A 0.432026 0.369806 0.608117 53
18-005U 0.634951 0.231766 0.481421 53
18-007U 0.602360 0.249827 0.499827 53
18-008U 0.593745 0.254833 0.504810 53
18-009A 0.832611 0.147024 0.383438 53
18-010U 0.696217 0.201272 0.448633 53
A factor outside 0.5 to 2.0 is not automatically wrong, but it should
make you ask why the station is so different from its neighbors.
Apply Factors Manually#
The safest workflow is to estimate factors first, inspect them, then apply
them to a copy of the site collection with
pycsamt.emtools.ss.apply_ss_factors().
1from pycsamt.emtools.ss import apply_ss_factors
2
3corrected = apply_ss_factors(
4 sites,
5 factors_full_band,
6 key="fac_z",
7 inplace=False,
8 recursive=False,
9 verbose=1,
10)
Because apparent resistivity is proportional to |Z|^2, applying
fac_z changes determinant apparent resistivity by approximately
-delta_log10_rho in log10 space:
18-001A -0.672558
18-002U -0.625887
18-003A -0.291283
18-004A -0.432026
18-005U -0.634951
18-006A -0.293025
Use inplace=False during exploration. This returns a corrected copy and
keeps sites unchanged for comparison. Use inplace=True only inside a
controlled pipeline or after you have saved the original state.
Apply AMA in One Step#
For routine processing, pycsamt.emtools.ss.correct_ss_ama() combines
factor estimation and application:
1from pycsamt.emtools.ss import correct_ss_ama
2
3corrected = correct_ss_ama(
4 sites,
5 sort_by="name",
6 half_window=3,
7 weights="tri",
8 pband=None,
9 max_skew=None,
10 robust_freq="median",
11 robust_overall="median",
12 inplace=False,
13 recursive=False,
14 verbose=1,
15)
The one-step call returns a corrected Sites collection:
one_step_sites_type: Sites
The one-step function is convenient, but the two-step factor workflow is better when you are still learning the dataset.
Compare Before and After#
After correction, compare the original and corrected collections. A good correction should reduce station-level vertical jumps without creating frequency-dependent artifacts.
1import matplotlib.pyplot as plt
2from pycsamt.emtools.ss import (
3 plot_ss_delta_profile,
4 plot_ss_delta_psection,
5 plot_ss_station_curves,
6)
7
8ax = plot_ss_delta_profile(
9 sites,
10 corrected,
11 pband=None,
12 robust="median",
13)
14ax.figure.savefig(plot_dir / "delta_profile.png", dpi=200, bbox_inches="tight")
15plt.close(ax.figure)
16
17ax = plot_ss_delta_psection(
18 sites,
19 corrected,
20 axis_y="logperiod",
21 pband=None,
22)
23ax.figure.savefig(plot_dir / "delta_psection.png", dpi=200, bbox_inches="tight")
24plt.close(ax.figure)
25
26ax = plot_ss_station_curves(
27 sites,
28 corrected,
29 station=str(factor_df["station"].iloc[0]),
30 pband=None,
31)
32ax.figure.savefig(plot_dir / "station_curve_before_after.png", dpi=200)
33plt.close(ax.figure)
The generated determinant-resistivity comparison below is intentionally simple: the top panel reduces the correction to one median value per station, and the bottom panel shows that the applied static-shift factor is period independent.
The profile plot shows one correction value per station. The pseudo-section shows whether the correction is approximately uniform across period. The station-curve plot is useful for manual review of individual stations.
Use One-Call QC Wrappers#
The ss_qc_* helpers estimate, apply, and plot in one call. They are useful
for fast experiments. Use return_sites=True when you want the corrected
collection returned with the plot.
1from pycsamt.emtools.ss import ss_qc_psection, ss_qc_profile
2
3ax, corrected_preview = ss_qc_profile(
4 sites,
5 method="ama",
6 return_sites=True,
7 sort_by="lon",
8 half_window=3,
9 max_skew=6.0,
10)
11ax.figure.savefig(plot_dir / "ama_preview_profile.png", dpi=200)
12plt.close(ax.figure)
13
14ax = ss_qc_psection(
15 sites,
16 method="ama",
17 sort_by="lon",
18 half_window=3,
19 max_skew=6.0,
20 axis_y="logperiod",
21)
22ax.figure.savefig(plot_dir / "ama_preview_psection.png", dpi=200)
23plt.close(ax.figure)
These wrappers are excellent for exploration. For a formal processing record,
also save the factor table produced by estimate_ss_ama.
Try Alternative Estimators#
AMA is the recommended starting point, but pyCSAMT also exposes other factor estimators:
1from pycsamt.emtools.ss import (
2 estimate_ss_bilateral,
3 estimate_ss_loess,
4 estimate_ss_refmedian,
5)
6
7loess = estimate_ss_loess(
8 sites,
9 half_window=3,
10 poly=1,
11 it=2,
12 pband=(0.01, 10.0),
13 max_skew=6.0,
14 api=True,
15)
16
17bilateral = estimate_ss_bilateral(
18 sites,
19 half_window=4,
20 pband=(0.01, 10.0),
21 max_skew=6.0,
22 summary="median",
23 api=True,
24)
25
26refmedian = estimate_ss_refmedian(
27 sites,
28 pband=(0.01, 10.0),
29 max_skew=6.0,
30 api=True,
31)
32
33loess.to_pandas(copy=True).to_csv(review_dir / "factors_loess.csv", index=False)
34bilateral.to_pandas(copy=True).to_csv(
35 review_dir / "factors_bilateral.csv",
36 index=False,
37)
38refmedian.to_pandas(copy=True).to_csv(
39 review_dir / "factors_refmedian.csv",
40 index=False,
41)
For L18PLT, the first rows of the comparison show why method comparison is
useful:
station ama loess refmedian bilateral
18-001A 0.461021 1.438779 0.654036 0.191077
18-002U 0.486471 0.906571 0.909473 0.256785
18-003A 0.715087 1.416089 1.304343 0.322513
18-004A 0.608117 0.829027 0.901936 0.239036
18-005U 0.481421 0.672624 0.840085 0.222722
18-006A 0.713654 1.202319 1.115067 1.147786
Use the alternatives as checks:
estimate_ss_loessUseful when you want a local smooth trend rather than a simple moving neighbor median.
estimate_ss_refmedianUseful when the whole profile can be compared to a survey-wide reference.
estimate_ss_bilateralUseful when you want spatial weighting that also respects value similarity.
If all methods point to the same shifted stations, confidence in the correction increases. If the methods disagree strongly, inspect the data before applying a large correction.
Export Corrected EDI Files#
Once the correction is accepted, export the corrected collection as new EDI files. Do not overwrite the raw EDI directory during tutorial or exploratory work.
1from pycsamt.site.export import write_sites
2
3written = write_sites(
4 corrected,
5 corrected_dir,
6 template="{station}_static_shift_corrected.edi",
7 exist_ok=True,
8 manifest_csv=review_dir / "corrected_edi_manifest.csv",
9)
10
11print(f"wrote {len(written)} corrected EDI files")
The manifest records which station was written to which file. Keep it with the factor table and QC plots.
Run Through a Pipeline#
For repeatable production work, put static-shift correction in a pipeline
configuration. SS001 is the AMA correction step.
1name: static_shift_review
2input: data/AMT/WILLY_DATA/L18PLT
3output_dir: results/static_shift_review
4
5steps:
6 - name: qc_before
7 code: QC001
8 - name: static_shift
9 code: SS001
10 params:
11 sort_by: lon
12 half_window: 3
13 weights: tri
14 max_skew: 6.0
15 - name: qc_after
16 code: QC001
Inspect the registered static-shift steps from the command line:
1pycsamt pipe steps --category static_shift
The pipeline registry currently includes:
SS001AMA correction with
pycsamt.emtools.ss.correct_ss_ama().SS002LOESS factor estimation followed by factor application.
SS003Reference-median factor estimation followed by factor application.
SS004Bilateral factor estimation followed by factor application.
Use the pipeline when the same correction recipe must be shared, reviewed, or re-run on several lines.
Checklist Before Inversion#
Before using corrected data for inversion preparation, confirm that:
the original EDI files are still available;
the factor table has been saved;
large correction factors have been manually reviewed;
phase behavior is still geologically reasonable;
correction plots show station-level offsets, not frequency-dependent distortion;
corrected EDI files were exported into a separate directory;
the inversion input preparation step points to the corrected directory, not the raw directory by accident.
Troubleshooting#
- All factors are exactly one
The selected band may not contain usable samples, all stations may already be aligned, or the estimator could not build a neighbor trend.
- One station receives an extreme factor
Check whether it has enough frequencies, whether its coordinates are correct, and whether the station is an actual geologic outlier.
- The pseudo-section correction is not period independent
The problem may not be static shift. Inspect source effects, dead-band behavior, phase jumps, and frequency-dependent noise.
- Corrected curves look worse
Revisit station order, period band,
half_window, and skew threshold. Trysort_by="name"if coordinates are unreliable.- Correction changes interpretation too strongly
Treat the correction as provisional. Run one inversion with raw data and one with corrected data, then compare residuals and geologic plausibility.
See Also#
- Inspect and QC a Survey
Inspect station quality before correction.
- Prepare an Occam2D Inversion
Prepare corrected data for inversion.
- Static Shift
Scientific background and interpretation cautions.
- Pipeline Steps
Pipeline static-shift step catalogue.
- EDI Recompute Workflow
Recompute and rewrite EDI files before correction when imports are inconsistent.
- pycsamt.emtools
EMTools API reference.