17.6. Pipeline Steps#
Pipeline steps are the atomic processing operations used by
pycsamt.pipeline.Pipeline. Each operation is defined once in the
step registry and can then be used from the Python API, YAML/JSON/Python
configuration files, pipeline preset recipes, and the pycsamt pipe
command line.
The registry gives every operation a stable pipeline step code, such
as FREQ001 or NR001. The code is intentionally short because it
appears in terminal output, saved reports, plot folders, and reproducible
pipeline.yaml files. Each code also has a snake-case registry 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.
17.6.1. Step Contract#
A registered step has two layers.
StepSpecStepSpec registry metadata. It stores the code, name, human label, category, default parameters, transform function, QC plot function entries, and whether the operation returns a modified site collection.
StepA configured instance used inside a pipeline. It binds a
StepSpecto user parameter override values.
The relationship is deterministic. If a registry entry has defaults \(\theta_{\mathrm{default}}\) and the user supplies overrides \(\theta_{\mathrm{user}}\), the configured parameters are
where the right-hand values win for keys that appear in both dictionaries.
This is why a short configuration can override only mains_hz for
NR001 while keeping the default n_harm and tol_hz.
In Python, a step is created with pycsamt.pipeline.Step:
1>>> from pycsamt.pipeline import Step
2>>> notch = Step("NR001", mains_hz=50, n_harm=30)
3>>> notch.params
4{'mains_hz': 50, 'n_harm': 30, 'tol_hz': 0.08}
5>>> band = Step("select_band", band_hz=(0.01, 10000.0))
6>>> band.spec.code
7'FREQ001'
The two lookup styles are equivalent:
1>>> Step("NR001").spec is Step("notch_powerline").spec
2True
The first form is better for compact configs and reports. The second form is often easier to read in Python code.
17.6.2. How Steps Execute#
During a run, the pipeline applies each step in order. If the input state is \(S_{j-1}\) and the configured step is \(T_j\), then a normal transform step computes
A diagnostic step instead records figures or summaries while passing the same site collection forward:
Operationally, each step follows this sequence:
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:
1>>> result = pipe.run(sites, outdir="results/basic_qc")
2>>> [(sr.step_code, sr.step_name, sr.ok, sr.n_sites_in, sr.n_sites_out)
3... for sr in result.step_results]
4[('NR001', 'notch', True, 3, 3), ..., ('QC001', 'qc_snapshot', True, 3, 3)]
17.6.3. 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
Captured frequency catalogue excerpt:
1{
2 "frequency": [
3 {
4 "code": "FREQ001",
5 "name": "select_band",
6 "label": "Frequency Band Select",
7 "defaults": {"band_hz": [0.001, 10000.0]},
8 "returns_sites": true
9 },
10 {
11 "code": "FREQ002",
12 "name": "drop_duplicates",
13 "label": "Drop Duplicate Frequencies",
14 "defaults": {},
15 "returns_sites": true
16 }
17 ]
18}
Captured NR001 information, simplified to ASCII:
1NR001 Power-line Harmonic Notch [noise_removal]
2name : notch_powerline
3function : pycsamt.emtools.remove_noise.notch_powerline
4defaults : mains_hz=50 n_harm=30 tol_hz=0.08
5qc plots : nr_qc_harmonic_waterfall, nr_qc_snr_gain_profile
6returns : Sites (transform)
7Usage:
8 Step("NR001")
9 Step("NR001", mains_hz=50)
10 Step("notch_powerline")
17.6.4. Discover Steps From Python#
The same registry is available from Python:
1>>> from pycsamt.pipeline import categories, list_steps, lookup_step
2>>> categories()
3['dimensionality', 'export', 'frequency', 'noise_removal', 'preview', 'qc', 'skew', 'source_effects', 'static_shift', 'tensor']
4>>> {c: len(list_steps(c)) for c in categories()}
5{'dimensionality': 3, 'export': 3, 'frequency': 9, 'noise_removal': 14, 'preview': 2, 'qc': 7, 'skew': 4, 'source_effects': 2, 'static_shift': 4, 'tensor': 7}
6>>> [(spec.code, spec.name, spec.defaults)
7... for spec in list_steps("frequency")[:2]]
8[('FREQ001', 'select_band', {'band_hz': (0.001, 10000.0)}), ('FREQ002', 'drop_duplicates', {})]
9>>> lookup_step("NR001") is lookup_step("notch_powerline")
10True
Use pycsamt.pipeline.Pipeline.step_info() when you want a formatted
human-readable explanation:
1>>> from pycsamt.pipeline import Pipeline
2>>> "Power-line Harmonic Notch" in Pipeline.step_info("NR001")
3True
4>>> "SS001" in Pipeline.catalogue("static_shift")
5True
17.6.5. Registered Categories#
The current registry contains 55 built-in processing and diagnostic steps.
The opt-in AI audit step (AI001, see Extending The Pipeline) is not counted
here since it is not registered by default.
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.
|
|
|
Raw-vs-processed 1-D preview of a deterministic random station
subset, meant to open ( |
|
|
Write processed sites out to ModEM, Occam2D, or MARE2DEM solver input files. |
17.6.6. Recommended Ordering#
The registry does not force a single scientific workflow, because survey physics and data quality differ. Still, most survey pipelines are easier to reason about when step ordering follows the data dependency: operations that define the sampling axis should come before operations that estimate physical structure from that axis. A useful high-level order is:
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.
In symbolic form, a reviewed processing chain often looks like
where \(F_{\mathrm{freq}}\) defines the usable frequency axis,
\(N_{\mathrm{noise}}\) suppresses artifacts, \(G_{\mathrm{dim/skew}}\)
masks intervals that fail the interpretation assumptions, \(C_{\mathrm{ss}}\)
corrects static shift, \(R_{\mathrm{tensor}}\) rotates or balances tensor
components, and \(Q\) records final diagnostics. This is a guide, not a
law. For example, stratagem_mt runs static-shift correction before AMT
band trimming because that workflow expects the complete Stratagem frequency
range for the static-shift estimate.
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:
1>>> from pycsamt.pipeline import Pipeline, Step
2>>> pipe = Pipeline(
3... [
4... ("drop_duplicates", Step("FREQ002")),
5... ("select_band", Step("FREQ001", band_hz=(0.001, 10000.0))),
6... ("align_grid", Step("FREQ004")),
7... ("notch", Step("NR001", mains_hz=50)),
8... ("qc", Step("QC001")),
9... ],
10... name="basic_manual",
11... )
12>>> len(pipe)
135
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.
17.6.7. 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
17.6.8. 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
mains_hz="auto"(NR001only)Real EDI frequency grids are usually log-spaced rather than sampled exactly on multiples of 50/60 Hz, so a fixed numeric
mains_hzcan silently miss harmonics that fall outside the tight+-tol_hzwindow. Passing the literal string"auto"instead makes the match data-driven: pyCSAMT scores 50 Hz and 60 Hz – the only two real-world AC grid frequencies – against the survey’s actual pooled frequency array, resolves to whichever explains more harmonics, then snaps each harmonic to its single nearest real sample within a relativesnap_fractolerance (default 1%, deliberately tight so a coarse, non-mains-aware grid does not get treated as evidence). If neither candidate clears a minimum-evidence bar,"auto"leaves the data untouched and warns rather than guessing:1>>> from pycsamt.api import read_edis 2>>> from pycsamt.emtools.remove_noise import notch_powerline 3>>> survey = read_edis("data/AMT/WILLY_DATA/L18PLT", recursive=False, strict=False, progress=False) 4>>> out = notch_powerline(survey.collection, mains_hz="auto", verbose=1)
A
UserWarningreports the resolved fundamental (captured against the real 28-stationL18PLTline):1notch_powerline: mains_hz="auto" resolved to 60 Hz (4/30 harmonics matched within 1%) for this survey.Passing a plain number is completely unaffected – output is identical to every previous release.
verbose=2additionally logs every individual snapped harmonic (station, target frequency, the real frequency substituted for it, and the offset). In a config file:1- name: notch 2 code: NR001 3 params: 4 mains_hz: auto
17.6.9. 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
17.6.10. 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:
1>>> from pycsamt.pipeline import Pipeline, Step
2>>> pipe = Pipeline(
3... [
4... ("drop_duplicates", Step("FREQ002")),
5... ("select_band", Step("FREQ001", band_hz=(1.0, 10000.0))),
6... ("static_shift", Step("SS001")),
7... ("rotate", Step("TZ001", method="swift")),
8... ("qc", Step("QC001")),
9... ],
10... name="tensor_ready",
11... )
12>>> [label for label, step in pipe]
13['drop_duplicates', 'select_band', 'static_shift', 'rotate', 'qc']
17.6.11. 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
17.6.12. 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
17.6.13. 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
17.6.14. 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
17.6.15. Export Steps#
Export steps write processed sites out to solver-ready input files. Unlike
QC steps they are not diagnostic-only: returns_sites=True, so a real
write failure propagates through the pipeline’s normal on_step_error
policy rather than being silently turned into a warning.
Code |
Name |
Use when |
Defaults |
|---|---|---|---|
|
|
You want ModEM data, model, and control files (plus covariance for a 3-D run). |
|
|
|
You want a full native Occam2D data/mesh/model/startup working directory. |
|
|
|
You want the MARE2DEM |
|
EXPORT003 deliberately writes only the data file.
pycsamt.models.mare2dem.builder.InputBuilder.write_resistivity/.write_settings
are separate calls with their own configuration, and full mesh generation
(the .poly file) is not wired into any pipeline step yet.
A step function only ever receives sites and its own parameters, with no
hook into the run’s own output directory, so each export step’s workdir
resolves relative to the process’s current working directory rather than
following --out. Point it explicitly at wherever the run’s output
should live:
1>>> from pycsamt.pipeline import Pipeline, Step
2>>> pipe = Pipeline(
3... [
4... ("notch", Step("NR001")),
5... (
6... "export_modem",
7... Step("EXPORT001", workdir="results/line22_export/modem"),
8... ),
9... ],
10... name="export_ready",
11... )
12>>> [label for label, step in pipe]
13['notch', 'export_modem']
Captured against the real 25-station WILLY L22 line:
1pycsamt pipe run data/AMT/WILLY_DATA/L22PLT \
2 --steps NR001,EXPORT001,EXPORT002,EXPORT003 \
3 --out results/line22_export
1PipelineResult 'cli_pipeline'
2 Sites : 25 in → 25 out
3 Steps : 4 (4 ok, 0 err)
4 Time : 12.15 s
5 Plots : 2
6 Output : results\line22_export
Files actually written under each step’s default workdir:
1pipeline_exports/modem/ control.inv covariance.cov data.dat m0.ws
2pipeline_exports/occam2d/ Occam2DMesh Occam2DModel OccamDataFile.dat Startup
3pipeline_exports/mare2dem/ line.emdata
17.6.16. Parameters And Defaults#
User parameters are merged on top of registry defaults. If a default is not overridden, the registry value is used. The merge is shallow and right-biased:
1>>> from pycsamt.pipeline import Step
2>>> step = Step("NR001", mains_hz=60)
3>>> step.params["mains_hz"]
460
5>>> step.params["n_harm"]
630
7>>> step.params["tol_hz"]
80.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. For reviewed work, write every scientifically
important parameter explicitly even when it equals the default; that makes the
processing choice visible in the configuration file.
17.6.17. 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.
17.6.18. Diagnostic Plots#
Each StepSpec may declare QC plot function entries. When plotting is enabled, the pipeline calls those functions after a successful transform and saves the figures under the run output directory. The files are diagnostic artifacts; they do not feed back into the next transform unless a separate step explicitly uses the underlying data.
The plot call order for step \(j\) is
where \(Q_j\) is the set of registered QC plot functions for that step. Diagnostic-only QC steps have \(S_j=S_{j-1}\), but their plots still describe the current state of the survey at that checkpoint.
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.
17.6.19. 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.
17.6.20. 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.
17.6.21. 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 thresh: 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
17.6.22. 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.
A second, runtime path exists for steps that should not be reviewed into
pyCSAMT itself, such as a site-specific correction or a vendor-format
adapter: register_step adds a StepSpec to the same registry without
touching pyCSAMT’s source tree, and the resulting entry is usable everywhere
a built-in step is. See Extending The Pipeline for the full mechanism, including
how a third-party package can announce its steps through an entry point so
they are found automatically rather than registered by hand in every script.
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:
1>>> StepSpec(
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.
17.6.23. Testing New Steps#
At minimum, a new step should be covered by tests like these:
1>>> from pycsamt.pipeline import Pipeline, Step, lookup_step
2>>>
3>>> def 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...
8>>> def test_step_defaults_and_overrides():
9... step = Step("NR015", window=7)
10... assert step.params["window"] == 7
11...
12>>> def 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.
17.6.24. 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.