Site Containers#

The pycsamt.site.base module provides the station-centric containers used throughout the site layer. These containers wrap SEG-EDI objects without replacing the lower-level EDI parser. Their purpose is to make common survey operations easier: station lookup, coordinate access, tensor inspection, DataFrame export, bulk selection, profile preparation, and writing cleaned sites back to disk.

The main objects are:

Object

Scope

Main use

SiteMixin

One EDI-like site.

Shared accessors for name, coordinates, frequency, impedance, resistivity, phase, tipper, metadata, quality flags, and DataFrame export.

Site

One concrete station.

High-level wrapper around one pycsamt.seg.edi.EDIFile with a stable station identity and convenience edit helpers.

Sites

Many stations.

Ordered collection wrapper with integer indexing, name lookup, selection, batch edits, topography alignment, profile conversion, and export.

to_sites()

API boundary helper.

Coerce compatible EDI-like inputs into a Sites container.

to_edis()

API boundary helper.

Unwrap Site or Sites objects back to raw EDI objects or an pycsamt.seg.collection.EDICollection.

Where Containers Fit#

The site containers sit above pycsamt.seg and below the higher-level workflow layers.

Layer

Main object

Responsibility

EDI parsing

pycsamt.seg.edi.EDIFile

Read, store, and write one SEG-EDI file.

EDI collection

pycsamt.seg.collection.EDICollection

Discover and parse many EDI files from paths, folders, or glob patterns.

Site layer

Site, Sites

Expose station-oriented accessors, editing, filtering, summaries, and survey preparation helpers.

Downstream workflows

Selection, diagnostics, export, profiles, inversion, agents.

Use prepared site containers as consistent inputs.

Use EDIFile or EDICollection when you are primarily parsing SEG-EDI data. Use Site or Sites when you are preparing, inspecting, selecting, or editing station data.

Creating One Site#

Create a Site from a parsed pycsamt.seg.edi.EDIFile.

1from pycsamt.seg.edi import EDIFile
2from pycsamt.site.base import Site
3
4edi = EDIFile("data/edi/S01.edi")
5site = Site(edi)
6
7print(site.name)
8print(site.coords)
9print(site.summary())

The wrapper keeps the original EDI object available as site.edi and through Site.to_edi(). Prefer the Site accessors for routine work, and unwrap to EDI only when you need lower-level EDI functionality.

1raw = site.to_edi()
2detached = site.to_edi(copy=True)

Station Identity#

Station identity is normalized from EDI HEAD fields. The name resolution order is:

  1. dataid;

  2. station;

  3. sitename;

  4. name;

  5. STATION;

  6. the file stem, when header fields are missing.

When a Site is created, the constructor attempts to stabilize the EDI header so that dataid and, when absent, station match the resolved site stem. This makes name lookup and downstream joins more predictable.

1site = Site(EDIFile("data/edi/LINE01_012.edi"))
2
3print(site.name)
4print(site.meta)

Rename a site with Site.rename():

1renamed = site.rename("L01_012")
2
3print(site.name)      # original unchanged
4print(renamed.name)   # new wrapper

Use inplace=True when the existing wrapper should be modified:

1site.rename("L01_012", inplace=True)

Coordinates#

Coordinates are exposed as (lat, lon, elev) in decimal degrees and meters.

1lat, lon, elev = site.coords
2print(lat, lon, elev)

Update coordinates with Site.set_coords():

1moved = site.set_coords(10.25, 20.75, 640.0)
2print(moved.coords)

Like Site.rename(), coordinate updates are copy-returning by default and in-place when requested:

1site.set_coords(10.25, 20.75, 640.0, inplace=True)

Array Accessors#

The container exposes the most common station arrays as properties.

Property

Meaning

Shape convention

freq

Frequency vector in Hz.

(n_freq,).

z

Complex impedance tensor.

Usually (n_freq, 2, 2); flattened internally as Zxx, Zxy, Zyx, Zyy when exported.

z_err

Impedance uncertainty.

Aligned with z when present.

rho

Apparent resistivity.

Aligned with impedance components.

phase

Impedance phase in degrees.

Aligned with impedance components.

tipper

Vertical magnetic transfer function.

(n_freq, 2) with Tx and Ty.

meta

Header and optional INFO metadata.

Python dictionary.

Example:

1print(site.freq)
2print(site.z.shape if site.z is not None else "no Z")
3print(site.quality_flags())

Quality And Component Checks#

Use Site.quality_flags() for coarse array availability checks.

1flags = site.quality_flags()
2
3if not flags["has_z"]:
4    print(f"{site.name} has no finite impedance tensor")

Use Site.has_component() when a specific impedance or tipper component matters.

1if site.has_component("Zxy") and site.has_component("Zyx"):
2    print("off-diagonal impedance components are available")
3
4if site.has_component("tipper"):
5    print("tipper is available")

Supported impedance component names are Zxx, Zxy, Zyx, and Zyy. Tipper checks accept tip, tx, ty, and tipper.

DataFrame Export#

Site.to_dataframe() exports core arrays into frequency-indexed pandas.DataFrame objects. This is the quickest way to inspect one station in a notebook or build masks for editing.

1z = site.to_dataframe("z")
2rp = site.to_dataframe("resphase")
3tip = site.to_dataframe("tipper")
4
5print(z.head())
6print(rp.head())
7print(tip.head())

The accepted kind values are:

Kind

Aliases

Columns

"z"

"imp", "impedance"

Zxx, Zxy, Zyx, Zyy.

"resphase"

"rp", "rho_phase"

rho_zxx, phi_zxx, rho_zxy, phi_zxy, and the equivalent Zyx/Zyy columns.

"tipper"

"tip", "t"

Tx, Ty.

Pass api=True when a pyCSAMT APIFrame is required:

1frame = site.to_dataframe("z", api=True)
2print(frame.kind)
3print(frame.df.head())

Creating A Collection#

Use Sites when working with more than one station.

 1from pycsamt.seg.edi import EDIFile
 2from pycsamt.site.base import Sites
 3
 4edis = [
 5    EDIFile("data/edi/S01.edi"),
 6    EDIFile("data/edi/S02.edi"),
 7    EDIFile("data/edi/S03.edi"),
 8]
 9
10sites = Sites(edis)
11
12print(len(sites))
13print([site.name for site in sites])

If your input is a directory or glob pattern, parse it first with pycsamt.seg.collection.EDICollection, then wrap it:

1from pycsamt.seg.collection import EDICollection
2from pycsamt.site.base import Sites
3
4collection = EDICollection.from_sources("data/edi")
5sites = Sites(collection)

For API boundaries that may receive several input types, use to_sites():

1from pycsamt.site.base import to_sites
2
3sites = to_sites(collection)

Unwrapping Back To EDI#

The site layer is intentionally reversible. Use to_edis() when a workflow has prepared, filtered, renamed, or edited Site objects but the next function expects raw EDI objects.

1from pycsamt.site.base import to_edis
2
3raw = to_edis(site)
4edis = to_edis(sites)
5collection = to_edis(sites, as_collection=True)

By default, unwrapping is shallow: the returned EDI objects are the same objects stored by the wrappers. Pass copy=True when the caller should be able to mutate the returned objects independently.

1detached_edis = to_edis(sites, copy=True)

The helper also accepts mixed inputs, path-like sources, raw EDI objects, and existing EDICollection instances. Invalid items are skipped unless strict=True is requested.

1edis = to_edis([site, raw_edi, "data/edi"], strict=False)
2checked = to_edis(sites, strict=True, progress=True)

Use the collection methods when you are already holding a Sites instance:

1edis = sites.to_edis()
2collection = sites.to_edicollection(copy=True)

Sites.from_any is another entry point when you want pyCSAMT’s session normalization to interpret heterogeneous inputs:

1sites = Sites.from_any("data/edi")

Collection Lookup#

Sites preserves input order and supports integer indexing, case-insensitive name lookup, and safe lookup.

1first = sites[0]
2same = sites.by_index(0)
3station = sites["s02"]
4maybe = sites.get("missing")
5
6print(first.name)
7print(same.name)
8print(station.name)
9print(maybe is None)

sites["missing"] raises KeyError. Use Sites.get() when missing stations are acceptable.

The Sites.as_list() method returns the underlying EDI objects:

1edi_objects = sites.as_list()

This is useful when passing a prepared collection into utilities that still operate directly on EDI objects. For new API boundaries, prefer Sites.to_edis() or to_edis() because they also support copying, progress display, strict validation, and collection output.

Mapping And Selection#

Use Sites.map() to apply a lightweight operation to every station:

1names = sites.map(lambda site: site.name)
2coverage = sites.map(lambda site: site.summary()["nfreq"])

For simple collection filtering, use Sites.select():

1subset = sites.select(names=["S01", "S03"])
2with_zxy = sites.select(predicate=lambda site: site.has_component("Zxy"))

For richer selection by glob names, frequency coverage, coordinates, chainage, or data quality, use the functions documented in Site Selection.

Bulk Edits#

Sites.edit_all() applies common operations across the collection.

1renamed = sites.edit_all(
2    rename=lambda name: f"LINE01_{name}",
3    inplace=False,
4)
5
6sliced = sites.edit_all(
7    freq_slice=slice(1, None),
8    inplace=False,
9)

The optional mask callback receives the output of site.to_dataframe("z") and returns a boolean mask. Rows where the mask is False are set to NaN in the impedance tensor.

1def keep_lower_frequencies(frame):
2    cutoff = frame.index.to_series().median()
3    return frame.index <= cutoff
4
5masked = sites.edit_all(mask=keep_lower_frequencies)

Use pycsamt.site.edit when you need more explicit editing functions such as tensor rotation, coordinate table assignment, frequency range selection, or resistivity/phase recomputation.

Topography Alignment#

Sites.with_topography() updates site coordinates and elevation from a table-like object. Station identifiers are matched by normalized names.

 1import pandas as pd
 2
 3topo = pd.DataFrame(
 4    {
 5        "station": ["S01", "S02"],
 6        "latitude": [10.125, 10.250],
 7        "longitude": [20.500, 20.625],
 8        "elevation": [640.0, 655.0],
 9    }
10)
11
12updated = sites.with_topography(topo, inplace=False)
13print(updated["S01"].coords)

Unmatched stations are left unchanged. Use this after loading raw EDI files when field GPS or corrected elevation tables are authoritative.

Closest Site#

Sites.closest() finds the nearest station to a target coordinate.

1nearest = sites.closest(lat=10.20, lon=20.55)
2
3if nearest is not None:
4    print(nearest.name)

Add tol in meters when a maximum distance is required:

1nearest = sites.closest(lat=10.20, lon=20.55, tol=500.0)

If the closest station is farther than tol, the method returns None.

Profiles#

Sites.to_profile() converts station coordinates into a survey-line profile for line-ordered work.

1profile = sites.to_profile(origin=(10.0, 20.0), azimuth=90.0)
2
3if hasattr(profile, "chainages"):
4    print(profile.chainages)
5else:
6    print([site.name for site in profile["sites"]])

When pycsamt.site.profile.Profile is available, the method returns a rich profile object. Otherwise it returns a fallback dictionary containing origin, azimuth, and line-ordered sites.

For more control over profile inference, chainage, and spacing statistics, continue to Location And Profiles.

Writing Sites#

Sites.write() writes one EDI file per station into a directory.

1paths = sites.write(
2    "outputs/clean_edi",
3    template="{station}.edi",
4    exist_ok=True,
5)
6
7for path in paths:
8    print(path)

For richer export workflows, including manifest CSV files and zip packages, use the functions documented in Export And Reporting.

Common Patterns#

Inspect one station:

1site = sites["S01"]
2print(site.summary())
3print(site.to_dataframe("resphase").head())

Build a QC table:

1import pandas as pd
2
3qc = pd.DataFrame([site.summary() for site in sites])
4print(qc[["name", "nfreq", "components", "tipper"]])

Select, align, and export:

1selected = sites.select(predicate=lambda site: site.has_component("Zxy"))
2updated = selected.with_topography(topo)
3updated.write("outputs/selected_edi", exist_ok=True)

Common Mistakes#

Assuming every object has Sites.from_path

Use pycsamt.seg.collection.EDICollection.from_sources, Sites.from_any(), or to_sites() depending on the input form.

Editing site.edi directly during routine workflows

Direct EDI edits are sometimes necessary, but the site helpers keep name, coordinate, and array conventions more consistent.

Forgetting copy versus in-place behaviour

Site.rename(), Site.set_coords(), Site.set_empty(), Sites.edit_all(), and Sites.with_topography() return new objects by default unless inplace=True is used.

Comparing station names before normalization

Wrap EDI files as Site or Sites before doing name-based joins or lookups.

Using summary as a substitute for data inspection

summary is a quick overview. Use Site.to_dataframe(), Site.quality_flags(), and the diagnostics in Computed Diagnostics for deeper checks.

Next Pages#

Continue with: