10.7. The StratagemSurvey Pipeline#

Every page from Stratagem Concepts through Export and Renaming worked one stage of the K2 survey at a time – loading, coordinate injection, QC, static-shift correction, frequency filtering, noise removal, export – each rebuilt from scratch to keep that page self-contained. StratagemSurvey is the same eight stages composed into a single object with a fluent, chainable API: fit() loads and injects coordinates, and every step after it – run_qc(), remove_static_shift(), drop_frequencies(), remove_noises(), export(), rename() – returns self, so the whole survey reads as one expression. Nothing about composing them changes what each stage does; the point of this page is showing that the numbers already verified piece by piece across this section come out identically when the pipeline runs them end to end.

10.7.1. The fit() Step#

fit() is the one step every other method depends on: it loads the EDI batch (and, if raw_dir is given, the raw hardware reader), drops any drop_stations before coordinate injection, and runs CoordinateInjector. K2’s station 1 – the calibration/test shot from Stratagem Concepts – is dropped by its 0-based EDIBatch index, exactly the way Coordinate Injection did it by hand:

>>> import pandas as pd
>>> from pathlib import Path
>>> from pycsamt.stratagem import StratagemSurvey

>>> aligned = pd.read_csv("data/stratagem/K2/k2-gps-aligned.csv")
>>> survey_coords = aligned[aligned["use_for_survey"]]
>>> survey_coords.to_csv("k2_coords_for_injection.csv", index=False)

>>> sv = StratagemSurvey(
...     edi_dir="data/stratagem/K2/k2-edi",
...     coord_file="k2_coords_for_injection.csv",
...     raw_dir="data/stratagem/K2/k2-HX",
...     epsg=32649,
...     order="forward",
...     drop_stations=[0],
...     easting_col="easting",
...     northing_col="northing",
...     elev_col="elev",
...     station_col="edi_file",
... ).fit()

>>> sv.batch_.n_stations_, sv.raw_reader_.n_stations_, sv.n_stations_
(87, 87, 86)
>>> sv.edi_objects_[0].station
'Z2HX002'

batch_/raw_reader_ still report all 87 stations – they are the raw loaders, untouched by drop_stations – while n_stations_ and edi_objects_ reflect the 86 that actually went into coordinate injection, starting at station 2 exactly as Coordinate Injection and Loading Hardware and EDI Data found by hand. coord_file still needs one row per surviving station, which is why survey_coords is filtered by use_for_survey before being written, the same filtering Coordinate Injection did explicitly.

10.7.2. Chained Processing Steps#

Every subsequent call mutates edi_objects_ in place and returns self, so the full pipeline – QC, static-shift correction, frequency filtering, noise removal – reads as one continued expression:

>>> sv = (
...     sv
...     .run_qc()
...     .remove_static_shift()
...     .drop_frequencies(fmin=10.0, fmax=10000.0)
...     .remove_noises()
... )
>>> print(sv.summary())
StratagemSurvey
  edi_dir    : data/stratagem/K2/k2-edi
  coord_file : k2_coords_for_injection.csv
  n_stations : 86
  epsg / zone: 32649 / 49N
  raw_reader : yes (87 stations)
  coord order: forward
  QC flags   : 86 / 86 stations
  SS fac_z   : median=0.952
  freq filter: hw=460, band=2832, incoh=0
  noise rm   : 86 stations

Every number here is one already seen in isolation: 86 of 86 stations flagged at the default QC skew threshold (Quality Control), a median static-shift factor of 0.952 (Static Shift and Noise Removal), 460 hardware- masked and 2832 band-dropped frequency cells with nothing left for the incoherence stage to catch (Quality Control again). Running the whole chain through one object rather than by hand changes nothing about the computation – it is the same CoordinateInjector, QualityController, StaticShiftCorrector, FrequencyFilter, and NoiseRemover underneath, only wired together and stored on self instead of tracked by hand across separate variables.

10.7.3. Export and Rename#

export() and rename() close the chain out the same way Export and Renaming did directly with EDIWriter/EDIRenamer:

>>> sv = sv.export("k2_corrected").rename(basename="T2.", dst_path="k2_renamed")
>>> exported = sorted(Path("k2_corrected").glob("*.edi"))
>>> renamed = sorted(Path("k2_renamed").glob("*.edi"))
>>> len(exported), exported[0].name, exported[-1].name
(86, 'Z2HX002.edi', 'Z2HX087.edi')
>>> len(renamed), renamed[0].name, renamed[-1].name
(86, 'T2.000.edi', 'T2.085.edi')

export keeps each station’s original filename (the same EDIWriter-default behaviour from Export and Renaming), and rename imposes the sequential T2.0NN.edi convention on top of that – export’s output is untouched by it, since rename defaults to reading from the most recent export directory rather than overwriting it. The same reminder from Export and Renaming applies here too: if the destination were sites_’s generic Sites.write() instead of rename(), the T2.0NN identity would not carry over to it.

10.7.4. Alternative EDI Sources#

edi_dir does not require a directory path. Anything ensure_sites() already accepts – a Sites, an EDICollection, or a plain list of EDIFile – works too, which is useful when a batch is already loaded and processed by other pycsamt.emtools code before it reaches Stratagem-specific handling:

>>> from pycsamt.stratagem import EDIBatch
>>> from pycsamt.emtools import ensure_sites

>>> batch = EDIBatch("data/stratagem/K2/k2-edi").fit()
>>> sites = ensure_sites(batch.edi_objects_)
>>> survey_coords.to_csv("k2_coords_for_injection.csv", index=False)

>>> sv2 = StratagemSurvey(
...     edi_dir=sites,
...     coord_file="k2_coords_for_injection.csv",
...     epsg=32649,
...     order="forward",
...     drop_stations=[0],
...     easting_col="easting",
...     northing_col="northing",
...     elev_col="elev",
...     station_col="edi_file",
... ).fit()
>>> sv2.batch_ is None, sv2.n_stations_, sv2.edi_objects_[0].station
(True, 86, 'Z2HX002')

batch_ stays None in this form – there was no directory listing to report, only whatever order sites already had – but every other attribute behaves identically, including drop_stations counting against sites’ own order the same way it counts against EDIBatch’s natural-sorted order.

10.7.5. Validating a Full Run#

Composing every stage into one call does not remove the reasons Static Shift and Noise Removal and Quality Control gave for checking intermediate results before trusting a correction – those results are still sitting on sv, not hidden behind the fluent interface:

>>> len(sv._ss_corrector_.factors_)
74
>>> round(sv._ss_corrector_.factors_["fac_z"].max(), 2)
93.03
>>> len(sv.qc_.flagged_stations())
86

Station 61’s fac_z=93 outlier from Static Shift and Noise Removal is exactly as reachable here, through sv._ss_corrector_.factors_, as it was from a standalone StaticShiftCorrector. Nothing about running the whole survey as one expression is a reason to skip reading factors_, qc_.flagged_stations(), or coordinate_frame before treating the exported EDIs as final – the one-call form saves bookkeeping, not judgement. With that, the K2 line is fully reconciled, corrected, and exported: from raw hardware files and a misleading GPS table in Stratagem Concepts to 86 renamed, denoised EDIs ready for EM Tools Guide or inversion.