Pipeline Steps#
Pipeline steps are the atomic processing operations used by
pycsamt.pipeline.Pipeline. Each step is registered once in the
pipeline registry and can then be used from the Python API, YAML/JSON/Python
configuration files, presets, and the pycsamt pipe command line.
The registry gives every operation a stable code such as FREQ001 or
NR001. The code is intentionally short because it appears in terminal
output, saved reports, plots, and reproducible pipeline.yaml files. Each
code also has a snake-case name such as select_band or
notch_powerline; both forms are accepted when constructing a step.
Use this page when you need to:
choose the right operation for a processing chain;
understand the order in which steps should usually run;
discover default parameters and diagnostic plots;
write a custom pipeline configuration;
extend the registry in a controlled way for new processing operations.
Step Model#
A registered step has two layers.
StepSpecRegistry metadata. It stores the code, name, human label, category, default parameters, transform function, QC plot functions, and whether the operation returns a modified site collection.
StepA configured instance used inside a pipeline. It binds a
StepSpecto parameter overrides.
In Python, a step is created with pycsamt.pipeline.Step:
1from pycsamt.pipeline import Step
2
3notch = Step("NR001", mains_hz=50, n_harm=30)
4band = Step("select_band", band_hz=(0.01, 10000.0))
The two lookup styles are equivalent:
1Step("NR001")
2Step("notch_powerline")
The first form is better for compact configs and reports. The second form is often easier to read in Python code.
How Steps Execute#
During a run, the pipeline applies each step in order:
count the input sites;
call the step transform function;
generate QC plots, if enabled;
save intermediate EDI snapshots, if configured;
record a
pycsamt.pipeline.StepResult;pass the output sites to the next step.
For normal processing steps, returns_sites=True and the transform output
becomes the input to the next step. Diagnostic-only steps use
returns_sites=False. They keep the site collection unchanged and are used
to generate plots or summaries at useful checkpoints.
Example result fields:
1result = pipe.run(sites, outdir="results/basic_qc")
2
3for step_result in result.step_results:
4 print(step_result.step_code)
5 print(step_result.step_name)
6 print(step_result.ok)
7 print(step_result.n_sites_in, step_result.n_sites_out)
8 print(step_result.plots)
Discover Steps From The CLI#
The command-line catalogue is the fastest way to inspect your installed version:
1pycsamt pipe steps
2pycsamt pipe steps --category frequency
3pycsamt pipe steps --category noise_removal --codes-only
4pycsamt pipe steps --info NR001
5pycsamt pipe steps --info notch_powerline
Machine-readable output is useful for notebooks, dashboards, or scripts:
1pycsamt pipe steps --format json
2pycsamt pipe steps --category static_shift --format csv
Discover Steps From Python#
The same registry is available from Python:
1from pycsamt.pipeline import categories, list_steps, lookup_step
2
3print(categories())
4
5for spec in list_steps("frequency"):
6 print(spec.code, spec.name, spec.label, spec.defaults)
7
8nr001 = lookup_step("NR001")
9same = lookup_step("notch_powerline")
10assert nr001 is same
Use pycsamt.pipeline.Pipeline.step_info() when you want a formatted
human-readable explanation:
1from pycsamt.pipeline import Pipeline
2
3print(Pipeline.step_info("NR001"))
4print(Pipeline.catalogue("static_shift"))
Registered Categories#
The current registry contains 46 processing and diagnostic steps.
Category |
Codes |
Main purpose |
|---|---|---|
|
|
Select, clean, align, regrid, decimate, smooth, mask, and recover frequency samples. |
|
|
Remove power-line harmonics, smooth noisy responses, suppress outliers, apply EMAP-style filters, enforce off-diagonal consistency, and run bundled denoising chains. |
|
|
Estimate and apply static-shift corrections using AMA, LOESS, reference-median, or bilateral-filter strategies. |
|
|
Rotate, antisymmetrise, clip, balance, invert, and reorient impedance tensors. |
|
|
Classify dimensionality, mask by dimensionality class, and project to a 2-D representation. |
|
|
Mask or select bands using Bahr skewness and related low-skew criteria. |
|
|
Correct near-field source effects and normalize source response. |
|
|
Generate diagnostic snapshots without changing the site collection. |
Recommended Ordering#
The registry does not force a single scientific workflow, but most survey pipelines are easier to reason about when the steps follow this order:
frequency cleanup;
noise removal;
dimensionality and skew gates;
static-shift correction;
tensor rotation or tensor balancing;
source-effect correction, when needed;
QC snapshots and final output.
A compact first-pass chain looks like this:
1pycsamt pipe run data/edis \
2 --steps FREQ002,FREQ001,FREQ004,NR001,QC001 \
3 --out results/basic_manual
The equivalent Python pipeline is:
1from pycsamt.pipeline import Pipeline, Step
2
3pipe = Pipeline(
4 [
5 ("drop_duplicates", Step("FREQ002")),
6 ("select_band", Step("FREQ001", band_hz=(0.001, 10000.0))),
7 ("align_grid", Step("FREQ004")),
8 ("notch", Step("NR001", mains_hz=50)),
9 ("qc", Step("QC001")),
10 ],
11 name="basic_manual",
12)
Ordering matters because each step receives the site collection produced by the previous step. For example, it is usually better to drop duplicate frequencies before aligning the grid, and it is usually better to perform frequency and noise cleanup before estimating dimensionality or skew masks.
Frequency Steps#
Frequency steps define the usable sampling axis before more interpretive processing begins.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
You need to keep a scientific frequency interval and remove very low or very high frequencies from the chain. |
|
|
|
Multiple samples share the same nominal frequency and should be collapsed before alignment or interpolation. |
none |
|
|
Low-confidence frequencies should be removed entirely. |
none |
|
|
Sites need a common frequency grid before profile operations or cross-site comparisons. |
none |
|
|
You want a regular log-frequency sampling density. |
|
|
|
You want a lighter frequency set for quick inspection or faster downstream runs. |
|
|
|
Small frequency-to-frequency oscillations should be smoothed. |
|
|
|
Low-confidence samples should remain in the grid as |
|
|
|
Low-confidence samples should be recovered by interpolation after confidence gating. |
|
Common frequency cleanup example:
1steps:
2 - name: drop_duplicates
3 code: FREQ002
4 - name: select_band
5 code: FREQ001
6 params:
7 band_hz: [0.01, 10000.0]
8 - name: align_grid
9 code: FREQ004
Noise Removal Steps#
Noise-removal steps suppress survey artifacts while preserving the interpretable impedance structure. Prefer targeted operations first; reserve the bundled denoising step for quick workflows or carefully reviewed production recipes.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
Power-line harmonics contaminate the response. |
|
|
|
Responses are noisy along log-frequency. |
none |
|
|
Isolated outliers should be pulled toward the group trend rather than removed aggressively. |
none |
|
|
Spike-like outliers need robust local filtering. |
|
|
|
Neighboring stations define a stable spatial trend. |
none |
|
|
You want EMAP-style spatial filtering. |
none |
|
|
EMAP filtering should be gated by confidence and return the filtered site collection. |
none |
|
|
Off-diagonal components need robust low-rank denoising. |
none |
|
|
Off-diagonal impedance components should be made more internally consistent. |
none |
|
|
Incoherent frequency samples should be masked before interpretation. |
none |
|
|
A fixed moving-average smoother is appropriate for all or selected components. |
|
|
|
You want a moving average that is less sensitive to extremes. |
|
|
|
A spatial-window static-shift correction is needed from the noise module. |
|
|
|
A bundled denoising recipe is acceptable for a first pass or a reviewed production preset. |
|
Example:
1steps:
2 - name: notch
3 code: NR001
4 params:
5 mains_hz: 60
6 n_harm: 20
7 - name: robust_spikes
8 code: NR004
9 params:
10 win: 5
11 nsig: 3.5
12 - name: incoherence_gate
13 code: NR010
Static-Shift Steps#
Static-shift correction should normally run after basic frequency and noise cleanup. Static-shift steps modify the site collection and generate comparison plots when output plotting is enabled.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
You want the standard AMA static-shift correction. |
none |
|
|
You want a two-phase LOESS estimate followed by factor application. |
none |
|
|
A reference median across stations is the preferred shift estimator. |
none |
|
|
Shift factors should be estimated with a bilateral-style spatial filter. |
|
Example:
1steps:
2 - name: select_band
3 code: FREQ001
4 params:
5 band_hz: [0.01, 10000.0]
6 - name: notch
7 code: NR001
8 - name: static_shift
9 code: SS004
10 params:
11 half_window: 5
12 max_skew: 5.0
Tensor Steps#
Tensor steps change tensor orientation, symmetry, or component consistency. Run them only after the frequency axis and gross noise problems are under control.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
The impedance tensor should be rotated to estimated strike. |
|
|
|
Off-diagonal tensor symmetry needs to be enforced or inspected. |
|
|
|
Tensor outlier frequencies should be sigma-clipped. |
|
|
|
Off-diagonal components need balancing. |
none |
|
|
A known fixed rotation angle should be applied. |
|
|
|
The impedance tensor should be inverted. |
none |
|
|
Sensor orientations are known and should be corrected explicitly. |
|
Example:
1from pycsamt.pipeline import Pipeline, Step
2
3pipe = Pipeline(
4 [
5 ("drop_duplicates", Step("FREQ002")),
6 ("select_band", Step("FREQ001", band_hz=(1.0, 10000.0))),
7 ("static_shift", Step("SS001")),
8 ("rotate", Step("TZ001", method="swift")),
9 ("qc", Step("QC001")),
10 ],
11 name="tensor_ready",
12)
Dimensionality Steps#
Dimensionality steps help decide whether a frequency band or station interval is suitable for 1-D, 2-D, or 3-D assumptions.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
You need a diagnostic classification of dimensionality. This is a diagnostic step and does not modify sites. |
none |
|
|
You want to keep or mask samples by dimensionality class before further processing. |
|
|
|
The response should be projected toward a 2-D representation. |
none |
Example:
1steps:
2 - name: classify_dimensionality
3 code: DIM001
4 - name: keep_2d
5 code: DIM002
6 params:
7 dim_type: 2d
8 - name: project_2d
9 code: DIM003
Skew Steps#
Skew steps are useful when selecting lower-skew intervals for 2-D inversion or for rejecting intervals where 3-D effects dominate.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
Frequencies above a Bahr skewness threshold should be masked. |
|
|
|
You want the longest continuous low-skew band. |
|
|
|
You want the lowest-skew band selected automatically. |
none |
|
|
Small gaps in a low-skew interval should be closed. |
|
Example:
1pycsamt pipe run data/edis \
2 --steps FREQ002,FREQ001,FREQ004,NR001,SK001,TZ001,QC001 \
3 --out results/low_skew_rotation
Source-Effect Steps#
Source-effect steps are more specialized than the basic QC and denoising chain. Use them when survey geometry, transmitter effects, or diagnostic plots indicate source overprint or near-field behavior.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
Near-field source effects should be corrected. |
none |
|
|
Source response normalization is needed before interpretation or comparison. |
none |
Example:
1steps:
2 - name: source_diagnostic
3 code: QC003
4 - name: near_field
5 code: SRC001
6 - name: normalized_source
7 code: SRC002
QC And Diagnostic Steps#
QC steps are diagnostic-only checkpoints. They generate figures but pass the site collection through unchanged.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
You want a quick-look dashboard, station confidence view, and coverage pseudo-section. |
none |
|
|
You want a field-zone classification plot. |
none |
|
|
You want a source-overprint diagnostic before or after source-effect corrections. |
none |
|
|
You want a Bostick depth-section diagnostic snapshot. |
none |
Use QC snapshots at major checkpoints:
1steps:
2 - name: raw_qc
3 code: QC001
4 - name: notch
5 code: NR001
6 - name: cleaned_qc
7 code: QC001
8 - name: static_shift
9 code: SS001
10 - name: final_qc
11 code: QC001
Parameters And Defaults#
User parameters are merged on top of registry defaults. If a default is not overridden, the registry value is used.
1from pycsamt.pipeline import Step
2
3step = Step("NR001", mains_hz=60)
4
5# The registry default n_harm and tol_hz remain active.
6assert step.params["mains_hz"] == 60
7assert step.params["n_harm"] == 30
8assert step.params["tol_hz"] == 0.08
In YAML, place overrides under params:
1steps:
2 - name: notch_60hz
3 code: NR001
4 params:
5 mains_hz: 60
6 n_harm: 25
7 tol_hz: 0.1
Keep parameter names aligned with the underlying emtools function. Use
pycsamt pipe steps --info CODE or Pipeline.step_info("CODE") before
editing an unfamiliar step.
Step Labels#
The registry code identifies the operation. The pipeline label identifies the position or intent of that operation inside one workflow.
1steps:
2 - name: raw_notch
3 code: NR001
4 - name: post_static_shift_notch
5 code: NR001
6 params:
7 n_harm: 10
The two entries use the same operation, but reports and plot directories can distinguish them because their labels differ.
Diagnostic Plots#
Each StepSpec may declare QC plot functions. When plotting is enabled,
the pipeline calls those functions after a successful transform and saves the
figures under the run output directory.
Examples:
NR001can generate a harmonic waterfall and SNR gain profile.FREQ001can generate band microstrips and a coverage-quality heatmap.SS004can generate static-shift delta, comparison, and summary plots.QC001can generate the quick-look dashboard, station confidence dashboard, and coverage pseudo-section.
QC plot failures are skipped so that a successful processing step is not turned into a failed run only because a diagnostic figure could not be drawn. If a plot is missing, inspect the saved report and rerun with verbose CLI output.
Error Handling#
Step failures are controlled by the pipeline error policy. From the CLI, use
--on-error:
1pycsamt pipe run data/edis --preset basic_qc --on-error raise
2pycsamt pipe run data/edis --preset basic_qc --on-error warn
3pycsamt pipe run data/edis --preset basic_qc --on-error skip
raiseStop immediately at the failing step.
warnRecord the error, warn, continue with the previous site collection, and return a nonzero CLI exit status if any step failed.
skipRecord the error and continue with the previous site collection without a warning. The final result is still not OK if any step failed.
Diagnostic-only steps are special: if their diagnostic transform raises, the step warns and passes the original sites through unchanged.
Choosing Between Similar Steps#
Use the smallest operation that matches the problem you can actually see in the data.
- For harmonic contamination
Start with
NR001and tunemains_hz,n_harm, andtol_hz.- For frequency-grid problems
Start with
FREQ002followed byFREQ001andFREQ004.- For isolated spikes
Start with
NR004. Move toNR012when the smoothing window should be robust to extremes across a broader interval.- For spatially coherent noise
Compare
NR005,NR006, andNR007with QC plots enabled.- For static shift
Start with
SS001for a standard workflow. UseSS002,SS003, orSS004when the survey geometry and QC evidence justify a different estimator.- For 2-D inversion preparation
Combine frequency cleanup, noise removal, skew or dimensionality gates, static-shift correction, strike rotation, and final QC.
Full Example Configuration#
This example is explicit enough for a reviewed processing run while remaining short enough to audit:
1name: line22_processing
2output_dir: results/line22_processing
3
4steps:
5 - name: drop_duplicates
6 code: FREQ002
7
8 - name: select_band
9 code: FREQ001
10 params:
11 band_hz: [0.01, 10000.0]
12
13 - name: align_grid
14 code: FREQ004
15
16 - name: notch_powerline
17 code: NR001
18 params:
19 mains_hz: 50
20 n_harm: 30
21 tol_hz: 0.08
22
23 - name: robust_spike_filter
24 code: NR004
25 params:
26 win: 3
27 nsig: 3.0
28
29 - name: low_skew_gate
30 code: SK001
31 params:
32 threshold: 0.3
33
34 - name: static_shift
35 code: SS001
36
37 - name: rotate_to_strike
38 code: TZ001
39 params:
40 method: swift
41
42 - name: final_qc
43 code: QC001
Run it with:
1pycsamt pipe show config/line22_processing.yaml
2pycsamt pipe run data/edis \
3 --config config/line22_processing.yaml \
4 --out results/line22_processing \
5 --on-error warn \
6 -v
Extension Policy#
The current pipeline registry is source-controlled and static. That is intentional: processing steps are scientific operations, so adding one should be reviewed with tests, defaults, documentation, and diagnostic behavior.
To add a new built-in step:
implement or expose the transform function in the appropriate
pycsamt.emtoolsmodule;add a
pycsamt.pipeline.StepSpecentry inpycsamt/pipeline/_registry.py;choose a code prefix that matches the category, such as
FREQ,NR,SS,TZ,DIM,SK,SRC, orQC;define conservative defaults;
attach QC plot functions when useful;
decide whether the step returns a modified site collection;
add tests that prove lookup, parameter merging, execution, and config round-tripping;
update this page and any presets that should use the new operation.
Minimal registry entry pattern:
1StepSpec(
2 code="NR015",
3 name="my_new_filter",
4 label="My New Noise Filter",
5 category="noise_removal",
6 mod="pycsamt.emtools.remove_noise",
7 fn_name="my_new_filter",
8 defaults={"window": 5},
9 qc_defs=[
10 ("pycsamt.emtools.remove_noise", "nr_qc_station_offdiag_curves"),
11 ],
12)
For two-phase operations, use an override_fn wrapper that receives
sites and keyword parameters, then returns the modified site collection.
Static-shift steps such as SS002 and SS003 follow this pattern.
Testing New Steps#
At minimum, a new step should be covered by tests like these:
1from pycsamt.pipeline import Pipeline, Step, lookup_step
2
3def test_registry_lookup_for_new_step():
4 spec = lookup_step("NR015")
5 assert spec.name == "my_new_filter"
6 assert lookup_step("my_new_filter") is spec
7
8def test_step_defaults_and_overrides():
9 step = Step("NR015", window=7)
10 assert step.params["window"] == 7
11
12def test_pipeline_accepts_new_step(sites):
13 pipe = Pipeline([("new_filter", Step("NR015"))])
14 result = pipe.run(sites, outdir=None, save_plots=False)
15 assert result.ok
Also test YAML loading if the step is expected to be used from configuration files.
Troubleshooting#
- Unknown step code
Run
pycsamt pipe stepsorpycsamt pipe steps --info CODE. Codes are exact and uppercase, for exampleNR001.- Unknown step name
Use the snake-case registry name, for example
notch_powerlinerather thannotch. Pipeline labels may be arbitrary, but registry names are fixed.- A parameter is ignored or raises
TypeError Check the underlying emtools function signature and the output of
pycsamt pipe steps --info CODE. Parameter names must match the transform function.- The site count changes unexpectedly
Frequency-selection, masking, or dimensionality operations can change the effective data content. Inspect
StepResult.n_sites_inandStepResult.n_sites_outin the run summary.- Expected plots are missing
Confirm that plots were not disabled with
--no-plots. Some QC plots are skipped when the required data are absent or when the plotting function cannot build a figure for the current site collection.- A diagnostic step appears to do nothing
QC steps intentionally pass sites through unchanged. Inspect the plot directory and report rather than expecting data changes.