Data Loading#
Data loading is the first boundary in a pyCSAMT workflow. The goal is to turn files, directories, parsed EDI objects, or existing site containers into one predictable object that the science tools can use.
For processing workflows, the canonical loader is
pycsamt.emtools._core.ensure_sites(). It returns a
pycsamt.site.base.Sites container. Most functions in
pycsamt.emtools call this same helper internally, so using it in notebooks,
scripts, agents, and tests gives you the same behavior as the package itself.
Use pycsamt.api.read_edis() when you want a public API survey view for
notebooks, reports, and API-style tables. The survey view still wraps the same
EDI-level data, but ensure_sites is the direct science/workflow input.
Loading EDI Directories#
The common case is a directory containing one or more .edi files. The
example below uses one of the repository’s example survey lines.
1from pycsamt.emtools import ensure_sites
2
3edi_dir = "data/AMT/WILLY_DATA/L18PLT"
4sites = ensure_sites(
5 edi_dir,
6 recursive=True,
7 strict=False,
8 on_dup="replace",
9 verbose=0,
10)
11
12print(f"Loaded {len(sites)} stations")
13print([site.name for site in list(sites)[:3]])
Line 1 imports the loader used by the processing modules. Line 3 points at a
directory rather than at a single file. Lines 4-10 normalize the input into a
Sites object. recursive=True searches subdirectories, strict=False
allows malformed or unrelated files to be skipped, and on_dup="replace"
keeps the last station encountered when duplicated station names are found.
The returned sites object is iterable. Each item is a
pycsamt.site.base.Site wrapper around one parsed EDI object.
Loading Explicit Files#
When the desired stations are known, pass a list of files. This avoids accidentally loading scratch files or neighboring survey lines.
1from pathlib import Path
2
3from pycsamt.emtools import ensure_sites
4
5line = Path("data/AMT/WILLY_DATA/L18PLT")
6selected = [
7 line / "18-001A.edi",
8 line / "18-002U.edi",
9 line / "18-003A.edi",
10]
11
12sites = ensure_sites(selected, recursive=False, strict=True)
Line 6 creates an explicit list of path-like objects. Line 13 disables
directory recursion because the input is already a file list. strict=True
is useful here: if none of the requested inputs can be resolved into valid
stations, loading fails immediately instead of returning an empty container.
Normalizing Existing Objects#
ensure_sites is intentionally permissive at the boundary. It accepts:
a directory, a single EDI path, a glob-like path, or a list of paths;
an
EDIFileor anEDICollection;a single
Site;an existing
Sitesobject;an iterable containing EDI-like objects.
This lets you normalize once and then write the rest of the workflow against
Sites.
1from pycsamt.api import read_edis
2from pycsamt.emtools import ensure_sites
3
4survey = read_edis("data/AMT/WILLY_DATA/L18PLT", recursive=True)
5sites = ensure_sites(survey.collection, recursive=False)
6
7same_sites = ensure_sites(sites)
8assert same_sites is sites
Lines 4-5 show the bridge from the public API reader to the science container.
read_edis returns an APISurvey view, while survey.collection is the
underlying EDI collection. Line 7 is a no-op normalization: if the input is
already a Sites instance, the same object is returned.
Inspecting Loaded Stations#
Before applying QC, static-shift correction, dimensionality analysis, or inversion preparation, inspect what actually loaded.
1first = sites[0]
2same = sites[first.name]
3maybe = sites.get("missing_station")
4
5print(first.summary())
6print(first.has_component("Zxy"))
7print(first.has_component("tipper"))
8
9for site in sites:
10 row = site.summary()
11 print(row["name"], row["nfreq"], row["lat"], row["lon"])
Line 1 retrieves by zero-based position. Line 2 retrieves by station name;
name lookup is case-insensitive and raises KeyError if the station is not
present. Line 3 uses safe lookup and returns None when the station is
missing.
Site.summary() returns a dictionary with the station name, number of
frequencies, coordinates, present impedance components, and whether tipper data
are available. has_component("Zxy") and has_component("tipper") are
quick checks before running tools that require those arrays.
Selecting Stations#
Use Sites.select to keep a named subset or stations matching a predicate.
The method returns a new Sites container and does not mutate the original.
1named = sites.select(names=["18-001A", "18-002U"])
2
3with_tipper = sites.select(
4 predicate=lambda site: site.has_component("tipper")
5)
6
7dense = sites.select(
8 predicate=lambda site: site.summary()["nfreq"] >= 20
9)
Line 1 keeps specific station names. Lines 3-5 keep only stations with tipper
data. Lines 7-9 keep stations with at least 20 frequency samples. Predicates
receive a Site object, so they can use station names, coordinates,
frequency counts, and component checks.
Duplicate Stations#
Field directories sometimes contain repeated station names: an original file, a corrected export, and a manually edited copy may all describe the same station. Choose the policy explicitly.
1latest = ensure_sites("data/edis", on_dup="replace")
2first = ensure_sites("data/edis", on_dup="keep_first")
3last = ensure_sites("data/edis", on_dup="keep_last")
4
5checked = ensure_sites(
6 "data/edis",
7 on_dup="raise",
8 strict=True,
9)
replace is the default and keeps the latest duplicate found by the lower
level collection loader. keep_first keeps the first station name
encountered. keep_last keeps the last station name encountered. raise
is the safest production setting when duplicates should be treated as a data
management error.
For path inputs, pyCSAMT maps these policies onto the EDI collection parser
and performs any extra duplicate check after parsing. For already parsed
objects, the same policy is applied while building the Sites container.
Strict Versus Exploratory Loading#
Use strict=False while exploring mixed folders. Use strict=True when a
pipeline should fail early if no valid stations can be loaded.
1exploratory = ensure_sites(
2 "field_drop/raw_export",
3 recursive=True,
4 strict=False,
5 verbose=1,
6)
7
8production = ensure_sites(
9 "field_drop/approved_edi",
10 recursive=True,
11 strict=True,
12 on_dup="raise",
13 verbose=1,
14)
In the exploratory call, unrelated files or broken EDI files can be skipped so
you can inspect what is salvageable. In the production call, an empty result
raises ValueError and duplicated station names raise before downstream
processing can silently use the wrong station.
Using Loaded Data In Processing#
Once data are normalized to Sites, pass the same object to QC,
static-shift, plotting, frequency tools, and inversion preparation.
1from pycsamt.emtools.qc import build_qc_table, qc_flags
2from pycsamt.emtools.ss import estimate_ss_ama
3
4qc_table = build_qc_table(sites)
5flags = qc_flags(sites, min_frac_ok=0.6, min_snr_med=2.0)
6ss_table = estimate_ss_ama(
7 sites,
8 half_window=3,
9 sort_by="lon",
10 verbose=0,
11)
12
13print(qc_table.head())
14print(flags.head())
15print(ss_table)
Lines 4-11 reuse the same loaded container. This is the expected pattern:
normalize at the boundary, then pass sites through the workflow. You do
not need to reload the EDI directory for each processing step.
Loading Through The Agent Layer#
The loader agent is useful when a workflow should return an
AgentResult with status, summary text, and a quality table.
1from pycsamt.agents.loader import MTLoaderAgent
2
3agent = MTLoaderAgent(recursive=True, on_dup="replace")
4result = agent.execute({
5 "path": "data/AMT/WILLY_DATA/L18PLT",
6})
7
8if result.status != "success":
9 raise RuntimeError(result.summary)
10
11print(result["n_stations"])
12print(result["quality_table"])
Lines 3-6 delegate loading to pycsamt.agents.loader.MTLoaderAgent.
Internally, the agent calls ensure_sites and then performs a lightweight
quality scan. Use this route for conversational workflows, dashboards, and
pipeline orchestration. Use ensure_sites directly for lower-level scripts
and notebooks.
Writing And Reloading EDI Files#
Sites.write writes one EDI file per station. This is useful after
renaming, topography alignment, frequency slicing, denoising, or correction
steps that return a new Sites object.
1from pathlib import Path
2
3outdir = Path("results/loaded_line")
4written = sites.write(
5 outdir,
6 template="{station}.edi",
7 exist_ok=True,
8)
9
10reloaded = ensure_sites(outdir, recursive=False, strict=True)
11print([path.name for path in written])
12print(len(reloaded))
Lines 4-8 create or reuse the output directory and write station files with names derived from normalized station IDs. Line 10 reloads the written files as a quick integrity check before handing them to another tool.
Troubleshooting#
ensure_sites: got NoneThe loader received
None. Check that your script is passing a real path, EDI object,Site,Sites, or iterable of those objects.ensure_sites(strict=True): no sites were resolvedNo valid EDI-like items were found. Confirm the path, enable
recursive=Truefor nested folders, and inspect whether files contain impedance data.Duplicate site ... encountered with on_dup='raise'Two or more inputs have the same station identity. Remove the duplicate files, rename stations intentionally, or use
keep_first/keep_lastduring exploratory work.- An empty or tiny station count
Make sure you are pointing to the survey-line directory, not a parent folder with unrelated files. Use an explicit file list when only selected stations should be loaded.
- Missing
Zor tipper components Loading can succeed even when optional components are absent. Check
site.summary()andsite.has_component(...)before running a method that requires specific transfer-function components.
See Also#
- Quick Start
End-to-end first workflow using real v2 entry points.
- Read an EDI Survey
Public
read_edistutorial and API survey view examples.- pycsamt.api
Public API helpers, including
read_edis.pycsamt.emtools._coreCanonical
ensure_sitesloader used by processing tools.pycsamt.site.baseSiteandSitescontainer implementation.