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.

StepSpec

Registry 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.

Step

A configured instance used inside a pipeline. It binds a StepSpec to 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:

  1. count the input sites;

  2. call the step transform function;

  3. generate QC plots, if enabled;

  4. save intermediate EDI snapshots, if configured;

  5. record a pycsamt.pipeline.StepResult;

  6. 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

frequency

FREQ001 - FREQ009

Select, clean, align, regrid, decimate, smooth, mask, and recover frequency samples.

noise_removal

NR001 - NR014

Remove power-line harmonics, smooth noisy responses, suppress outliers, apply EMAP-style filters, enforce off-diagonal consistency, and run bundled denoising chains.

static_shift

SS001 - SS004

Estimate and apply static-shift corrections using AMA, LOESS, reference-median, or bilateral-filter strategies.

tensor

TZ001 - TZ007

Rotate, antisymmetrise, clip, balance, invert, and reorient impedance tensors.

dimensionality

DIM001 - DIM003

Classify dimensionality, mask by dimensionality class, and project to a 2-D representation.

skew

SK001 - SK004

Mask or select bands using Bahr skewness and related low-skew criteria.

source_effects

SRC001 - SRC002

Correct near-field source effects and normalize source response.

qc

QC001 - QC004

Generate diagnostic snapshots without changing the site collection.

Frequency Steps#

Frequency steps define the usable sampling axis before more interpretive processing begins.

Code

Name

Use when

Defaults

FREQ001

select_band

You need to keep a scientific frequency interval and remove very low or very high frequencies from the chain.

band_hz=(0.001, 10000.0)

FREQ002

drop_duplicates

Multiple samples share the same nominal frequency and should be collapsed before alignment or interpolation.

none

FREQ003

drop_low_confidence

Low-confidence frequencies should be removed entirely.

none

FREQ004

align_grid

Sites need a common frequency grid before profile operations or cross-site comparisons.

none

FREQ005

regrid_logspace

You want a regular log-frequency sampling density.

n_per_decade=6

FREQ006

decimate

You want a lighter frequency set for quick inspection or faster downstream runs.

step=2

FREQ007

smooth_freq

Small frequency-to-frequency oscillations should be smoothed.

window=3

FREQ008

mask_low_confidence

Low-confidence samples should remain in the grid as NaN values rather than being removed.

method='composite', threshold=0.5

FREQ009

recover_low_confidence

Low-confidence samples should be recovered by interpolation after confidence gating.

method='composite', ci_hi=0.9, ci_lo=0.5, interpolation='linear'

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

NR001

notch_powerline

Power-line harmonics contaminate the response.

mains_hz=50, n_harm=30, tol_hz=0.08

NR002

smooth_logfreq

Responses are noisy along log-frequency.

none

NR003

shrink_group_trend

Isolated outliers should be pulled toward the group trend rather than removed aggressively.

none

NR004

hampel_filter

Spike-like outliers need robust local filtering.

win=3, nsig=3.0

NR005

spatial_median

Neighboring stations define a stable spatial trend.

none

NR006

emap_filter

You want EMAP-style spatial filtering.

none

NR007

emap_confidence

EMAP filtering should be gated by confidence and return the filtered site collection.

none

NR008

rpca_offdiag

Off-diagonal components need robust low-rank denoising.

none

NR009

enforce_offdiag

Off-diagonal impedance components should be made more internally consistent.

none

NR010

mask_incoherent

Incoherent frequency samples should be masked before interpretation.

none

NR011

fixed_length_mavg

A fixed moving-average smoother is appropriate for all or selected components.

window=5, component='all'

NR012

trimmed_mavg

You want a moving average that is less sensitive to extremes.

window=5, component='all'

NR013

correct_static_shift_spatial

A spatial-window static-shift correction is needed from the noise module.

window_m=1500.0, spacing_m=200.0, comp='det'

NR014

denoise_pipeline

A bundled denoising recipe is acceptable for a first pass or a reviewed production preset.

mains_hz=50.0, n_harm=30, tol_hz=0.08, smooth_win=5, gate_snr=2.5

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

SS001

correct_ss_ama

You want the standard AMA static-shift correction.

none

SS002

correct_ss_loess

You want a two-phase LOESS estimate followed by factor application.

none

SS003

correct_ss_refmedian

A reference median across stations is the preferred shift estimator.

none

SS004

correct_ss_bilateral

Shift factors should be estimated with a bilateral-style spatial filter.

half_window=4, max_skew=6.0, summary='median'

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

TZ001

rotate_strike

The impedance tensor should be rotated to estimated strike.

method='swift'

TZ002

antisymmetrize

Off-diagonal tensor symmetry needs to be enforced or inspected.

how='rms'

TZ003

sigma_clip

Tensor outlier frequencies should be sigma-clipped.

sigma=3

TZ004

balance_offdiag

Off-diagonal components need balancing.

none

TZ005

rotate_fixed

A known fixed rotation angle should be applied.

angle=0.0

TZ006

invert_tensor

The impedance tensor should be inverted.

none

TZ007

orient_from_sensors

Sensor orientations are known and should be corrected explicitly.

ex=0.0, ey=0.0, bx=0.0, by=0.0, degrees=True

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

DIM001

classify_dim

You need a diagnostic classification of dimensionality. This is a diagnostic step and does not modify sites.

none

DIM002

mask_by_dim

You want to keep or mask samples by dimensionality class before further processing.

dim_type='2d'

DIM003

project_2d

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

SK001

mask_by_skew

Frequencies above a Bahr skewness threshold should be masked.

threshold=0.3

SK002

longest_low_skew

You want the longest continuous low-skew band.

threshold=0.3

SK003

select_skew_band

You want the lowest-skew band selected automatically.

none

SK004

close_skew_gaps

Small gaps in a low-skew interval should be closed.

thresh=3.0, max_gap=1

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

SRC001

correct_near_field

Near-field source effects should be corrected.

none

SRC002

normalize_response

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

QC001

qc_snapshot

You want a quick-look dashboard, station confidence view, and coverage pseudo-section.

none

QC002

field_zone_snapshot

You want a field-zone classification plot.

none

QC003

source_overprint_snapshot

You want a source-overprint diagnostic before or after source-effect corrections.

none

QC004

depth_section_snapshot

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:

  • NR001 can generate a harmonic waterfall and SNR gain profile.

  • FREQ001 can generate band microstrips and a coverage-quality heatmap.

  • SS004 can generate static-shift delta, comparison, and summary plots.

  • QC001 can 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
raise

Stop immediately at the failing step.

warn

Record the error, warn, continue with the previous site collection, and return a nonzero CLI exit status if any step failed.

skip

Record 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 NR001 and tune mains_hz, n_harm, and tol_hz.

For frequency-grid problems

Start with FREQ002 followed by FREQ001 and FREQ004.

For isolated spikes

Start with NR004. Move to NR012 when the smoothing window should be robust to extremes across a broader interval.

For spatially coherent noise

Compare NR005, NR006, and NR007 with QC plots enabled.

For static shift

Start with SS001 for a standard workflow. Use SS002, SS003, or SS004 when 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:

  1. implement or expose the transform function in the appropriate pycsamt.emtools module;

  2. add a pycsamt.pipeline.StepSpec entry in pycsamt/pipeline/_registry.py;

  3. choose a code prefix that matches the category, such as FREQ, NR, SS, TZ, DIM, SK, SRC, or QC;

  4. define conservative defaults;

  5. attach QC plot functions when useful;

  6. decide whether the step returns a modified site collection;

  7. add tests that prove lookup, parameter merging, execution, and config round-tripping;

  8. 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 steps or pycsamt pipe steps --info CODE. Codes are exact and uppercase, for example NR001.

Unknown step name

Use the snake-case registry name, for example notch_powerline rather than notch. 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_in and StepResult.n_sites_out in 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.