pycsamt.site#

Survey-site containers, station selection, profile management, editing, computed geometry, export, and reporting helpers.

Survey sites, station collections, diagnostics, editing, and export helpers.

class pycsamt.site.SiteMixin(edi)#

Bases: CoreObject

Lightweight wrapper exposing station-centric accessors and utilities for a single EDIFile.

The mixin normalizes common site operations around the underlying EDI content, including coordinate handling, impedance arrays, tipper, derived resistivity/phase, and convenient export to pandas.DataFrame.

Notes

The station name is resolved from EDI HEAD fields in the following order: dataid, station, sitename, name, STATION. If none is present, the file stem is used. See _station_name().

The Z tensor is treated as an array of shape (n, 2, 2) or flattened to (n, 4) as needed. The order is: Zxx, Zxy, Zyx, Zyy.

See also

pycsamt.seg.edi.EDIFile

Parsed SEG-EDI container.

pycsamt.site.base.Site

Concrete site wrapper that enforces a stable ID.

pycsamt.site.base.Sites

Collection helper for multiple sites.

References

Parameters:

edi (EDIFile)

property name: str#

Station identifier resolved from the EDI header or file stem.

Returns:

Station name used for lookups and display.

Return type:

str

Notes

The resolution order is defined in _station_name().

property coords: tuple[float, float, float]#

Geographic coordinates of the site.

Returns:

A 3-tuple (lat, lon, elev) in decimal degrees and meters.

Return type:

tuple of float

Notes

This accessor relies on _get_coords() which parses EDI HEAD latitude, longitude and elevation.

property freq: Any#

Frequency vector extracted from the Z section.

Returns:

One-dimensional array of frequencies in Hz, or None if missing.

Return type:

array-like or None

See also

to_dataframe

Tabular export of arrays at each frequency.

property z: Any#

Complex impedance tensor across frequencies.

Returns:

Array with shape (n, 2, 2) or flattened to (n, 4) depending on context, or None if absent.

Return type:

array-like or None

Notes

Flattening order is Zxx, Zxy, Zyx, Zyy.

property z_err: Any#

Uncertainty associated with the impedance tensor.

Returns:

Error array aligned with :pyattr:`z`, or None if absent.

Return type:

array-like or None

property rho: Any#

Apparent resistivity derived from the impedance tensor.

Returns:

Resistivity values per component and frequency, or None if not computed or absent.

Return type:

array-like or None

Notes

Derived fields may be recomputed downstream when the Z tensor or frequency vector is updated.

property phase: Any#

Impedance phase (degrees) derived from the tensor.

Returns:

Phase values per component and frequency, or None if not computed or absent.

Return type:

array-like or None

property tipper: Any#

Vertical magnetic transfer function (tipper).

Returns:

Two columns (Tx, Ty) per frequency, or None if missing.

Return type:

array-like or None

property meta: dict[str, Any]#

Minimal metadata snapshot collected from the EDI.

Returns:

Dictionary that may include station, name, lat, lon, elev, dataid, sitename, and an INFO sub-dict if present.

Return type:

dict

Notes

Values are read on a best-effort basis and non-existing keys are omitted.

to_dataframe(kind='z', *, api=None)#

Export core arrays to a tidy pandas.DataFrame.

Parameters:
  • kind ({"z", "imp", "impedance", "resphase", "rp",) – “rho_phase”, “tip”, “tipper”, “t”}, optional Selects which quantity to export. The default is "z".

  • api (bool | None)

Returns:

Frame indexed by frequency (name "f"). Columns depend on kind: - "z": Zxx, Zxy, Zyx, Zyy (complex values). - "resphase": pairs of columns per component

rho_* and phi_*.

  • "tipper": Tx, Ty.

Return type:

pandas.DataFrame

Raises:

ValueError – If kind is not recognized.

Notes

Missing arrays yield empty frames with the correct index.

Examples

>>> df = site.to_dataframe("z")
>>> df.columns
Index(["Zxx","Zxy","Zyx","Zyy"], dtype="object")

See also

quality_flags

Quick presence checks of available arrays.

quality_flags()#

Report presence and basic validity of key arrays.

Returns:

Flags: has_freq, has_z, has_z_err, has_rho, has_phase, has_tipper. A flag is True if the array exists, has non-zero size, and all values are finite.

Return type:

dict

Examples

>>> site.quality_flags()["has_z"]
True
has_component(comp)#

Check if a given component contains any finite value.

Parameters:

comp (str) – One of "Zxx", "Zxy", "Zyx", "Zyy" for impedance, or "tip", "tx", "ty", "tipper" for tipper.

Returns:

True if the component exists and has at least one finite value.

Return type:

bool

Notes

Component names are case-insensitive.

Examples

>>> site.has_component("Zxy")
True
summary()#

Summarize site identity, geometry, and data coverage.

Returns:

Keys include: name, nfreq, lat, lon, elev, components (present Z components), and tipper (boolean).

Return type:

dict

Examples

>>> s = site.summary()
>>> s["name"], s["nfreq"]
('E01', 37)
rename(new, *, inplace=False)#

Set a new station identifier across common header fields.

Parameters:
  • new (str) – New station name or ID.

  • inplace (bool, optional) – If True, modify this instance. If False, return a new Site. The default is False.

Returns:

The modified site (new instance unless inplace).

Return type:

Site

Notes

The method writes to EDI HEAD fields dataid, station, sitename, name (best effort) and also updates edi.name. Downstream containers that derive station IDs from the file stem may be configured to prefer edi.name to preserve the rename.

Examples

>>> s2 = site.rename("X_E01")
>>> s2.name
'X_E01'
set_coords(lat, lon, elev=None, *, inplace=False)#

Update the site coordinates in the EDI HEAD section.

Parameters:
  • lat (float) – Latitude in decimal degrees.

  • lon (float) – Longitude in decimal degrees.

  • elev (float, optional) – Elevation in meters. If None, elevation is left unchanged. The default is None.

  • inplace (bool, optional) – If True, modify this instance. If False, return a new Site. The default is False.

Returns:

The modified site (new instance unless inplace).

Return type:

Site

Notes

Latitude and longitude are validated by utility functions to ensure plausible values.

Examples

>>> site = site.set_coords(10.0, 20.0, 100.0)
>>> site.coords
(10.0, 20.0, 100.0)
set_empty(*, inplace=False)#

Clear Z-related arrays to an empty dataset.

Parameters:

inplace (bool, optional) – If True, modify this instance. If False, return a new Site. The default is False.

Returns:

The modified site (new instance unless inplace).

Return type:

Site

Notes

The following arrays are replaced with empty arrays: freq, z, z_error, rho, and phase. Use this to initialize a skeleton record without data.

Examples

>>> s2 = site.set_empty()
>>> s2.to_dataframe("z").empty
True
class pycsamt.site.Site(edi)#

Bases: SiteMixin

High-level wrapper for a single MT/CSAMT site backed by an EDIFile. The class enforces a stable, file-stem-based station identifier and exposes convenient accessors for coordinates, impedance Z, tipper, and derived quantities.

The constructor normalizes EDI HEAD fields so that dataid (and, if absent, station) matches the site stem resolved by _stem_from_edi(). If an in-memory edi.name exists, the stem may prefer it so that a prior rename is preserved. This normalization improves name-based indexing, deterministic selection, and downstream joins in collections.

Parameters:

edi (pycsamt.seg.edi.EDIFile) – Parsed SEG-EDI container holding one station. The file may be constructed from disk or synthesized in memory.

Variables:
  • edi (pycsamt.seg.edi.EDIFile) – Underlying EDI object. Use with care; prefer the typed accessors of SiteMixin (e.g., freq, z).

  • name (str) – Normalized station identifier. By default this equals the file stem, unless a previous explicit rename is in effect.

  • coords (tuple of float) – Geographic location as (lat, lon, elev). Latitude and longitude are in degrees; elevation is in meters.

Notes

Identity policy

The site identity is derived from header fields in the following order: dataid, station, sitename, name, STATION. If none are present, the file stem is used. The constructor ensures that dataid is set to the resolved stem to stabilize lookups.

Array conventions

The impedance tensor \(Z\) may be represented as a 3D array with shape (n, 2, 2) or flattened to (n, 4) in the component order Zxx, Zxy, Zyx, Zyy. Frequencies are 1D of length n. The tipper has two columns Tx and Ty.

Derived fields

Apparent resistivity and phase are derived from \(Z\) and the frequency vector. When Z or frequency slices are applied, recomputation is triggered after arrays are aligned to avoid inconsistent shapes.

Robustness

Accessors are defensive. Missing arrays produce empty frames or None. Coordinates are validated for range using utility checks.

Examples

Basic construction and inspection
>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.base import Site
>>> e = EDIFile("E01.edi")        # parse from disk
>>> s = Site(e)
>>> s.name
'E01'
>>> s.coords
(..., ..., ...)
>>> s.summary()["nfreq"] >= 0
True
Tabular export
>>> from pycsamt.site.base import Site
>>> dfz = s.to_dataframe("z")
>>> list(dfz.columns)
['Zxx', 'Zxy', 'Zyx', 'Zyy']
>>> dfrp = s.to_dataframe("resphase")
>>> sorted([c for c in dfrp.columns if c.startswith("rho_")])[:2]
['rho_zxx', 'rho_zxy']
Rename without mutating the original instance
>>> s2 = s.rename("X_E01")   # returns a new Site
>>> s2.name
'X_E01'
>>> s.name  # original unchanged
'E01'
Coordinate update
>>> s3 = s.set_coords(10.0, 20.0, 100.0)
>>> s3.coords
(10.0, 20.0, 100.0)
With a collection
>>> from pycsamt.site.base import Sites
>>> e2 = EDIFile("E02.edi")
>>> col = Sites([s.edi, Site(e2).edi])
>>> [t.name for t in col]
['E01', 'E02']
>>> col["E02"].summary()["name"]
'E02'

See also

pycsamt.site.base.SiteMixin

Mixin providing typed accessors and utilities.

pycsamt.site.base.Sites

Collection helper for selection, slicing, and bulk edits.

pycsamt.seg.edi.EDIFile

Low-level SEG-EDI container.

References

to_edi(*, copy=False)#

Return the underlying EDI object.

Parameters:

copy (bool, default False) – If True, return a best-effort deep copy of the wrapped EDI object. If copying fails, the original object is returned.

Returns:

EDI object wrapped by this Site.

Return type:

pycsamt.seg.edi.EDIFile

See also

pycsamt.site.base.to_edis

General unwrapping helper for Site/Sites and mixed inputs.

class pycsamt.site.Sites(edic)#

Bases: CoreObject

Container for multiple Site objects with convenient indexing, selection, and bulk edit operations.

Sites wraps each provided EDIFile into a Site, ensuring that station identity is normalized consistently. You can iterate, index by integer, or look up by case-insensitive station name. Bulk operations such as renaming, frequency slicing, and masking are provided via edit_all().

Parameters:

edic (pycsamt.seg.collection.EDICollection or sequence of) – pycsamt.seg.edi.EDIFile Parsed EDI collection or any sequence of EDI objects. Items are wrapped into Site instances in the order provided.

Variables:

_items (list of Site) – Internal sequence of sites. This is considered private. Iterate over Sites instead of accessing it directly.

Notes

Identity and lookup

Each site inside the container uses the same naming policy as Site. Name-based lookups with sites["E01"] are case-insensitive and match the normalized station name. If duplicates exist, the first match is returned.

Order preservation

Ordering of input items is preserved. Integer indexing with sites[i] retrieves the i-th Site.

Bulk edits

edit_all() supports three common operations: - rename: compute a new name from the old name. - freq_slice: apply a frequency slice (slice)

consistently across Z, freq, errors, and derived fields.

  • mask: apply a boolean mask to the Z tensor rows.

Use inplace=True to modify the container, otherwise a new Sites is returned.

Geospatial helpers

closest() uses geodetic distance to find the nearest site to a given latitude and longitude. Sites with missing or non-finite coordinates are skipped.

Topography integration

with_topography() aligns site coordinates and elevation with a user-provided frame, returning a new container unless inplace=True is requested.

Profile conversion

to_profile() attempts to build a Profile when that optional dependency is available. Otherwise, a lightweight dict describing a chainage-ordered sequence is returned.

Persistence

write() emits one EDI file per site into a target directory. If an EDI object provides to_file, it is used; otherwise a placeholder is written.

Robustness and errors
  • __getitem__ raises KeyError when a name is not found. Prefer get() to obtain None instead.

  • Bulk operations ignore missing arrays on a best-effort basis so that other arrays can still be processed.

Examples

Build from a few EDI files
>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.base import Sites
>>> e1, e2 = EDIFile("E01.edi"), EDIFile("E02.edi")
>>> sites = Sites([e1, e2])
>>> len(sites)
2
>>> [s.name for s in sites]
['E01', 'E02']
Indexing and lookup
>>> sites[0].name
'E01'
>>> sites["e02"].name
'E02'
>>> sites.get("missing") is None
True
Bulk rename without mutating the original container
>>> def rnm(n):
...     return "X_" + n
>>> out = sites.edit_all(rename=rnm, inplace=False)
>>> [s.name for s in out]
['X_E01', 'X_E02']
>>> [s.name for s in sites]
['E01', 'E02']
Frequency slice across all arrays
>>> sl = slice(1, None)  # drop the first frequency
>>> out2 = sites.edit_all(freq_slice=sl, inplace=False)
>>> f0 = sites["E01"].freq
>>> f1 = out2["E01"].freq
>>> len(f1) == len(f0) - 1
True
Selection by names or predicate
>>> subset = sites.select(names=["E02"])
>>> [s.name for s in subset]
['E02']
>>> # predicate: keep sites with tipper available
>>> subset2 = sites.select(predicate=lambda s: s.has_component("tipper"))
>>> isinstance(subset2, Sites)
True
Nearest station to a target location
>>> near = sites.closest(lat=10.0, lon=20.0, tol=None)
>>> near is None or hasattr(near, "name")
True
Persist to a directory
>>> import tempfile, pathlib
>>> tmp = pathlib.Path(tempfile.mkdtemp())
>>> out_paths = sites.write(tmp, template="{station}.edi", exist_ok=True)
>>> all(p.exists() for p in out_paths)
True

See also

pycsamt.site.base.Site

Site wrapper used for each element in the container.

pycsamt.seg.collection.EDICollection

Parsed collection produced by the core parser.

pycsamt.site.profile.Profile

Optional profile object produced by to_profile().

References

property edic: list[EDIFile]#
by_index(i)#

Retrieve a site by zero-based index.

Parameters:

i (int) – Position in the container.

Returns:

The site at the requested index.

Return type:

Site

Examples

>>> sites.by_index(0).name == sites[0].name
True
get(name)#

Safe lookup by case-insensitive station name.

Parameters:

name (str) – Station identifier to find.

Returns:

Matching site, or None if not present.

Return type:

Site or None

Examples

>>> sites.get("missing") is None
True
>>> sites.get("E01").name
'E01'

See also

__getitem__

Raises on missing names.

as_list()#

Return the underlying list of EDI objects.

Returns:

The EDI objects corresponding to each site.

Return type:

list of pycsamt.seg.edi.EDIFile

Notes

This is useful when passing the dataset to utilities that operate on EDI-level structures rather than on sites.

Examples

>>> edis = sites.as_list()
>>> hasattr(edis[0], "get_section")
True
to_edis(*, copy=False, progress=False, verbose=0)#

Return the underlying EDI objects as a list.

Parameters:
  • copy (bool, default False) – If True, return best-effort deep copies of the EDI objects.

  • progress (bool or {'auto'}, default False) – Enable progress display while unwrapping.

  • verbose (int, default 0) – Verbosity forwarded to progress/reporting helpers.

Returns:

EDI objects in site order.

Return type:

list of pycsamt.seg.edi.EDIFile

See also

to_edicollection

Return an EDICollection instead.

pycsamt.site.base.to_edis

General unwrapping helper.

to_edicollection(*, copy=False, progress=False, verbose=0)#

Return the underlying EDI objects as an EDICollection.

Parameters:
  • copy (bool, default False) – If True, return best-effort deep copies of the EDI objects.

  • progress (bool or {'auto'}, default False) – Enable progress display while unwrapping.

  • verbose (int, default 0) – Verbosity assigned to the returned collection.

Returns:

Collection built from the underlying EDI objects.

Return type:

pycsamt.seg.collection.EDICollection

closest(lat, lon, tol=None)#

Find the closest site to a target coordinate using geodetic distance.

Parameters:
  • lat (float) – Target latitude in decimal degrees.

  • lon (float) – Target longitude in decimal degrees.

  • tol (float, optional) – Maximum allowed distance in meters. If provided and the nearest site is farther than tol, return None. The default is None.

Returns:

Nearest site or None if all sites are too far or lack valid coordinates.

Return type:

Site or None

Notes

Coordinates are validated. Sites with non-finite values are skipped. Distance is computed in meters using a geodetic model.

Examples

>>> near = sites.closest(10.0, 20.0)
>>> near is None or hasattr(near, "name")
True
>>> sites.closest(0.0, 0.0, tol=1.0) is None
True
map(fn)#

Apply a function to every site and collect the results.

Parameters:

fn (callable) – Function of signature fn(site) -> Any.

Returns:

Results collected in order.

Return type:

list

Examples

>>> sites.map(lambda s: s.name)[:2]
['E01', 'E02']
edit_all(*, rename=None, freq_slice=None, mask=None, inplace=False)#

Bulk-edit all sites with optional rename, frequency slicing, and tensor masking.

Parameters:
  • rename (callable, optional) – Function rename(old_name) -> new_name. If provided, each site is renamed accordingly.

  • freq_slice (slice, optional) – Row-wise slice applied consistently to frequency, impedance Z, errors, and derived arrays.

  • mask (callable, optional) – Function mask(df) -> bool_series where df is the output of site.to_dataframe("z"). Rows where the mask is False are set to NaN in Z.

  • inplace (bool, optional) – If True, modify this container and return it. If False, return a new Sites. Default is False.

Returns:

The edited container (possibly the same instance).

Return type:

Sites

Notes

  • When inplace=False, sites are shallow-cloned so that edits do not affect the original container.

  • Frequency slicing is applied atomically to avoid temporary shape mismatches between freq and Z-derived arrays.

  • Missing arrays are tolerated on a best-effort basis.

Examples

Rename with a prefix
>>> def rnm(n): return "X_" + n
>>> out = sites.edit_all(rename=rnm)
>>> [s.name for s in out][:2]
['X_E01', 'X_E02']
Slice away the first frequency
>>> sl = slice(1, None)
>>> out2 = sites.edit_all(freq_slice=sl)
>>> len(out2["E01"].freq) == len(sites["E01"].freq) - 1
True
Mask the top half of rows in Z
>>> def top_half(frame):
...     m = np.ones(len(frame), dtype=bool)
...     m[: len(frame) // 2] = False
...     return m
>>> out3 = sites.edit_all(mask=top_half)
>>> df = out3["E01"].to_dataframe("z")
>>> np.isnan(df.iloc[0].values).all()
True

See also

Site.rename

Per-site rename helper.

Site.to_dataframe

Source for building masks.

with_topography(frame, *, inplace=False)#

Align site coordinates and elevation from a tabular frame.

Parameters:
  • frame (Any) – A table-like object (e.g., pandas.DataFrame) with station identifiers and columns for latitude, longitude, and elevation. Column names are resolved by the topography utility.

  • inplace (bool, optional) – If True, modify this container and return it. If False, return a new Sites. Default is False.

Returns:

Container with updated coordinates.

Return type:

Sites

Notes

Sites are matched by normalized station identifiers. The operation is performed on a best-effort basis; unmatched stations are left unchanged.

Examples

>>> import pandas as pd
>>> df = pd.DataFrame({
...   "station": ["E01", "E02"],
...   "latitude": [10.0, 11.0],
...   "longitude": [20.0, 21.0],
...   "elevation": [100.0, 200.0],
... })
>>> out = sites.with_topography(df, inplace=False)
>>> tuple(round(v, 3) for v in out["E01"].coords)
(10.0, 20.0, 100.0)
select(names=None, predicate=None)#

Filter sites by explicit names or by a boolean predicate.

Parameters:
  • names (sequence of str, optional) – Case-insensitive station names to retain. If provided, this takes precedence over predicate.

  • predicate (callable, optional) – Function predicate(site) -> bool. Sites for which the function returns True are retained.

Returns:

New container with the selected sites.

Return type:

Sites

Notes

If neither names nor predicate is provided, the method returns a shallow copy of the current container.

Examples

>>> subset = sites.select(names=["E02"])
>>> [s.name for s in subset]
['E02']
>>> subset2 = sites.select(predicate=lambda s: s.has_component("Zxy"))
>>> isinstance(subset2, Sites)
True
classmethod from_any(source, topo_src=None)#

Construct a container from heterogeneous inputs by using a normalized loading session.

Parameters:
  • source (Any) – Input that can be understood by the loader. Supported cases include: - an EDICollection, - a list of EDIFile, - a single EDIFile, - or an iterable that yields EDIs.

  • topo_src (Any, optional) – Optional topography source passed to the session for use during loading. The default is None.

Returns:

Parsed container. Returns an empty container if the input cannot be interpreted.

Return type:

Sites

Notes

The method uses normalize_session() to handle parsing, discovery, and optional topography alignment in a consistent way.

Examples

>>> from pycsamt.seg.collection import EDICollection
>>> # edicol = ...  # suppose we already parsed a folder
>>> # sites = Sites.from_any(edicol)
>>> # isinstance(sites, Sites)
True
write(outdir, *, template='{station}.edi', exist_ok=False)#

Write one EDI file per site to a directory.

Parameters:
  • outdir (str or pathlib.Path) – Destination directory. It is created if missing.

  • template (str, optional) – Filename template. The token {station} is replaced by the normalized station name. Default is "{station}.edi".

  • exist_ok (bool, optional) – If False and a file already exists, raise FileExistsError. If True, overwrite. Default is False.

Returns:

Paths to the written files.

Return type:

list of pathlib.Path

Raises:

FileExistsError – If a target file exists and exist_ok is False.

Notes

If an EDI object implements to_file, it is used to serialize. Otherwise a small placeholder file is written.

Examples

>>> import tempfile, pathlib
>>> tmp = pathlib.Path(tempfile.mkdtemp())
>>> paths = sites.write(tmp, exist_ok=True)
>>> all(p.exists() for p in paths)
True
to_profile(origin, azimuth, *, crs=None)#

Convert sites to a 1D profile aligned with a specified azimuth, returning either a rich Profile object or a lightweight fallback.

Parameters:
  • origin (tuple of float) – (lat, lon) in decimal degrees defining the profile origin.

  • azimuth (float) – Profile azimuth in degrees. North is 0 and angles increase clockwise.

  • crs (int, optional) – Optional CRS code for libraries that require it. Not used in the lightweight fallback. Default is None.

Returns:

If Profile is available, a Profile is returned. Otherwise a dict is returned with keys "origin", "azimuth", and "sites" in chainage order.

Return type:

Profile or dict

Notes

The fallback computes local chainage by a flat approximation around origin:

\[\begin{split}ch = dx * \\sin(az) + dy * \\cos(az)\end{split}\]

where dx and dy are metric offsets relative to the origin. Sites lacking valid coordinates are skipped.

Examples

>>> prof = sites.to_profile(origin=(0.0, 0.0), azimuth=90.0)
>>> hasattr(prof, "chainages") or isinstance(prof, dict)
True

See also

pycsamt.site.profile.Profile

Rich profile object when available.

pycsamt.site.to_edis(x, *, as_collection=False, copy=False, recursive=True, on_dup='replace', strict=False, verbose=0, progress=False)#

Unwrap site-like inputs to raw EDI objects.

This is the inverse boundary of to_sites(). It accepts a single Site, a Sites collection, an EDICollection, raw EDI objects, path-like inputs, or mixed iterables containing those forms. The returned objects are the underlying EDI containers used by low-level writers, exporters, and EM processing functions.

Parameters:
  • x (Any) – Site-like input to unwrap. Supported values include Site, Sites, EDIFile, EDICollection, path-like inputs, or iterables containing site/EDI-like objects.

  • as_collection (bool, default False) – If True, return an EDICollection. Otherwise a single input returns one EDI object and multi-item inputs return a list.

  • copy (bool, default False) – If True, return best-effort deep copies of the EDI objects. If copying fails for an item, that item is returned unchanged.

  • recursive (bool, default True) – Forwarded to path-like discovery through EDICollection.

  • on_dup ({'replace', 'keep', 'keep_first', 'keep_last', 'raise'}, default 'replace') – Duplicate station policy. replace and keep are forwarded to path loading. keep_first, keep_last, and raise are enforced after collection construction.

  • strict (bool, default False) – If True, raise when an object cannot be unwrapped to EDI. If False, invalid items are skipped.

  • verbose (int, default 0) – Verbosity forwarded to collection construction and duplicate policy diagnostics.

  • progress (bool or {'auto'}, default False) – Enable progress display while unwrapping iterable inputs.

Returns:

Raw EDI object(s), depending on the input shape and as_collection.

Return type:

EDIFile, list of EDIFile, or EDICollection

Notes

The operation is shallow by default. It returns the same EDI objects wrapped by Site or Sites. Pass copy=True when the caller should be able to mutate the returned objects independently.

Examples

>>> from pycsamt.site.base import Site, Sites, to_edis
>>> site = Site(edi)
>>> raw = to_edis(site)
>>> raw is edi
True
>>> raws = to_edis(Sites([edi]))
>>> len(raws)
1
>>> coll = to_edis(Sites([edi]), as_collection=True)
>>> len(coll)
1

See also

to_sites

Wrap raw EDI-like inputs into Sites.

Site.to_edi

Convenience method for one Site.

Sites.to_edis

Convenience method for a Sites collection.

pycsamt.site.rotate(site, angle_deg, *, inplace=False)#

Rotate impedance tensor Z (and tipper T, if present) by an azimuthal angle in degrees.

The rotation is applied in the horizontal plane using the similarity transform \(Z' = R Z R^{-1}\), where \(R\) is the 2x2 rotation matrix built from angle_deg. When a tipper is available (either on the EDI object as T / TIP / Tip or attached to Z), the 2-component vector is rotated consistently.

Parameters:
  • site (Any) – An EDI-like object (e.g., pycsamt.seg.edi.EDIFile) or wrapper exposing a Z section compatible with a complex 2x2 impedance array and, optionally, a tipper.

  • angle_deg (float) – Rotation angle in degrees. Positive values rotate the measurement axes according to the internal convention of this package. If your acquisition system defines the sign oppositely, use the negative of your desired angle.

  • inplace (bool, optional) – If True, mutate site in place. Otherwise, work on a shallow copy and return that copy. Default is False.

Returns:

The rotated object. If inplace is True, this is the same object as site; otherwise a new object.

Return type:

Any

Notes

  • Error arrays (z_error or aliases) are rotated with a magnitude-only scheme (using absolute values of the rotation matrices) as a pragmatic best-effort. This is a common, but approximate, practice.

  • Only arrays with shapes consistent with MT tensors ((n, 2, 2) for Z, (n, 2) for T) are rotated. Other shapes are ignored silently.

  • The function is no-throw by design. If a section is not present or incompatible, it is skipped.

Examples

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.edit import rotate
>>> ed = EDIFile("path/to/station.edi")
>>> ed_rot = rotate(ed, 30.0)       # copy by default
>>> ed_rot2 = rotate(ed, -45.0, inplace=True)

See also

select_freq

Subset rows by frequency range or indices.

rename

Rename the station using a policy or explicit name.

pycsamt.site.edit.rotate_all

Broadcast rotation over a collection.

References

pycsamt.site.select_freq(site, *, fmin=None, fmax=None, keep=None, inplace=False)#

Subset the dataset along frequency by range or explicit indices, keeping all affected arrays aligned.

This applies the selection to every frequency-indexed array found on the object, including Z, Z errors, derived resistivity/phase, and any tipper arrays resident on either the root EDI object or under its Z section.

Parameters:
  • site (Any) – An EDI-like object (e.g., pycsamt.seg.edi.EDIFile) with a discoverable frequency vector.

  • fmin (float, optional) – Keep rows with freq >= fmin. Ignored if None.

  • fmax (float, optional) – Keep rows with freq <= fmax. Ignored if None.

  • keep (Iterable[int] or numpy.ndarray, optional) – Explicit indices or a boolean mask to keep. If provided, fmin and fmax are ignored. Use integer indices for exact row picks, or a boolean mask of the same length as the frequency vector.

  • inplace (bool, optional) – If True, mutate site in place. Otherwise, operate on a shallow copy. Default is False.

Returns:

The object after selection. If inplace is True, this is the same object as site; otherwise a new object.

Return type:

Any

Notes

  • All known aliases are sliced consistently (e.g., frequency, Z, Z error, resistivity, phase, tipper, and related per-row arrays) to preserve alignment.

  • If the frequency vector is empty or missing, the call is a no-op.

  • The function is no-throw by design; incompatible shapes are skipped silently.

Examples

Keep only rows with frequency >= 10 Hz:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.edit import select_freq
>>> ed = EDIFile("path/to/station.edi")
>>> ed_hi = select_freq(ed, fmin=10.0)

Keep the first and last rows explicitly:

>>> sel = [0, -1]
>>> ed_edge = select_freq(ed, keep=sel)

Apply in place:

>>> _ = select_freq(ed, fmin=1.0, fmax=100.0, inplace=True)

See also

rotate

Rotate Z and tipper by an azimuth angle.

rename

Rename the station identifiers.

pycsamt.site.edit.select_freq_all

Broadcast selection over a collection.

References

pycsamt.site.rename(site, name=None, policy=None, *, inplace=False)#

Rename a station using an explicit name or a policy function.

This updates common station identifiers across the EDI header and attempts to keep them in sync so that downstream code resolves the new name consistently.

Parameters:
  • site (Any) – An EDI-like object (e.g., pycsamt.seg.edi.EDIFile) or wrapper with a modifiable HEAD section.

  • name (str, optional) – Explicit new station name to set. If provided, this takes precedence over policy.

  • policy (Callable[[str], str], optional) – A function mapping the current station name to a new one. Ignored when name is provided.

  • inplace (bool, optional) – If True, mutate site in place. Otherwise, operate on a shallow copy. Default is False.

Returns:

The object with updated identifiers. If inplace is True, this is the same object as site; otherwise a new object.

Return type:

Any

Notes

  • The function writes multiple header fields when present (e.g., dataid, station, and other common aliases) and also mirrors the name to edi.name. This increases the chance that name resolution remains stable across different readers.

  • The rename operation does not touch on-disk filenames. File paths remain unchanged unless you later write out using a template that depends on the station name.

  • If both name and policy are given, name wins.

Examples

Policy-based rename:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.edit import rename
>>> ed = EDIFile("path/to/station.edi")
>>> ed2 = rename(ed, policy=lambda n: f"X_{n}")

Explicit name, in place:

>>> _ = rename(ed, name="ST123A", inplace=True)

See also

rotate

Rotate Z and tipper by an azimuth angle.

select_freq

Subset rows by frequency range or indices.

pycsamt.site.edit.rename_all

Broadcast rename over a collection.

pycsamt.site.base.Site

Wrapper that resolves a stable site name for indexing.

References

pycsamt.site.set_coords(site, *, lat=None, lon=None, elev=None, inplace=False)#

Set geographic coordinates on the EDI header.

Only the values explicitly provided are updated. The call delegates to the same coordinate writer used by the Site API, so downstream tools see consistent lat, lon, and elev fields.

Parameters:
  • site (Any) – An EDI-like object (e.g., pycsamt.seg.edi.EDIFile) or wrapper exposing a mutable HEAD section.

  • lat (float, optional) – Latitude in degrees. If None, the field is left unchanged.

  • lon (float, optional) – Longitude in degrees. If None, the field is left unchanged.

  • elev (float, optional) – Elevation in meters. If None, the field is left unchanged.

  • inplace (bool, optional) – If True, mutate site in place. Otherwise, work on a shallow copy and return that copy. Default is False.

Returns:

The updated object. If inplace is True, this is the same object as site; otherwise a new object.

Return type:

Any

Notes

  • The function validates numeric types and writes to common HEAD attribute names (lat, lon, elev).

  • If a field is not present in the header, a best-effort attribute is created.

Examples

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.edit import set_coords
>>> ed = EDIFile("path/to/station.edi")
>>> ed2 = set_coords(ed, lat=35.1, lon=12.8, elev=1234.0)
>>> _ = set_coords(ed, lat=36.0, inplace=True)

See also

pycsamt.site.base.Site.set_coords

Object-oriented wrapper.

pycsamt.site.edit.set_coords_all

Broadcast over a collection.

References

pycsamt.site.fill_missing(site, *, how='zero', components=('Z', 'Tip'), inplace=False)#

Replace missing or non-finite values in Z and/or tipper arrays with zeros or NaNs.

The operation preserves shapes and alignment across arrays. If an array is absent, a new one is allocated with the correct shape inferred from the frequency vector.

Parameters:
  • site (Any) – An EDI-like object with a discoverable frequency vector and optionally Z and tipper sections.

  • how ({"zero", "nan"}, optional) – Replacement policy. Use "zero" to fill with numeric zeros. Use "nan" to fill with NaN for all non-finite entries. Default is "zero".

  • components (Iterable[str], optional) – Which components to process. Accepts items like "Z" or "Tip" (case-insensitive). Default is ("Z", "Tip").

  • inplace (bool, optional) – If True, mutate in place. Otherwise, operate on a shallow copy and return that copy. Default is False.

Returns:

The object after filling. If inplace is True, this is the same object as site; otherwise a new object.

Return type:

Any

Notes

  • Z arrays are expected as shape (n, 2, 2) and tipper as (n, 2). Only arrays with the expected shapes are modified or allocated.

  • When Z exists, the function also fills common aliases for errors and derived quantities, such as z_error, rho (resistivity), and phase arrays, if present.

  • The frequency vector length n defines the number of rows used for any new arrays.

  • This function is no-throw by design. Incompatible or absent pieces are skipped silently.

Examples

Fill Z and tipper with zeros where values are non-finite:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.edit import fill_missing
>>> ed = EDIFile("path/to/station.edi")
>>> ed2 = fill_missing(ed, how="zero")

Fill only Z with NaNs, in place:

>>> _ = fill_missing(ed, how="nan", components=("Z",),
...                  inplace=True)

See also

select_freq

Subset rows by frequency while keeping arrays aligned.

rotate

Rotate Z and tipper by an azimuth angle.

References

pycsamt.site.recompute_res_phase(site, *, inplace=False)#

Recompute apparent resistivity and phase from the impedance tensor Z for a single site.

The function looks for a Z section and, if present, calls its compute_resistivity_phase() method. The operation is best-effort and suppresses exceptions.

Parameters:
  • site (Any) – An EDI-like object (EDIFile) or a wrapper exposing a Z section with the expected API.

  • inplace (bool, optional) – If True, mutate the given object in place. Otherwise work on a shallow copy and return it. Default is False.

Returns:

The mutated object (in place) or a new object (copy).

Return type:

Any

Notes

  • Derived quantities are typically written under common aliases (e.g., rho or resistivity for apparent resistivity in ohm-m, and phase in degrees).

  • The method assumes Z has shape (n, 2, 2) and that a frequency vector is present. If these are missing or incompatible, nothing is changed.

Examples

Single-site:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.edit import recompute_res_phase
>>> ed = EDIFile("path/to/station.edi")
>>> ed2 = recompute_res_phase(ed)

In place:

>>> _ = recompute_res_phase(ed, inplace=True)

See also

select_freq

Keep a subset of rows by frequency.

fill_missing

Ensure arrays are allocated and finite before recomputation.

pycsamt.site.base.Site.to_dataframe

Inspect derived arrays.

References

pycsamt.site.rotate_all(sites, angle_deg, *, inplace=False)#

Rotate every site in a collection by an azimuthal angle in degrees.

This is the broadcast variant of rotate(). It accepts either a Sites wrapper or any iterable of EDI-like objects and returns a new Sites unless inplace is requested.

Parameters:
  • sites (Any) – A pycsamt.site.base.Sites instance or any iterable of EDI-like objects.

  • angle_deg (float) – Rotation angle in degrees, passed through to rotate().

  • inplace (bool, optional) – If True, attempt to apply the rotation in place on the given container. Otherwise, return a new Sites. Default is False.

Returns:

A sites collection holding the rotated items, or the original container when mutated in place.

Return type:

pycsamt.site.base.Sites

Notes

  • The function preserves the input order of items.

  • If some items lack compatible arrays, they are skipped silently.

Examples

Using a list of EDI files:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.edit import rotate_all
>>> eds = [EDIFile("A.edi"), EDIFile("B.edi")]
>>> ro = rotate_all(eds, 15.0)

Using a Sites wrapper:

>>> from pycsamt.site.base import Sites
>>> sites = Sites(eds)
>>> ro2 = rotate_all(sites, -30.0)

See also

rotate

Single-site rotation.

select_freq_all

Broadcast selection by frequency.

pycsamt.site.select_freq_all(sites, *, fmin=None, fmax=None, keep=None, inplace=False)#

Subset all sites in a collection along frequency, keeping arrays aligned.

This is the broadcast variant of select_freq(). It accepts a Sites wrapper or any iterable of EDI-like objects and returns a new Sites unless inplace is requested.

Parameters:
  • sites (Any) – A pycsamt.site.base.Sites instance or any iterable of EDI-like objects.

  • fmin (float, optional) – Keep rows with freq >= fmin. Ignored if None.

  • fmax (float, optional) – Keep rows with freq <= fmax. Ignored if None.

  • keep (Iterable[int] or numpy.ndarray, optional) – Explicit indices or a boolean mask to keep. If provided, fmin and fmax are ignored.

  • inplace (bool, optional) – If True, attempt to modify the given container in place. Otherwise, return a new Sites. Default is False.

Returns:

A sites collection with selection applied, or the original container when mutated in place.

Return type:

pycsamt.site.base.Sites

Notes

  • All known frequency-indexed arrays are sliced in sync for each site (Z, errors, derived quantities, and tipper).

  • Items missing a frequency vector are left unchanged.

Examples

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.edit import select_freq_all
>>> eds = [EDIFile("A.edi"), EDIFile("B.edi")]
>>> sites = Sites(eds)
>>> out = select_freq_all(sites, fmin=1.0, fmax=100.0)

Use explicit row indices for all sites:

>>> out2 = select_freq_all(eds, keep=[0, -1])

See also

select_freq

Single-site selection by frequency.

rotate_all

Broadcast rotation over a collection.

References

pycsamt.site.rename_all(sites, *, policy=None, name_fn=None, inplace=False)#

Batch rename a collection of sites.

This is the broadcast variant of rename(). It accepts a Sites wrapper or any iterable of EDI-like objects and produces a new Sites (unless inplace is True).

Parameters:
  • sites (Any) – A pycsamt.site.base.Sites instance or any iterable of EDI-like objects.

  • policy (Callable[[str], str], optional) – Function mapping the current station name to a new one, e.g. lambda n: f"X_{n}". Ignored if name_fn is provided.

  • name_fn (Callable[[Any], str], optional) – Function mapping each EDI object to a new name, e.g. from the file stem. This takes precedence over policy and is useful to guarantee uniqueness across sites.

  • inplace (bool, optional) – If True, attempt to modify the given container in place. Otherwise, return a new Sites. Default is False.

Returns:

A collection holding the renamed items, or the original container when mutated in place.

Return type:

pycsamt.site.base.Sites

Notes

  • The rename updates common header identifiers and mirrors the name to edi.name for robust downstream resolution.

  • If multiple sites share the same original name, a simple policy like lambda n: "X_" + n may produce duplicate outputs. Prefer name_fn that uses a unique attribute (e.g., the file stem) to avoid collisions.

  • The function preserves input order and is no-throw; items that cannot be renamed are skipped.

Examples

Policy-based rename for all items:

>>> from pycsamt.site.edit import rename_all
>>> out = rename_all(eds, policy=lambda n: f"X_{n}")

Unique names from file stems:

>>> from pathlib import Path
>>> out = rename_all(
...     eds,
...     name_fn=lambda e: f"X_{Path(getattr(e,'path','')).stem}",
... )

See also

rename

Single-site rename helper.

set_coords_all

Batch coordinate assignment.

pycsamt.site.base.Sites

Collection wrapper used here.

References

pycsamt.site.set_coords_all(sites, src, *, inplace=False)#

Batch set coordinates for a collection of sites.

Coordinates can be provided by a callable, a mapping keyed by station name, or an object exposing a .frame attribute with tabular data.

Parameters:
  • sites (Any) – A pycsamt.site.base.Sites instance or any iterable of EDI-like objects.

  • src (Any) –

    One of:
    • callable(edi) -> (lat, lon, elev)

    • mapping[name] -> (lat, lon, elev)

    • object with .frame DataFrame-like with columns: station plus either lat/lon or latitude/longitude, and optionally elev/elevation.

  • inplace (bool, optional) – If True, attempt to modify the given container in place. Otherwise, return a new Sites. Default is False.

Returns:

A collection with updated coordinates, or the original container when mutated in place.

Return type:

pycsamt.site.base.Sites

Notes

  • The lookup order is: callable then mapping by station name, then the optional .frame table. The first source that returns a non-None triple is used.

  • Latitude and longitude are expected in degrees; elevation in meters.

  • The function preserves input order and is no-throw; items without a matching entry are left unchanged.

Examples

From a mapping keyed by names:

>>> from pycsamt.site.edit import set_coords_all
>>> coords = {"S01": (35.1, 12.8, 1234.0)}
>>> out = set_coords_all(eds, coords)

From a callable using file stems:

>>> from pathlib import Path
>>> def pick(edi):
...     stem = Path(getattr(edi, "path", "")).stem
...     return (10.0, 20.0, 100.0) if stem == "S01" else \
...            (11.0, 21.0, 110.0)
>>> out = set_coords_all(eds, pick)

From a pandas DataFrame holder:

>>> class Holder:
...     def __init__(self, frame):
...         self.frame = frame
>>> out = set_coords_all(eds, Holder(df))

See also

set_coords

Single-site coordinate update.

rename_all

Batch rename helper.

pycsamt.site.base.Site.set_coords

OOP variant per site.

References

pycsamt.site.set_coords_from_table(sites, table, *, columns=None, crs_from=None, to_crs='EPSG:4326', inplace=False)#

Set site coordinates for many EDI files from a table.

This high-level helper accepts a wide range of table-like objects (CSV path, pandas DataFrame, numpy structured array, or list of dicts / tuples). It normalizes column names, optionally projects easting/northing to lon/lat, builds a mapping {station: (lat, lon, elev)}, and delegates to set_coords_all().

Parameters:
  • sites (Any) – A Sites instance, an iterable of EDIFile objects, or anything accepted by set_coords_all().

  • table (Any) –

    One of:
    • Path to a CSV or whitespace-delimited text file.

    • A pandas.DataFrame.

    • A numpy structured array.

    • A list of dicts or a list of tuples.

  • columns (dict, optional) – Explicit column mapping. Keys are canonical names and values are the actual column names present in table. Supported canonical keys are: 'station', 'lat', 'lon', 'elev', 'easting', 'northing'. Matching is case-insensitive.

  • crs_from (str, optional) – Source CRS used when the table provides easting and northing instead of lat and lon. Required in that case. Example: 'EPSG:32631' for UTM 31N.

  • to_crs (str, default "EPSG:4326") – Target CRS for output coordinates. The default is WGS84 lon/lat.

  • inplace (bool, default False) – If True, mutate the given collection and return it. Otherwise return a new Sites with updated EDI objects.

Returns:

The same semantics as set_coords_all(): either the mutated input (inplace=True) or a new Sites instance.

Return type:

Any

Raises:
  • ValueError – If the station column cannot be resolved, or if neither (lat, lon) nor (easting, northing) can be resolved, or if crs_from is required but missing.

  • ImportError – If projection is needed (easting/northing present) but pyproj is not installed.

Notes

Column detection is case-insensitive and understands common aliases:

  • station: station, name, site, id.

  • lat: lat, latitude.

  • lon: lon, long, longitude.

  • elev: elev, elevation, z.

  • easting: easting, x.

  • northing: northing, y.

When both geographic and projected fields are present, the geographic pair (lat, lon) is preferred. If only projected fields are present, a valid crs_from must be provided and pyproj will be used for projection.

Examples

Load from a CSV path with standard columns:

>>> from pycsamt.site.edit import set_coords_from_table
>>> from pycsamt.site.base import Sites
>>> edis = Sites([...])  # your EDI files
>>> out = set_coords_from_table(
...     edis, "coords.csv", inplace=False
... )
>>> isinstance(out, Sites)
True

Pass a DataFrame with aliases and an explicit mapping:

>>> import pandas as pd
>>> df = pd.DataFrame({
...     "name": ["S01", "S02"],
...     "latitude": [35.1, 35.2],
...     "long": [12.8, 12.9],
...     "elevation": [120.0, 130.0],
... })
>>> out = set_coords_from_table(
...     edis,
...     df,
...     columns={"station": "name",
...              "lat": "latitude",
...              "lon": "long",
...              "elev": "elevation"},
...     inplace=False,
... )

Use easting/northing with a source CRS:

>>> df = pd.DataFrame({
...     "station": ["S10"],
...     "easting": [400000.0],
...     "northing": [5750000.0],
... })
>>> out = set_coords_from_table(
...     edis,
...     df,
...     crs_from="EPSG:32631",  # UTM 31N
...     inplace=False,
... )

See also

set_coords_all

Broadcast setting of coordinates using a mapping.

set_coords

Single-site coordinate update helper.

References

pycsamt.site.set_coords_from_en(site, easting, northing, *, crs_from, elev=None, to_crs='EPSG:4326', inplace=False)#

Project (easting, northing) to lon/lat and set a site’s coords.

This convenience wraps projection and assignment for a single EDI site. It projects the provided easting/northing from crs_from to to_crs (default WGS84 lon/lat), then calls set_coords().

Parameters:
  • site (Any) – A single EDI-like object compatible with set_coords().

  • easting (float) – Easting value in meters for the source CRS.

  • northing (float) – Northing value in meters for the source CRS.

  • crs_from (str) – The EPSG or PROJ string that identifies the source CRS, for example 'EPSG:32631' for UTM 31N.

  • elev (float, optional) – Elevation in meters to store in the EDI header. If not provided, the previous elevation is kept (if any).

  • to_crs (str, default "EPSG:4326") – Target CRS for output coordinates. The default is WGS84 lon/lat.

  • inplace (bool, default False) – If True, mutate the given object and return it. Otherwise work on a copy and return the copy.

Returns:

The mutated site (inplace=True) or a new site object with updated coordinates.

Return type:

Any

Raises:

Notes

Projection uses pyproj with always_xy=True so that the axis order is interpreted as (lon, lat). Units are assumed to be meters for the easting and northing values.

The returned object follows the same semantics as set_coords(). If you need to update many sites, prefer set_coords_from_table() or set_coords_all().

Examples

Update a single site from UTM 31N (EPSG:32631) coordinates:

>>> from pycsamt.site.edit import set_coords_from_en
>>> site = ...  # an EDIFile
>>> site2 = set_coords_from_en(
...     site,
...     easting=400000.0,
...     northing=5750000.0,
...     crs_from="EPSG:32631",
...     elev=250.0,
...     inplace=False,
... )

Do the update in place and keep the existing elevation:

>>> _ = set_coords_from_en(
...     site,
...     easting=500000.0,
...     northing=4600000.0,
...     crs_from="EPSG:32630",
...     inplace=True,
... )

See also

set_coords

Assign lat, lon, elev on a single site.

set_coords_from_table

Batch update from tabular input.

set_coords_all

Broadcast update using a mapping.

References

pycsamt.site.by_names(sites, patterns, *, case=False)#

Select sites by matching station names against one or more patterns.

This is a flexible name-based selector that accepts several pattern types:

  • string with optional glob-like wildcards * and ?

  • compiled regular expression (re.Pattern)

  • callable fn(name)->bool

  • iterable of any mix of the above

A site is kept if any pattern matches its station name. Matching is stable: the relative order of kept sites is the same as in the input.

Parameters:
  • sites (Any) – A Sites instance, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like objects.

  • patterns (Iterable[Any] or Any) – One pattern or an iterable of patterns. See the list above for supported pattern types.

  • case (bool, optional) – If True, perform case-sensitive matching. If False (default) names and string patterns are upper-cased before comparison.

Returns:

A new Sites wrapper containing only the matched EDI items. The original container is not modified.

Return type:

pycsamt.site.base.Sites

Notes

String patterns support a minimal glob syntax. * matches any sequence (possibly empty) and ? matches any single character. If you need full regular expressions, pass a compiled re.Pattern.

When multiple patterns are given, the match is an OR over all patterns. Matching uses the station name as returned by station_name(ed) which reflects header normalization.

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import by_names
>>> sites = Sites([e1, e2, e3])  # EDIFile objects
>>> out = by_names(sites, "K*")  # glob: all names starting K
>>> [s.name for s in out]
['K01', 'K02']
>>> import re
>>> rx = re.compile(r"^S0[1-3]$")
>>> out = by_names(sites, rx)
>>> [s.name for s in out]
['S01', 'S02', 'S03']
>>> out = by_names(sites, lambda n: n.endswith("A"))
>>> [s.name for s in out]
['X1A', 'X2A']

See also

pycsamt.site.selection.by_index

Select by numeric positions.

pycsamt.site.selection.by_predicate

Keep sites for which a boolean predicate returns True.

pycsamt.site.selection.by_freq

Keep sites that contain data within a frequency window.

References

pycsamt.site.by_index(sites, indices)#

Select sites by zero-based numeric indices, supporting negative indices.

Indices are normalized exactly like Python sequence indexing: -1 addresses the last item, -2 the one before last, and so on. Out-of-range or non-integer indices are ignored. The resulting subset preserves the original ordering of the selected items, not the order in which indices are provided.

Parameters:
  • sites (Any) – A Sites instance, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like objects.

  • indices (Iterable[int] or int) – One index or an iterable of indices. Negative indices are supported and mapped to their Python equivalents.

Returns:

A new Sites wrapper containing only items at the requested positions. If no valid indices remain after normalization, an empty Sites is returned.

Return type:

pycsamt.site.base.Sites

Notes

Duplicate indices are de-duplicated in the output since the selection is implemented as a membership test over the set of normalized indices. The relative order of kept items is the same as in the original sequence.

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import by_index
>>> sites = Sites([e1, e2, e3])  # names: A, B, C
>>> out = by_index(sites, [0, -1])   # first and last
>>> [s.name for s in out]
['A', 'C']
>>> out = by_index(sites, 1)  # single integer
>>> [s.name for s in out]
['B']
>>> out = by_index(sites, [10, -10])  # both invalid -> empty
>>> len(out)
0

See also

pycsamt.site.selection.by_names

Name-based matching using strings, regex, or callables.

pycsamt.site.selection.by_chainage

Select by stored chainage range when available.

pycsamt.site.base.Sites.by_index

Random access to a single site by position.

pycsamt.site.by_chainage(sites, smin, smax)#

Select sites whose stored chainage falls within a closed interval.

This helper reads the chainage value first from the EDI HEAD section (head.chainage) and, if missing, from a top-level attribute edi.chainage. Sites for which a numeric chainage cannot be determined are silently skipped.

Parameters:
  • sites (Any) – A Sites instance, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like items.

  • smin (float) – Minimum chainage (inclusive).

  • smax (float) – Maximum chainage (inclusive).

Returns:

A new Sites wrapper containing only the EDI items whose chainage \(c\) satisfies \(smin \\le c \\le smax\).

Return type:

pycsamt.site.base.Sites

Notes

Chainage is a linear reference commonly used along profiles or lines, typically measured in meters from a chosen origin. If chainage is not present on a site, that site is excluded. The original order of sites is preserved among the kept items.

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import by_chainage
>>> s = Sites([e1, e2, e3])  # EDIFile objects
>>> out = by_chainage(s, smin=100.0, smax=300.0)
>>> [t.name for t in out]
['L02', 'L03']

See also

pycsamt.site.selection.by_index

Select by zero-based positions with negative support.

pycsamt.site.selection.by_names

Select by station names using glob, regex, or callables.

pycsamt.site.selection.by_freq

Keep sites that contain data in a frequency window.

pycsamt.site.base.Sites.to_profile

Build a profile or ordered view along a line.

References

pycsamt.site.by_freq(sites, fmin, fmax)#

Select sites that contain at least one data row with frequency inside a closed interval.

A site is kept if its frequency array f contains any finite value satisfying \(fmin \le f \le fmax\). Sites with empty or non-finite frequency arrays are skipped.

Parameters:
  • sites (Any) – A Sites instance, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like items.

  • fmin (float) – Minimum frequency (inclusive).

  • fmax (float) – Maximum frequency (inclusive).

Returns:

A new Sites wrapper containing only the EDI items with at least one finite frequency in the requested window.

Return type:

pycsamt.site.base.Sites

Notes

Frequencies are obtained via pycsamt.site.utils.get_freq(ed). The check is membership based (any row in range), not a full slicing or resampling. Use pycsamt.site.edit.select_freq() to actually subset rows by frequency.

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import by_freq
>>> s = Sites([e1, e2, e3])  # EDIFile objects
>>> out = by_freq(s, fmin=0.5, fmax=2.0)
>>> [t.name for t in out]
['A02', 'A03']

See also

pycsamt.site.selection.by_names

Name-based selection using glob, regex, or callables.

pycsamt.site.selection.by_chainage

Select by stored chainage range when available.

pycsamt.site.edit.select_freq

Subset frequency rows within sites.

pycsamt.site.by_bbox(sites, minlat, minlon, maxlat, maxlon)#

Select sites that fall inside an axis-aligned geographic box.

The selection is performed in latitude/longitude degrees and assumes a geographic CRS (WGS84-like). A site is kept if its stored coordinates satisfy

\[minlat \le lat \le maxlat \;\;\text{and}\;\; minlon \le lon \le maxlon .\]
Parameters:
  • sites (Any) – A Sites instance, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like items.

  • minlat (float) – Latitude and longitude bounds in degrees. Bounds are inclusive.

  • minlon (float) – Latitude and longitude bounds in degrees. Bounds are inclusive.

  • maxlat (float) – Latitude and longitude bounds in degrees. Bounds are inclusive.

  • maxlon (float) – Latitude and longitude bounds in degrees. Bounds are inclusive.

Returns:

A new Sites wrapper with only the items whose coords are inside the box.

Return type:

pycsamt.site.base.Sites

Notes

This is a simple axis-aligned test in lat/lon and does not handle antimeridian wrapping. If longitudes cross the antimeridian (for example, from 170 to -170 deg), split the selection into two boxes and union the results. Coordinates are retrieved via pycsamt.site.utils.get_coords().

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import by_bbox
>>> s = Sites([e1, e2, e3])  # EDIFile objects
>>> out = by_bbox(s, 24.0, 9.0, 27.0, 11.0)
>>> [site.name for site in out]
['S01', 'S03']

See also

pycsamt.site.selection.by_freq

Keep sites with at least one frequency inside a window.

pycsamt.site.selection.by_chainage

Select by stored chainage range.

pycsamt.site.selection.by_predicate

Arbitrary user-defined filtering.

pycsamt.site.base.Sites.closest

Find the closest site to a target coordinate.

References

pycsamt.site.by_predicate(sites, pred)#

Select sites using a user-supplied predicate function.

The predicate is called for each EDI-like object and should return True to keep the site. Any exception raised by the predicate is caught and treated as a False (site is not kept). This makes bulk filtering robust against occasional data issues.

Parameters:
  • sites (Any) – A Sites instance, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like items.

  • pred (Callable[[Any], bool]) – Function receiving a single EDI-like object and returning a boolean.

Returns:

A new Sites wrapper containing only the sites for which pred(site) returned True.

Return type:

pycsamt.site.base.Sites

Notes

The objects passed to pred are the raw EDI containers, not the Site wrapper. If you prefer the wrapper API, wrap the object inside the predicate:

lambda ed: Site(ed).has_component("Zxy").

Examples

Keep sites that have at least 3 frequency rows:

>>> from pycsamt.site.base import Sites, Site
>>> from pycsamt.site.selection import by_predicate
>>> s = Sites([e1, e2, e3])
>>> out = by_predicate(
...     s, lambda ed: (Site(ed).freq is not None and
...                    len(Site(ed).freq) >= 3)
... )
>>> [t.name for t in out]
['A01', 'A03']

Keep sites whose name matches a rule:

>>> import re
>>> from pycsamt.site.utils import station_name
>>> rule = re.compile(r"^X_")
>>> out = by_predicate(s, lambda ed: bool(rule.search(
...     station_name(ed))))
>>> [t.name for t in out]
['X_E01', 'X_E02']

See also

pycsamt.site.selection.by_names

Name-based selection with glob or regex patterns.

pycsamt.site.selection.drop_empty

Remove sites with no usable data arrays.

pycsamt.site.base.Sites.select

Selection API on the wrapper.

References

pycsamt.site.keep_finite_z(sites)#

Keep sites that contain at least one finite impedance value.

A site is considered to have finite data if either of the following is true:

  1. The impedance tensor array (Z.z or Z._z) contains any finite real or imaginary entry.

  2. If the tensor is not present, a resistivity array (Z._resistivity or Z.rho) exists and has at least one finite value.

Parameters:

sites (Any) – A Sites instance, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like items.

Returns:

A new Sites wrapper with only the sites that contain finite impedance (or resistivity) values.

Return type:

pycsamt.site.base.Sites

Notes

This function is intended as a coarse pre-filter to remove empty placeholders and fully invalid sites before more costly processing. It does not inspect errors or phases, and it does not modify the data. If a site has a Z container but all arrays are missing or fully non-finite, the site is dropped.

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import keep_finite_z
>>> s = Sites([e1, e2, e3])
>>> out = keep_finite_z(s)
>>> [t.name for t in out]
['MT01', 'MT03']

See also

pycsamt.site.selection.drop_empty

Remove sites with empty frequency or missing Z section.

pycsamt.site.edit.fill_missing

Allocate arrays and replace invalid entries.

pycsamt.site.compute.res_at_freq

Compute apparent resistivity at a specific frequency.

pycsamt.site.mask_large_phase_err(sites, thresh)#

Filter out sites whose maximum phase-error exceeds a threshold.

For each site, the function inspects the phase-error array when present (common attribute names are tried, e.g. _phase_err or phase_err). If no phase-error array is found, the site is conservatively kept. Otherwise, the site is kept only when the maximum finite phase-error is less than or equal to thresh.

Parameters:
  • sites (Any) – A Sites object, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like items.

  • thresh (float) – Threshold on phase-error (same units as stored by the processing pipeline, usually degrees).

Returns:

New wrapper containing only sites that pass the phase error test.

Return type:

pycsamt.site.base.Sites

Notes

The check uses a “best effort” attribute lookup and ignores non-finite values during the maximum computation. If the phase-error array is entirely missing, the site is kept. This behavior makes the filter robust when some sites did not store uncertainty products.

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import mask_large_phase_err
>>> s = Sites([e1, e2, e3])
>>> out = mask_large_phase_err(s, thresh=10.0)
>>> [t.name for t in out]
['E01', 'E03']

See also

pycsamt.site.selection.keep_finite_z

Keep sites that contain at least one finite impedance.

pycsamt.site.selection.drop_empty

Remove sites with no usable arrays.

pycsamt.site.edit.fill_missing

Allocate arrays and replace invalid entries.

References

pycsamt.site.drop_empty(sites)#

Drop sites that are effectively empty.

A site is considered empty when either:

  • The frequency vector is missing or has zero length.

  • The impedance container Z is missing.

  • The Z container is present but holds no usable arrays (for example, no z and no resistivity surrogate).

Parameters:

sites (Any) – A Sites object, an EDICollection, a sequence of EDIFile objects, or any iterable yielding EDI-like items.

Returns:

New wrapper that excludes empty sites.

Return type:

pycsamt.site.base.Sites

Notes

This is a coarse, fast filter that checks structural presence and basic array availability. It does not test for NaN-only content; for that, consider pycsamt.site.selection.keep_finite_z().

Examples

>>> from pycsamt.site.base import Sites
>>> from pycsamt.site.selection import drop_empty
>>> s = Sites([e1, e2, e3])
>>> out = drop_empty(s)
>>> [t.name for t in out]
['MT01', 'MT02']

See also

pycsamt.site.selection.keep_finite_z

Keep only sites with finite impedance or resistivity.

pycsamt.site.selection.by_freq

Keep sites that touch a target frequency window.

pycsamt.site.strike_estimate(obj, *, method='swift', api=None)#

Estimate a strike angle from impedance tensors.

This computes a 2D geoelectric strike angle in degrees. The routine supports either a single site or many sites. For a single site a scalar angle is returned. For multiple sites a pandas.DataFrame is returned with one row per site.

Parameters:
  • obj (Any) – A single EDI-like object (e.g. EDIFile) or an iterable of EDI-like objects. Each item must expose a .Z section of shape (n_freq, 2, 2) or be convertible to such.

  • method (str, optional) –

    Strike method. Allowed values are:

    • "swift" (default): grid search over 0..179 degrees that minimizes the diagonal power after rotation.

    • "groom": alias of "swift" in this lightweight mode.

    • "phase_diff": heuristic that returns 0 or 90 degrees based on the relative magnitude of off-diagonals.

  • api (bool | None)

Returns:

If obj is a single site, returns a float angle in degrees within [0, 180). If obj is iterable, returns a DataFrame with columns:

station, method, theta_deg.

Return type:

float or pandas.DataFrame

Notes

The Swift-style criterion rotates the impedance tensor \(Z\) by a test angle \(\\theta\) and minimizes

\[\begin{split}J(\\theta) = \lvert Z'_{xx} \rvert^2 + \lvert Z'_{yy} \rvert^2 ,\end{split}\]

where \(Z'\) is the rotated tensor. The returned angle is the argmin over a 1 degree grid in 0..179.

The "phase_diff" fallback returns 0 if median \(\\lvert Z_{xy} \rvert \ge \lvert Z_{yx} \rvert\), else 90. It is intended for degraded or sparse data.

This function does not alter data. If you need deterministic behavior for incomplete arrays, consider preparing tensors with pycsamt.site.edit.fill_missing().

Examples

Single site, Swift estimate:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site import compute as cmp, edit as ed
>>> edf = EDIFile("S01.edi")
>>> edf = ed.fill_missing(edf, how="zero",
...                       components=("Z",), inplace=False)
>>> ang = cmp.strike_estimate(edf, method="swift")
>>> 0.0 <= ang < 180.0
True

Many sites, returning a DataFrame:

>>> e1 = EDIFile("S01.edi")
>>> e2 = EDIFile("S02.edi")
>>> df = cmp.strike_estimate([e1, e2], method="phase_diff")
...
>>> list(df.columns)
['station', 'method', 'theta_deg']

See also

pycsamt.site.edit.rotate

Rotate site tensors by a user angle.

pycsamt.site.compute.phase_slope

Phase slope diagnostic over a frequency band.

References

pycsamt.site.res_at_freq(obj, freq, *, how='nearest', api=None)#

Evaluate apparent resistivity at a target frequency.

Computes apparent resistivity for the \(Z_{xy}\) and \(Z_{yx}\) components at a requested frequency. Works with a single site or a collection. For a single site, a dict is returned. For multiple sites, a pandas.DataFrame is returned.

Parameters:
  • obj (Any) – A single EDI-like object (e.g. EDIFile) or an iterable of such objects. Each item must expose a .Z section of shape (n_freq, 2, 2) and a frequency vector.

  • freq (float) – Query frequency in Hz.

  • how (str, optional) –

    Selection mode:

    • "nearest" (default): choose the nearest available frequency in the site data and report that value.

    • "interp": linearly interpolate resistivity versus frequency using numpy.interp. Interpolation occurs on linear frequency, not log frequency.

  • api (bool | None)

Returns:

If obj is a single site, returns a dictionary with keys "res_xy", "res_yx", "f_used". If obj is iterable, returns a DataFrame with columns station, res_xy, res_yx, f_used.

Return type:

dict or pandas.DataFrame

Notes

Apparent resistivity \(\\rho_a\) is computed as

\[\begin{split}\rho_a = \frac{\lvert Z \rvert^2} {\mu_0\,2\pi\\,f} ,\end{split}\]

where \(Z\) is the complex impedance for the selected component, \(\\mu_0\) is the magnetic permeability of free space, and \(f\) is frequency in Hz.

When how="interp", the function first computes \(\rho_a\) at all native frequencies, then interpolates the result to the query frequency using linear interpolation in frequency. If the frequency vector or impedance is missing, NaN values are returned.

Examples

Single site, nearest selection:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site import compute as cmp, edit as ed
>>> edf = EDIFile("S01.edi")
>>> edf = ed.fill_missing(edf, how="zero",
...                       components=("Z",), inplace=False)
>>> out = cmp.res_at_freq(edf, 150.0, how="nearest")
...
>>> set(out.keys()) == {"res_xy", "res_yx", "f_used"}
...
True

Single site, interpolated:

>>> out = cmp.res_at_freq(edf, 150.0, how="interp")
...
>>> out["f_used"]
150.0

Many sites, DataFrame:

>>> e1 = EDIFile("S01.edi")
>>> e2 = EDIFile("S02.edi")
>>> df = cmp.res_at_freq([e1, e2], 1.0, how="interp")
...
>>> list(df.columns)
['station', 'res_xy', 'res_yx', 'f_used']

See also

pycsamt.site.compute.strike_estimate

Estimate 2D strike angle from Z.

pycsamt.site.edit.select_freq

Subset site data by frequency criteria.

References

pycsamt.site.phase_slope(obj, band, *, api=None)#

Estimate phase slopes within a frequency band.

For each site, this computes the least-squares slope of phase (degrees) versus \(\log_{10}(f)\) over the requested band. Two slopes are reported, one for \(Z_{xy}\) and one for \(Z_{yx}\).

If a single site is provided, a dictionary is returned. If an iterable of sites is provided, a pandas.DataFrame is returned with one row per station.

Parameters:
  • obj (Any) – A single EDI-like object (e.g. EDIFile) or an iterable of such objects.

  • band (tuple of float) – Inclusive frequency band as (fmin, fmax) in Hz. The order does not matter; the function uses the numeric min and max.

  • api (bool | None)

Returns:

Single site -> {"slope_xy": float, "slope_yx": float}. Multi-site -> DataFrame with columns ["station", "slope_xy", "slope_yx"].

Return type:

dict or pandas.DataFrame

Notes

The phase series for each off-diagonal component is computed as

\[\begin{split}\phi(f) = \operatorname{angle}(Z(f)) \times 180/\\pi ,\end{split}\]

then a straight line is fit

\[\begin{split}\phi(f) \approx a\\,\\log_{10}(f) + b\end{split}\]

using numpy.polyfit(x, y, 1) where \(x=\\log_{10}(f)\). The reported slope is \(a\) in units of degrees per decade.

Rows or sites with missing data in the band are reported as NaN. The function does not unwrap phase.

Examples

Single site:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site import compute as cmp, edit as ed
>>> edf = EDIFile("S01.edi")
>>> edf = ed.fill_missing(edf, how="zero",
...                       components=("Z",), inplace=False)
>>> out = cmp.phase_slope(edf, band=(1.0, 1000.0))
...
>>> set(out.keys()) == {"slope_xy", "slope_yx"}
...
True

Many sites:

>>> e1 = EDIFile("S01.edi")
>>> e2 = EDIFile("S02.edi")
>>> df = cmp.phase_slope([e1, e2], band=(0.1, 10.0))
...
>>> list(df.columns)
['station', 'slope_xy', 'slope_yx']

See also

pycsamt.site.compute.strike_estimate

Strike angle by Swift-style criterion.

pycsamt.site.compute.res_at_freq

Apparent resistivity at a target frequency.

References

pycsamt.site.tipper_magnitude(obj, *, per_freq=False, api=None)#

Summarize or tabulate tipper magnitudes.

Computes the magnitude of the tipper vector per frequency as

\[\lVert \mathbf{T} \rVert = \sqrt{\lvert T_x \rvert^2 + \lvert T_y \rvert^2} ,\]

where \(T_x, T_y\) are the complex tipper components. The result can be returned as per-frequency values or summarized statistics.

For a single site, returns a dict. For an iterable of sites, returns a pandas.DataFrame.

Parameters:
  • obj (Any) – A single EDI-like object (e.g. EDIFile) or an iterable of such objects. The tipper may be attached as ed.Tip, ed.T, or ed.TIP and must expose a 2-component array shaped (n_freq, 2) or (n_freq, 1, 2).

  • per_freq (bool, optional) – If False (default), return summary statistics (mean, median, max). If True, return per-frequency values.

  • api (bool | None)

Returns:

Single site:
  • per_freq=False -> {"mean", "median", "max"}

  • per_freq=True -> {"freq", "mag"}

Multi-site:
  • per_freq=False -> DataFrame with columns ["station", "mean", "median", "max"]

  • per_freq=True -> DataFrame with columns ["station", "freq", "mag"]

Return type:

dict or pandas.DataFrame

Notes

If the site has no tipper section, summary statistics are NaN and per-frequency mode yields an empty result for that site. To initialize missing arrays, consider pycsamt.site.edit.fill_missing() with components=("Tip",).

Frequencies are reported from the site frequency vector. The function assumes the tipper array and frequency vector are aligned along their first dimension.

Examples

Single site, summary stats:

>>> from pycsamt.seg.edi import EDIFile
>>> from pycsamt.site import compute as cmp, edit as ed
>>> edf = EDIFile("S01.edi")
>>> edf = ed.fill_missing(edf, how="zero",
...                       components=("Tip",), inplace=False)
>>> s = cmp.tipper_magnitude(edf, per_freq=False)
...
>>> set(s.keys()) == {"mean", "median", "max"}
...
True

Single site, per-frequency:

>>> out = cmp.tipper_magnitude(edf, per_freq=True)
...
>>> list(out.keys())
['freq', 'mag']

Many sites, summary:

>>> e1 = EDIFile("S01.edi")
>>> e2 = EDIFile("S02.edi")
>>> df = cmp.tipper_magnitude([e1, e2], per_freq=False)
...
>>> list(df.columns)
['station', 'mean', 'median', 'max']

See also

pycsamt.site.edit.fill_missing

Initialize or sanitize Z/Tip arrays in a site.

pycsamt.site.compute.res_at_freq

Apparent resistivity at a target frequency.

References

pycsamt.site.write_site(site, path)#

Write a single site (EDI) to a target path.

This is a thin, best-effort adapter around several common EDI writer spellings. The function will create parent directories as needed and then try, in order, the following methods on site until one succeeds:

  1. write(new_edifn=path)

  2. write(path)

  3. to_file(path)

  4. save(path)

If none of these exist or succeed, a RuntimeError is raised.

Parameters:
  • site (Any) – An EDI-like object. It can be a pycsamt.seg.edi.EDIFile or any object exposing one of the writer methods listed above.

  • path (str or pathlib.Path) – Destination file path. Parent directories are created if they do not exist.

Returns:

The resolved output path.

Return type:

pathlib.Path

Notes

This function does not enforce overwrite policy. Whether an existing file is replaced depends on the underlying writer implementation of the provided site object.

Examples

>>> from pathlib import Path
>>> from pycsamt.site.export import write_site
>>> class Dummy:
...     def to_file(self, p):  # minimal writer
...         Path(p).write_text("# dummy edi\\n", encoding="utf-8")
...
>>> out = write_site(Dummy(), Path("out") / "S01.edi")
>>> out.name
'S01.edi'
>>> out.exists()
True

See also

pycsamt.site.export.write_sites

Batch writing with templated filenames and optional manifest.

pycsamt.site.export.pack_zip

Archive a set of sites into a zip.

References

pycsamt.site.write_sites(sites, outdir, *, template='{station}.edi', exist_ok=False, manifest_csv=None)#

Write a collection of sites to a directory using a filename template.

The function accepts many input forms (Sites, an EDICollection, any iterable of EDI-like objects, or a single object) and writes each item to outdir. Filenames are rendered from a context and the template string.

Supported template keys (filled via a safe formatter):

  • {station} : current station name

  • {index} : zero-based index in the iteration order

  • {lat}, {lon}, {elev} : header coordinates, or NaN

  • {chainage} : optional header chainage, or NaN

If the rendered name does not end with .edi, the extension is appended automatically.

Parameters:
  • sites (Any) – A Sites instance, an EDICollection, any iterable of EDI-like objects, or a single EDI-like object. EDI-like means it implements one of: write(new_edifn=...), write(...), to_file(...), or save(...).

  • outdir (str or pathlib.Path) – Output directory. It is created if it does not exist.

  • template (str, optional) – Filename template. Defaults to "{station}.edi".

  • exist_ok (bool, optional) – If False (default), raise FileExistsError on the first name collision inside outdir. If True, allow overwriting subject to the writer behavior.

  • manifest_csv (str or pathlib.Path or None, optional) – If provided, write a CSV manifest with one row per written site. Columns are: index, station, lat, lon, elev, chainage, filename, path.

Returns:

Paths to the files written, in the same order as the input iteration.

Return type:

list of pathlib.Path

Notes

The index used in templating and in the manifest is the zero-based position in the input order. Coordinate fields come from the EDI header when available; missing values are written as NaN.

Examples

>>> from pathlib import Path
>>> from pycsamt.site.export import write_sites
>>> class EdiToFile:
...     def __init__(self, name): self._n = name
...     def to_file(self, p):
...         Path(p).write_text(f"# {self._n}\\n", encoding="utf-8")
...     # station name is taken from header helpers when present,
...     # but the template can still use {index}.
...
>>> outdir = Path("eds_out")
>>> paths = write_sites(
...     [EdiToFile("S01"), EdiToFile("S02")],
...     outdir,
...     template="{index:03d}_{station}"
... )
>>> [p.exists() for p in paths]
[True, True]
>>> # Write with a manifest
>>> mpath = Path("eds_out") / "manifest.csv"
>>> _ = write_sites(
...     [EdiToFile("S01"), EdiToFile("S02")],
...     outdir,
...     template="{station}",
...     exist_ok=True,
...     manifest_csv=mpath,
... )
>>> mpath.exists()
True

See also

pycsamt.site.base.Sites.write

Higher-level convenience bound to a Sites collection.

pycsamt.site.export.pack_zip

Create a zip archive instead of a directory tree.

References

pycsamt.site.pack_zip(sites, out_zip, *, template='{station}.edi', manifest_csv=None)#

Pack a set of sites into a zip archive using a filename template.

Each input item is written to a temporary directory first, then added to the out_zip archive using ZIP_DEFLATED. Filenames inside the archive are rendered from the same context as in write_sites(). If a name lacks the .edi suffix, it is appended automatically.

Optionally, a CSV manifest can be written alongside the archive.

Parameters:
  • sites (Any) – A Sites instance, an EDICollection, any iterable of EDI-like objects, or a single EDI-like object.

  • out_zip (str or pathlib.Path) – Destination zip file path. Parent directories are created as needed.

  • template (str, optional) – Filename template for entries stored in the archive. Defaults to "{station}.edi".

  • manifest_csv (str or pathlib.Path or None, optional) – If provided, write a CSV manifest next to the zip. Columns: index, station, lat, lon, elev, chainage, filename, path.

Returns:

The path to the created zip archive.

Return type:

pathlib.Path

Notes

Files are staged in a temporary directory and then compressed with zipfile.ZIP_DEFLATED. The index used in templating and the manifest corresponds to the input iteration order. This function does not delete or modify any original EDI sources.

Examples

>>> from pathlib import Path
>>> from zipfile import ZipFile
>>> from pycsamt.site.export import pack_zip
>>> class EdiSave:
...     def __init__(self, name): self._n = name
...     def to_file(self, p):
...         Path(p).write_text(f"# {self._n}\\n", encoding="utf-8")
...
>>> zpath = Path("out_bundle") / "sites.zip"
>>> out = pack_zip(
...     [EdiSave("A01"), EdiSave("A02")],
...     zpath,
...     template="{station}.edi",
...     manifest_csv=Path("out_bundle") / "manifest.csv",
... )
>>> out == zpath, zpath.exists()
(True, True)
>>> with ZipFile(zpath, "r") as zf:
...     sorted(zf.namelist())
['A01.edi', 'A02.edi']

See also

pycsamt.site.export.write_sites

Write files to a directory instead of an archive.

References

class pycsamt.site.EDIRecomputeRecord(source, output, line, station, status, message='')#

Bases: object

Per-station outcome for an EDI recomputation workflow.

Variables:
  • source (pathlib.Path or None) – Source EDI path when known.

  • output (pathlib.Path or None) – Written EDI path when write=True and the station was written successfully.

  • line (str or None) – Line/group name inferred from the source directory.

  • station (str) – Station name after recomputation and optional renaming.

  • status (str) – "ok" for success or "failed" when processing continued after an error.

  • message (str, default "") – Optional diagnostic message.

Parameters:
  • source (Path | None)

  • output (Path | None)

  • line (str | None)

  • station (str)

  • status (str)

  • message (str)

source: Path | None#
output: Path | None#
line: str | None#
station: str#
status: str#
message: str = ''#
class pycsamt.site.EDIRecomputeResult(sites, records, output_root=None, items=<factory>)#

Bases: object

Result returned by EDIRecomputer.

Variables:
Parameters:
sites: Sites#
records: list[EDIRecomputeRecord]#
output_root: Path | None = None#
items: list[EDIFile]#
property edis: list[EDIFile]#

Return recomputed raw EDI objects.

property paths: list[Path]#

Return successfully written output paths.

property failed: list[EDIRecomputeRecord]#

Return failed records.

to_manifest(path)#

Write the records to a CSV manifest.

Parameters:

path (str | Path)

Return type:

Path

class pycsamt.site.EDIRecomputer(output_root=None, output_name='recomputed_edis', preserve_line_dirs=True, template='{station}.edi', overwrite=False, write=True, manifest_csv=True, rotate_angle=None, rotate_components=<factory>, fmin=None, fmax=None, keep_freq=None, fill_missing_values=None, recompute_resphase=True, rename_policy=None, datatype=None, synthesize_spectra=False, recursive=True, strict=False, on_dup='replace', copy=True, progress=False, verbose=0, progress_callback=None)#

Bases: object

Recompute and rewrite EDI files using pyCSAMT conventions.

This class is a workflow layer over the lower-level site helpers. It accepts EDI objects, EDI collections, files, line folders, or survey folders, applies a sequence of optional normalizations, and can write pyCSAMT-generated EDI files to a new output tree.

Parameters:
  • output_root (str or pathlib.Path, optional) – Root directory for recomputed EDI files. If omitted, a recomputed_edis directory is created next to a selected line folder, or inside a selected survey folder.

  • output_name (str, default "recomputed_edis") – Directory name used when output_root is not provided.

  • preserve_line_dirs (bool, default True) – If True, write each inferred line into its own subdirectory under output_root. If False, write all recomputed EDI files directly under output_root.

  • template (str, default "{station}.edi") – Filename template. Available keys are station, index, line, and source_stem.

  • overwrite (bool, default False) – Allow replacing existing output files.

  • write (bool, default True) – If False, only return recomputed in-memory objects.

  • manifest_csv (bool or str or pathlib.Path, default True) – Write a CSV manifest. True writes <output_root>/manifest.csv. A path writes there.

  • rotate_angle (float, optional) – Rotation angle in degrees. If omitted, no rotation is applied.

  • rotate_components (iterable of str, default ("Z", "Tip")) – Components to rotate. Accepts values such as "Z", "impedance", "Tip", and "tipper".

  • fmin (float, optional) – Frequency range to keep before recomputation.

  • fmax (float, optional) – Frequency range to keep before recomputation.

  • keep_freq (iterable of int, optional) – Explicit frequency indices or mask passed to pycsamt.site.edit.select_freq().

  • fill_missing_values ({"zero", "nan"}, optional) – Missing-value policy passed to pycsamt.site.edit.fill_missing().

  • recompute_resphase (bool, default True) – Recompute apparent resistivity and phase from impedance.

  • rename_policy (callable, optional) – Function mapping the current station name to a new name.

  • datatype (str, optional) – EDI writer datatype override, for example "mt" or "emap".

  • synthesize_spectra (bool, default False) – Ask the pyCSAMT EDI writer to synthesize spectra when possible and missing.

  • recursive (bool, default True) – Recurse while discovering EDI files inside line folders.

  • strict (bool, default False) – Raise on read/process/write errors instead of recording failed manifest rows.

  • on_dup ({"replace", "keep"}, default "replace") – Duplicate station policy during loading.

  • copy (bool, default True) – Recompute copies of loaded EDI objects. Keep this enabled when the original objects should remain unchanged.

  • progress (bool or {"auto"}, default False) – Show progress while recomputing.

  • verbose (int, default 0) – Verbosity for loading, processing, and writing.

  • progress_callback (Callable[[int, int, str, str, str], None] | None)

output_root: str | Path | None = None#
output_name: str = 'recomputed_edis'#
preserve_line_dirs: bool = True#
template: str = '{station}.edi'#
overwrite: bool = False#
write: bool = True#
manifest_csv: bool | str | Path = True#
rotate_angle: float | None = None#
rotate_components: Iterable[str]#
fmin: float | None = None#
fmax: float | None = None#
keep_freq: Iterable[int] | None = None#
fill_missing_values: str | None = None#
recompute_resphase: bool = True#
rename_policy: Callable[[str], str] | None = None#
datatype: str | None = None#
synthesize_spectra: bool = False#
recursive: bool = True#
strict: bool = False#
on_dup: str = 'replace'#
copy: bool = True#
progress: bool | str = False#
verbose: int = 0#
progress_callback: Callable[[int, int, str, str, str], None] | None = None#
run(source)#

Run the recomputation workflow.

Parameters:

source (Any)

Return type:

EDIRecomputeResult

recompute_one(edi)#

Recompute one EDI object and return the result.

Parameters:

edi (Any)

Return type:

EDIFile

pycsamt.site.recompute_edi(edi, *, rotate_angle=None, rotate_components=('Z', 'Tip'), fmin=None, fmax=None, keep_freq=None, fill_missing_values=None, recompute_resphase=True, rename_policy=None, copy=True)#

Recompute one EDI object.

The operation is copy-returning by default. It can rotate transfer functions, subset frequencies, fill missing values, recompute apparent resistivity/phase, and rename station ids.

Parameters:
Return type:

EDIFile

pycsamt.site.recompute_edis(source, **kwargs)#

Convenience function for EDIRecomputer.

Examples

>>> result = recompute_edis("WILLY_DATA", rotate_angle=30.0)
>>> result.paths
[...]
Parameters:
Return type:

EDIRecomputeResult

class pycsamt.site.SiteReport(site)#

Bases: object

Statistics and display for a single Site.

Parameters:

site (Site-like) – Any object that exposes name, coords, freq, z, rho, phase, and tipper as per SiteMixin.

Examples

from pycsamt.site.report import SiteReport
rep = SiteReport(site)
rep.report()               # rich terminal output
d = rep.to_dict()          # machine-readable dict
report(*, detail=False)#

Print a rich panel with site statistics.

Parameters:

detail (bool) – If True, include per-frequency Z and ρ–φ tables.

Return type:

None

summary()#

Return a one-line summary string.

Return type:

str

to_dict()#

Return a plain dict of all computed statistics.

Return type:

dict[str, Any]

to_dataframe(kind='resphase', *, api=None)#

Export site arrays to a pandas.DataFrame.

Parameters:
  • kind (str) – Passed to to_dataframe().

  • api (bool or None, default None) – True forces API view wrapping; False returns a bare pandas dataframe; None (default) defers to the global PYCSAMT_API_VIEW setting.

Return type:

Any

class pycsamt.site.SitesReport(sites)#

Bases: object

Statistics and display for a Sites collection.

Parameters:

sites (Sites-like) – Any iterable of Site-like objects.

Examples

from pycsamt.site.report import SitesReport
rep = SitesReport(sites)
rep.report()              # full survey panel + per-station table
rep.report(top=10)        # first 10 stations only
df = rep.to_dataframe()   # one row per station
report(*, top=None, detail=False)#

Print a full survey report.

Parameters:
  • top (int, optional) – Limit the per-station table to the first top stations.

  • detail (bool) – If True, print additional per-station statistics.

Return type:

None

summary()#
Return type:

str

to_dict()#

Return a list of per-station stat dicts.

Return type:

list[dict[str, Any]]

to_dataframe(*, api=None)#

Return a pandas.DataFrame with one row per station.

Parameters:

api (bool | None)

Return type:

Any

Site Modules#