2.9. pycsamt.seg#

SEG-style survey parsing, EDI objects, measurement sections, spectra, time-series helpers, and validation tools.

EDI section models for metadata, spectra, time series, and impedance data.

class pycsamt.seg.EDIMixin#

Bases: CoreObject

Lightweight registry and helpers used by EDI readers.

The mixin stores parsed section objects (e.g. >HEAD, >=MTSECT, >=SPECTRASECT) in a simple dictionary, and provides tiny utilities to manage and query them.

It does not perform I/O. Host classes remain free to decide when and how sections are discovered and populated. This keeps the orchestration logic small and testable.

Variables:

sections (dict[str, object]) – Case–insensitive mapping from a logical section key (e.g. "head", "mtsect", "spectra") to the parsed object that represents that section.

add_section(key, obj)#

Register obj under key. Keys are normalized to lower case.

Parameters:
Return type:

None

get_section(key)#

Retrieve a previously added section or None.

Parameters:

key (str)

Return type:

Any

has_section(key)#

Return True if a section exists under key.

Parameters:

key (str)

Return type:

bool

_tag2name(tag)#

Translate a raw >=... header tag to a canonical registry key (e.g. ">=MTSECT" "mtsect").

Notes

The registry is intentionally untyped so that different parser implementations can coexist. For instance, a project may store a header object (SpectraSECT) and also the decoded payload object (Spectra) under separate keys.

Examples

>>> mix = EDIMixin()
>>> mix._init_registry()
>>> mix.add_section("HEAD", object())
>>> mix.has_section("head")
True
>>> isinstance(mix.get_section("head"), object)
True

See also

EDIFile

High level reader that uses the registry to expose parsed sections to callers.

References

[EDIMixin-1]

SEG EDI MT/EMAP standard (1987). MTNet.

add_section(key, obj)#
Parameters:
Return type:

None

get_section(key)#
Parameters:

key (str)

Return type:

Any

has_section(key)#
Parameters:

key (str)

Return type:

bool

class pycsamt.seg.EDIOMixin#

Bases: CoreObject

Tolerant >BLOCK parser and TF (Z/Tipper) builder.

The mixin provides two core utilities used by EDIFile after headers are discovered:

The reader accepts both complex tensor blocks and the scalar families (RHO* and PHS*). When complex blocks are missing, the impedance tensor is reconstructed from resistivity and phase if possible.

Variables:

None

_scan_blocks(path, start=None, empty_val=1e32)#

Return a mapping key list[float] by streaming lines until the next section or EOF. Unknown keys are ignored. Values equal to empty_val (the EDI missing- data sentinel) are converted to NaN, never to zero – zero is a valid measured value and must not be invented for a period the instrument never recorded.

_build_from_comp(comp, z_obj, tip_obj)#

Populate z_obj and tip_obj from the components. Frequency order is normalized to descending. Z–error arrays are converted from variance blocks (.VAR).

Notes

  • Frequency order is unified to high→low. This follows a common practice in EDI archives and simplifies plotting.

  • The tipper is optional. If no tipper blocks are found, tip_obj is left untouched.

  • For RHO*/PHS* the method also carries *.ERR if present. Otherwise zero errors are assumed.

Examples

>>> comp = {"freq": [10, 1], "zxxr": [1, 2], "zxxi": [0, 0]}
>>> from pycsamt.z.z import Z
>>> from pycsamt.z.tipper import Tipper
>>> mix = EDIOMixin()
>>> z, t = Z(), Tipper()
>>> mix._build_from_comp(comp, z_obj=z, tip_obj=t)
>>> z.n_freq
2

See also

EDIFile

Uses these utilities during read_data().

References

[EDIOMixin-1]

SEG EDI MT/EMAP standard (1987). MTNet.

class pycsamt.seg.EDIFile(path=None, *, verbose=0)#

Bases: EDIMixin, EDIOMixin

High–level EDI dispatcher for SEG/EMAP/CSAMT archives.

The class discovers top–level headers (e.g. >=MTSECT, >=SPECTRASECT, >=TSERIESSECT), loads the matching data blocks, and exposes convenient Python containers for impedance tensors, tippers, spectra, and time series.

It also writes EDI files by reusing the in–memory sections, preserving headers when possible, and regenerating block payloads from the current objects.

Parameters:
  • path (str or Path, optional) – File to open. If given, read() is executed on construction.

  • verbose (int, optional) – Verbosity level propagated to subcomponents.

Variables:
  • path (Path or None) – Bound file path (if any).

  • Z (pycsamt.z.z.Z) – Impedance tensor container with errors and rotations.

  • Tip (pycsamt.z.tipper.Tipper) – Tipper container (optional).

  • sections (dict[str, object]) – Registry populated via EDIMixin.

  • block_size (int) – Numbers per line when writing numeric payloads.

  • float_fmt (str) – Float formatter used for numeric blocks.

  • header_tpl (str) – Template used for logical block titles.

  • Properties

  • ----------

  • station (str or None) – Shortcut to >HEAD.DATAID. Setter also mirrors the value to the MT/EMAP section id.

  • processingsoftware (str or None) – Shortcut to the name of the processing software from >INFO.

read(path=None)#

Load headers and sections. Parse numeric blocks into Z and Tip. Also attaches Spectra and TimeSeries if present.

Parameters:

path (str | Path | None)

Return type:

EDIFile

read_data()#

Low–level numeric parsing used by read().

Return type:

EDIFile

compose_headers()#

Serialize only the headers (no data blocks).

Parameters:

stamp_head (bool)

Return type:

str

write(...)#

Write a full EDI assembled from current objects and sections. MT or EMAP numeric families are chosen from the MT/EMAP header or inferred from context.

Parameters:
  • edi_fn (str | None)

  • new_edifn (str | None)

  • datatype (str | None)

  • savepath (str | Path | None)

  • add_filter_array (ndarray | None)

  • synthesize_spectra (bool)

  • preserve_zero (bool)

  • stamp_headers (bool)

  • force_tipper (bool | None)

Return type:

str

interpolate(new_freq, kind="slinear", ...)#

Interpolate Z on a new frequency grid. The grid is rounded to two decimals for stable serialization.

Parameters:
Return type:

Z

write_new_edi(edi_fn=None, Z=None, Tipper=None, ...)#

Rebuild a clean container bound to the same source, swap selected transfer functions, and delegate to write().

Parameters:
Return type:

str

Notes

  • Frequency order is normalized to descending on read. Therefore, a file written by write() and read back will expose Z.freq in high→low order.

  • The interpolation routine enforces the new grid to live strictly inside the source span when bounds_error is True.

  • Missing blocks are handled gracefully. If complex impedances are absent, the reader tries to reconstruct them from RHO*/PHS* families.

Examples

>>> ed = EDIFile("site.edi")
>>> ed.station
'SITE'
>>> ed.Z.n_freq > 0
True
>>> out = ed.write(savepath="outdir")
>>> Path(out).exists()
True
>>> fnew = np.geomspace(ed.Z.freq.min() * 1.1, ed.Z.freq.max() * 0.9, 16)
>>> z2 = ed.interpolate(fnew, kind="linear")
>>> ed.write_new_edi(edi_fn="interp.edi", Z=z2)

See also

EDIMixin

Registry and convenience helpers used internally.

EDIOMixin

Numeric block reader and TF builder used by read_data().

pycsamt.seg.spectra.Spectra, pycsamt.seg.time_series.TimeSeries

References

[EDIFile-1]

SEG EDI MT/EMAP standard (1987). MTNet.

[EDIFile-2]

B. Groom, R. Bailey (1989). Decomposition of the magnetotelluric impedance tensor. Geophysics.

read(path=None)#
Parameters:

path (str | Path | None)

Return type:

EDIFile

read_data()#
Return type:

EDIFile

compose_headers(*, stamp_head=True)#

Serialize structural EDI headers without numeric data blocks.

stamp_head preserves the historical default in which >HEAD file/program timestamps are refreshed by Head. Format converters may disable stamping when they need to retain mapped provenance metadata exactly.

Parameters:

stamp_head (bool)

Return type:

str

write(edi_fn=None, new_edifn=None, datatype=None, savepath=None, add_filter_array=None, synthesize_spectra=False, preserve_zero=False, stamp_headers=True, force_tipper=None, **kwargs)#
Parameters:
  • edi_fn (str | None)

  • new_edifn (str | None)

  • datatype (str | None)

  • savepath (str | Path | None)

  • add_filter_array (ndarray | None)

  • synthesize_spectra (bool)

  • preserve_zero (bool)

  • stamp_headers (bool)

  • force_tipper (bool | None)

Return type:

str

interpolate(new_freq, *, kind='slinear', bounds_error=True, period_buffer=None)#
Parameters:
Return type:

Z

interpolate_z(new_freq, *, kind='slinear', bounds_error=True, period_buffer=None)#
Parameters:
Return type:

Z

write_new_edi(edi_fn=None, Z=None, Tipper=None, *, Spectra=None, TimeSeries=None, sections=None, **kwargs)#
Parameters:
Return type:

str

property n_freq: int#

Number of frequencies in Z or Spectra.

property station: str | None#

Return DATAID from >HEAD if present.

property empty: float | None#

Return EMPTY sentinel from >HEAD if present.

property dtype: str | None#

Infer ‘mt’ or ‘emap’ from >=MT/EMAPSECT or tipper.

property has_tipper: bool#

True if non-zero tipper array present.

property spectra_sect#
property timeseries_sect#
property spectra#

Return high-level Spectra object if present.

property spectra_io#

Return SpectraIO if present.

property timeseries#

Return high-level TimeSeries object if present.

property timeseries_io#

Return TSIO if present.

property channels: list[str]#

Return TS channels if any, else [].

property path_str: str#

EDI path as string or empty if not set.

property edi_dir: Path | None#

Parent directory of EDI file if path set.

property processingsoftware: str | None#

Return INFO.Processing.ProcessingSoftware.name.

class pycsamt.seg.Spectra(name=None, *, verbose=0)#

Bases: EMBase

Container for >SPECTRA blocks grouped per frequency.

The class gathers one spectra record per frequency and exposes typed header fields (frequency, rotation flag, bandwidth, and averaging time) together with the numeric values stored in each block. It is a compact, array- oriented view on top of SpectraSECT and SpectraIO.

Parameters:
  • name (str, optional) – Display name forwarded to BaseEM.

  • verbose (int, default 0) – Verbosity level, forwarded to BaseEM.

Variables:
  • freq (ndarray, shape (n_blk,)) – Frequency (Hz) per block. Missing values are set to np.nan.

  • rotspec (ndarray of int, shape (n_blk,)) – Rotation specifier per block. Missing values are set to -1.

  • bw (ndarray, shape (n_blk,)) – Nominal bandwidth (Hz) per block or np.nan.

  • avgt (ndarray, shape (n_blk,)) – Averaging time (s) per block or np.nan.

  • values (list of ndarray) – Numeric payload for each block. Lengths may differ across blocks, as allowed by the SEG format.

  • n_values (ndarray of int, shape (n_blk,)) – Number of values in each block (as parsed or counted).

Notes

Blocks may contain vendor-specific options beyond the canonical FREQ, ROTSPEC, BW, and AVGT. Those options are preserved when round-tripping via to_io(). The class does not impose a common length across spectra vectors; if you require a 2-D array, pad the values list explicitly.

The constructor itself does not read files. Use from_io() or from_file() to populate an instance from sections and data blocks.

To recover a format-neutral EMTF document with full FCU-compatible covariance from this container, use pycsamt.emtf.EMTF.from_edi_spectra() or pycsamt.emtf.converters.spectra.spectra_to_emtf() on the pycsamt.emtf side rather than a method on this class — spectra parsing stays in pycsamt.seg, transfer-function/covariance recovery stays in the EMTF interoperability layer.

from_io(sect, io) : classmethod

Build a Spectra from SpectraSECT and SpectraIO.

from_file(path) : classmethod

Convenience that calls SpectraSECT.from_file and SpectraIO.from_file, then delegates to from_io().

to_io()#

Serialize the current state to a fresh pair (SpectraSECT, SpectraIO) that can be written back to an EDI file.

Return type:

tuple[SpectraSECT, SpectraIO]

Examples

Read, inspect, and serialize spectra:

from pycsamt.seg.spectra import Spectra

sp = Spectra.from_file("site.edi")
f = sp.freq
first = sp.values[0]

sect2, io2 = sp.to_io()
# writer can now combine sect2.write() and io2.write()

See also

pycsamt.seg.spectra.SpectraSECT

Header for >=SPECTRASECT sections.

pycsamt.seg.spectra.SpectraIO

Reader/writer for >SPECTRA blocks.

pycsamt.seg.EDIFile

High-level dispatcher that can attach spectra to an EDI session.

References

[Spectra-1]

SEG EDI standard, Spectra Data Sections. Society of Exploration Geophysicists.

[Spectra-2]

Chave, A. D., & Jones, A. G. (2012). The Magnetotelluric Method: Theory and Practice. Cambridge Univ. Press.

band: list[str]#
chan_ids: list[str]#
id_to_chtype: dict[str, str]#
declared_nfreq: int | None#
parsed_nfreq: int#
property freq: ndarray#
property S: ndarray#
property fcu_cross_spectra: ndarray#

Return spectra in the EMTF-FCU cross-power convention.

Spectra.S retains the historical pyCSAMT convention channel_i * conj(channel_j) so existing callers and to_Z() are unchanged by Phase 8. EMTF FCU uses the conjugate convention conj(channel_i) * channel_j.

EDI SPECTRA input keeps an exact FCU view in _S_fcu. For spectra constructed through the historical pyCSAMT API, the FCU view is therefore the element-wise complex conjugate of S.

property missing_mask: ndarray | None#

Return the per-frequency missing cross-spectral component mask.

property n_freq: int#
property n_chan: int#
classmethod from_io(sect, io, *, empty=1e+32, verbose=0)#
Parameters:
Return type:

Spectra

classmethod from_file(path, *, empty=1e+32, verbose=0)#

Read a Spectra directly from an EDI file path.

Convenience wrapper around from_io() that calls SpectraSECT.from_file and SpectraIO.from_file internally.

Parameters:
  • path (str or Path) – Path to the EDI file containing >=SPECTRASECT and >SPECTRA blocks.

  • empty (float) – Sentinel value for missing spectra entries. Default 1e32.

  • verbose (int) – Verbosity level forwarded to from_io().

Return type:

Spectra

to_edi(source_edi=None, *, station_name=None, e_labels=('EX', 'EY'), h_labels=('HX', 'HY'), ridge=None, estimate_error=False, dof=None)#

Convert cross-spectra to an MT-impedance EDIFile.

Calls to_Z() and assembles a complete >=MTSECT / >FREQ / >ZXXR / >ZXYR / … EDI ready to be saved with write().

The structural sections (>HEAD, >INFO, >=DEFINEMEAS) are re-used from source_edi when provided, preserving all acquisition metadata; otherwise a minimal header is synthesised from the Spectra metadata.

According to the SEG EDI standard (§ 7.53, 12.1), an MT data section requires:

>=MTSECT
  SECTID=...
  NFREQ=...
  HX=...  HY=...  HZ=...  EX=...  EY=...
>FREQ   //N
  ...
>ZXXR  ROT=ZROT  //N
  ...
>ZXXI  ROT=ZROT  //N
  ...
...
>END

The measurement IDs for HX, HY, … in >=MTSECT are resolved from id_to_chtype (populated by SpectraSECT from >HMEAS / >EMEAS lines).

Parameters:
  • source_edi (str, Path, or EDIFile, optional) – Spectra EDI file whose >HEAD, >INFO, and >=DEFINEMEAS sections are copied into the output. Pass the same path used with from_file() to produce a fully metadata-rich result. When None, a minimal header is synthesised.

  • station_name (str, optional) – Override for the DATAID in >HEAD and SECTID in >=MTSECT. Defaults to name.

  • e_labels (tuple of str) – Electric channel type labels forwarded to to_Z().

  • h_labels (tuple of str) – Horizontal magnetic channel type labels forwarded to to_Z().

  • ridge (float, optional) – Tikhonov regularisation forwarded to to_Z().

  • estimate_error (bool) – If True, propagate 1-σ errors into >ZXX.VAR … blocks.

  • dof (float or ndarray, optional) – Effective degrees of freedom forwarded to to_Z().

Returns:

Fully populated MT-impedance container. Call write() to save.

Return type:

EDIFile

Raises:

EdIDataError – If to_Z() fails (channel types not resolved, singular magnetic block, etc.).

Examples

Convert and save:

sp = Spectra.from_file("site.edi")
ed = sp.to_edi("site.edi", estimate_error=False)
out = ed.write(savepath="mt_output/")

Convert with a custom station name and error propagation:

ed = sp.to_edi(
    "site.edi",
    station_name="HBH03_imp",
    estimate_error=True,
    dof=24.0,
)
out = ed.write(savepath="mt_output/")

Verify the round-trip:

from pycsamt.seg.edi import EDIFile

ed2 = EDIFile(out)
assert ed2.Z.n_freq == sp.n_freq
to_io()#
Return type:

tuple[SpectraSECT, SpectraIO]

matrix(k)#
Parameters:

k (int)

Return type:

ndarray

psd(idx)#
Parameters:

idx (int)

Return type:

ndarray

cross(i, j)#
Parameters:
Return type:

ndarray

validate_frequency_count(*, policy='raise')#

Validate SPECTRASECT.NFREQ against parsed/usable blocks.

Parameters:

policy ({"raise", "warn", "ignore"}) – Action when the declared count differs from either the number of parsed >SPECTRA blocks or the number of usable frequency blocks. FCU historically stops on this inconsistency; warn is provided for recovery of heterogeneous archives.

Returns:

True if the declared NFREQ matches both the parsed and usable block counts (or no NFREQ was declared); False when a mismatch is found and policy is "warn" or "ignore".

Return type:

bool

Raises:
  • ValueError – If policy is not one of "raise", "warn", "ignore".

  • EdIDataError – If a mismatch is found and policy="raise".

rotate(theta_deg, *, pairs=None)#
Parameters:
Return type:

None

to_Z(*, id_to_chtype=None, e_labels=('EX', 'EY'), h_labels=('HX', 'HY'), use_remote=False, ridge=None, estimate_error=True, dof=None)#

Recover an impedance tensor Z and, if available, the tipper from cross-spectra stored in this Spectra.

The method resolves channel types, extracts the electric and magnetic sub-blocks, and computes per-frequency Z = S_EH @ inv(S_HH). If a vertical magnetic channel is present it also computes the tipper T = S_ZH @ inv(S_HH). Optional ridge regularization can be applied to stabilize the magnetic block.

Parameters:
  • id_to_chtype (dict of str to str, optional) – Mapping from measurement IDs (as recorded in >=SPECTRASECT or DefineMeas) to channel types ("HX", "HY", "HZ", "EX", "EY"). If omitted, the method uses self.id_to_chtype when available, otherwise it interprets self.chan_ids directly as labels.

  • e_labels (tuple of str, default ("EX", "EY")) – Labels that identify the two electric channels used for the E block.

  • h_labels (tuple of str, default ("HX", "HY")) – Labels that identify the two horizontal magnetic channels used for the H block.

  • use_remote (bool, default False) – When duplicate electric channels exist (e.g., local and remote), choose the second occurrence for the E block if True; otherwise choose the first.

  • ridge (float, optional) – Non-negative Tikhonov regularization added to S_HH prior to inversion, S_HH + ridge * I.

  • estimate_error (bool, default True) – If True, estimate per-component 1-sigma standard errors for Z (and tipper when available) using compute_errors_from_S and the degrees of freedom given by dof (or inferred; see Notes).

  • dof (float or ndarray, optional) – Effective degrees of freedom per frequency. If an array is provided it must broadcast to n_freq. If None and estimate_error is True, the method tries to infer DoF from metadata via effective_dof_from_meta using segnum, or avgt * bw as a fallback.

Returns:

  • z_obj (pycsamt.z.z.Z) – Impedance object on the spectra frequency grid with z populated and, when estimated, z_err set.

  • tip (pycsamt.z.tipper.Tipper or None) – Tipper on the same grid when HZ is available. When errors are estimated, tipper uncertainties are attached.

Raises:

EdIDataError – If spectra are empty, channel types cannot be resolved, or the stabilized magnetic block is singular.

Notes

Per frequency, Z is formed as Z = S_EH @ inv(S_HH), where S_EH is the cross-spectra between E and H, and S_HH is the magnetic auto/cross block. If a vertical magnetic channel is available, the tipper is computed as T = S_ZH @ inv(S_HH).

Channel type resolution proceeds in this order:

  1. explicit id_to_chtype argument,

  2. self.id_to_chtype from the section header or DefineMeas,

  3. direct interpretation of self.chan_ids.

When both local and remote electric channels are present, setting use_remote=True chooses the second occurrence as a simple heuristic. Frequency ordering is preserved.

Uncertainties are computed by first-order propagation under a complex-Wishart model and scale as 1 / sqrt(DoF). If DoF cannot be determined for every frequency, no per-frequency array is attached at all: z_err (and tip.tipper_err when applicable) come back as None rather than an array of NaN.

Examples

>>> Zhat, That = spectra.to_Z()
>>> Zhat, _ = spectra.to_Z(use_remote=True, ridge=1e-6)
>>> Zhat, _ = spectra.to_Z(dof=np.full(spectra.n_freq, 24.0))

See also

Spectra.from_Z

Inverse operation that synthesizes spectra.

spectra_from_Z

Functional wrapper for the inverse operation.

effective_dof_from_meta

Infer DoF from segnum, avgt and bw.

compute_errors_from_S

Per-frequency uncertainty estimator.

References

[Spectra-to-Z-1]

Chave, A. D., & Jones, A. G. (2012). The Magnetotelluric Method: Theory and Practice. Cambridge University Press.

[Spectra-to-Z-2]

Bendat, J. S., & Piersol, A. G. (2011). Random Data: Analysis and Measurement Procedures. Wiley.

classmethod from_Z(z_obj, **kws)#

Create a Spectra from a transfer function Z.

This class method is a thin, convenience wrapper around spectra_from_Z(). It synthesizes a full Hermitian cross–spectral density tensor from the impedance tensor Z(f) and optional inputs that control magnetic power and tipper usage.

Parameters:
  • z_obj (Z) – Input impedance object. The attributes z_obj.z (shape (n, 2, 2)) and z_obj.freq (shape (n,)) must be set.

  • **kws (Any) – Forwarded to spectra_from_Z(). See that function for the complete set of options such as S_HH, H_psd, tipper, include_hz, and chan_order.

Returns:

A spectra container on the same frequency grid as z_obj. Channel order follows the requested chan_order (default: HX, HY, EX, EY).

Return type:

Spectra

Raises:

EdIDataError – If z_obj is incomplete (missing z or freq).

Notes

Absolute spectral levels are not carried by the impedance tensor. To obtain physically scaled spectra, provide magnetic spectra via S_HH or H_psd. If neither is given, a unit–power assumption is used (S_HH = I), which is suitable for tests but not for quantitative analysis.

This method does not infer per–frequency metadata such as bandwidth or averaging time; those fields are initialized with zeros/NaNs.

Examples

>>> ed = EDIFile("site_imp.edi")
>>> sp = Spectra.from_Z(
...     ed.Z,
...     H_psd=(np.ones(ed.Z.n_freq), np.ones(ed.Z.n_freq), None),
... )
>>> sect, io = sp.to_io()
>>> _ = ed.write_new_edi(
...     edi_fn="site_with_synth_spec.edi",
...     Spectra=sp,
... )

See also

spectra_from_Z

Functional API that performs the synthesis.

pycsamt.seg.ops.synthesize_spectra_from_z

Low–level array helper used under the hood.

Spectra.to_Z

Inverse operation (spectra → Z).

References

[Spectra-from-Z-1]

Chave, A. D., & Jones, A. G. (2012). The Magnetotelluric Method: Theory and Practice. Cambridge Univ. Press.

[Spectra-from-Z-2]

Bendat, J. S., & Piersol, A. G. (2011). Random Data: Analysis and Measurement Procedures. Wiley.

[Spectra-from-Z-3]

SEG EDI MT/EMAP standard (1987). MTNet.

class pycsamt.seg.SpectraSECT(*args, verbose=0, logger=None, **kws)#

Bases: EDIComponentBase

Minimal container for the >=SPECTRASECT header.

The class parses and serializes the spectra section header that precedes one or more >SPECTRA data blocks. It collects the option key/values and the ordered set of measurement IDs that the spectra apply to, as described by the SEG EDI convention [SpectraSECT-1].

Parameters:
  • verbose (int or bool, optional) – Verbosity flag propagated from Base.

  • logger (object, optional) – Logger instance to use. If None, a default null-safe logger is attached.

  • **kws – Additional field overrides. Keys may include any attribute listed below.

  • args (Any)

Variables:
  • sectid (str or None) – Section identifier, often a site name. Some files omit this or use a numeric ID.

  • nchan (int or None) – Number of channels in the spectra set.

  • nfreq (int or None) – Number of frequencies expected in the section.

  • maxblks (int or None) – Maximum number of blocks. Rarely used.

  • meas_ids (list of str) – Ordered measurement ID list that follows the option lines in >=SPECTRASECT.

  • start_data_lines_num (int or None) – Line index in the EDI where the first >SPECTRA block begins. Set by from_file().

Notes

  • Parsing is tolerant to case and extra whitespace.

  • Unknown header keys are ignored instead of raising.

  • The measurement ID list is collected from the header body once option lines end.

  • The start of the spectra data is detected by the first >SPECTRA tag, by the next >=... tag, or by end of file, whichever comes first.

  • For consistent processing, maintain the same frequency set across related data sections, as recommended in the EDI spec [SpectraSECT-1].

See also

SpectraIO

Reader/writer for the >SPECTRA data blocks.

MTEMAP

Header for >=MTSECT or >=EMAPSECT. The spectra frequency set should match the MT set.

TSect

Header for >=TSERIESSECT (time series).

Examples

Read only the header and measurement IDs:

>>> sect = SpectraSECT.from_file("site.edi")
>>> sect.nfreq, sect.nchan
(128, 5)
>>> sect.meas_ids[:2]
['HX1', 'HY1']

Serialize a header:

>>> sect.nfreq = 3
>>> sect.meas_ids = ["HX", "HY", "EX", "EY"]
>>> lines = sect.write()
>>> print("".join(lines).strip())
>=SPECTRASECT
  SECTID=...
  NCHAN=...
  NFREQ=3
  MAXBLKS=...
    // 4
     HX
     HY
     EX
     EY

References

[SpectraSECT-1] (1,2)

SEG EDI standard, “Spectra Data Sections”.

KEY_ORDER: list[str] = ['sectid', 'nchan', 'nfreq', 'maxblks']#
sectid: str | None#
nchan: int | None#
nfreq: int | None#
maxblks: int | None#
meas_ids: list[str]#
start_data_lines_num: int | None#
id_to_chtype: dict[str, str]#
classmethod from_file(edi_path)#
Parameters:

edi_path (str)

Return type:

SpectraSECT

write()#
Return type:

list[str]

class pycsamt.seg.SpectraIO(*args, verbose=0, logger=None, **kws)#

Bases: EDIComponentBase

Read and write >SPECTRA data blocks.

A spectra section contains one block per frequency. Each block begins with a >SPECTRA line that holds options such as frequency and bandwidth, optionally followed by a comment with the number of values, then one or more lines of numeric values.

Known options are normalized:

  • FREQ : float

  • ROTSPEC : int

  • BW : float

  • AVGT : float

Unrecognized options are preserved in a free-form mapping so that vendor-specific metadata is not lost.

Parameters:
  • verbose (int or bool, optional) – Verbosity flag propagated from Base.

  • logger (object, optional) – Logger instance to use. If None, a default null-safe logger is attached.

  • **kws – Additional field overrides.

  • args (Any)

Variables:

blocks (list of _SpectraBlock) – Parsed spectra blocks, one per frequency. Each block stores header options, the optional value count hint, and the numeric values.

Notes

  • from_file() reads successive >SPECTRA blocks starting from a given line or from the first match in the file.

  • Values are parsed as floats; non-numeric tokens in data lines are ignored rather than raising.

  • The writer orders known options first in the header line, then appends extra options sorted by key. Both option keys and values are written in upper case.

  • Line formatting uses the per-line and float format defaults from Base unless you provide explicit overrides.

See also

SpectraSECT

Header container for spectra sections.

TSIO

Time-series counterpart for >TSERIES.

Examples

Read all spectra blocks:

>>> io = SpectraIO.from_file("site.edi")
>>> len(io.blocks)
128
>>> b0 = io.blocks[0]
>>> b0.freq, b0.bw
(..., ...)

Build and serialize blocks:

>>> from pycsamt.seg.spectra import _SpectraBlock
>>> io = SpectraIO()
>>> blk = _SpectraBlock()
>>> blk.freq = 10.0
>>> blk.rotspec = 1
>>> blk.values = [0.1, 0.2, 0.3]
>>> io.blocks.append(blk)
>>> lines = io.write(per_line=2, float_fmt="{: .3E}")
>>> print("".join(lines).strip())
>SPECTRA FREQ=10.0 ROTSPEC=1 // 3
  1.000E-01  2.000E-01
  3.000E-01

References

[SpectraIO-1]

SEG EDI standard, “Spectra Data Sections”.

blocks: list[_SpectraBlock]#
classmethod from_file(edi_path, start_line=None)#
Parameters:
  • edi_path (str)

  • start_line (int | None)

Return type:

SpectraIO

write(per_line=None, float_fmt=None)#
Parameters:
  • per_line (int | None)

  • float_fmt (str | None)

Return type:

list[str]

exception pycsamt.seg.SpectraValidationWarning#

Bases: UserWarning

Warning emitted for recoverable EDI SPECTRA inconsistencies.

class pycsamt.seg.TimeSeries(name=None, *, verbose=0)#

Bases: EMBase

Container for >TSERIES data aggregated by channel.

The class groups samples by channel ID and keeps a per-channel sampling interval. It is a light facade built on top of TSect and TSIO.

Parameters:
  • name (str, optional) – Display name forwarded to BaseEM.

  • verbose (int, default 0) – Verbosity level, forwarded to BaseEM.

Variables:
  • ids (list of str) – Ordered channel identifiers (e.g. ["HX","HY"]).

  • data (dict[str, ndarray]) – Mapping channel -> 1-D samples. Each array has length equal to the concatenation of all blocks that belong to that channel, in file order.

  • dt_map (dict[str, float]) – Mapping channel -> dt (seconds). When a block has no DT option, TSect.dt is used as a fallback. If neither is present, 1.0 is used in time().

  • npts_map (dict[str, int]) – Mapping channel -> number of samples accumulated across all blocks.

  • extra_blocks (list of dict) – Optional raw per-block metadata preserved for round- tripping or vendor-specific fields.

Notes

The class is designed for two common workflows:

  1. Build from parsed IO. Use from_io() with a header (TSect) and a data stream (TSIO). The constructor performs channel discovery, concatenation, and dt assignment.

  2. Write back to EDI. Use to_io() to obtain a fresh (TSect, TSIO) pair. One block per channel is emitted, using the accumulated data and the final dt chosen for that channel.

Channel order is stable and follows first appearance in the input stream. The time() vector is computed as np.arange(N) * dt for the requested channel.

channels()#

Return the ordered list of channel identifiers.

Return type:

list[str]

get(cid)#

Return the 1-D sample array for channel cid.

Parameters:

cid (str)

Return type:

ndarray

time(cid)#

Return a 1-D time vector using dt_map[cid] or the agreed fallback.

Parameters:

cid (str)

Return type:

ndarray

from_io(sect, io, empty=None) : classmethod

Build a TimeSeries from parsed header and IO.

to_io()#

Serialize the current state to (TSect, TSIO) for writing.

Return type:

tuple[TSect, TSIO]

align(ids=None, fill=0.0)#

Right-pad channels to the same length and return a 2-D array (nmax, nch) and the channel order.

Parameters:
Return type:

tuple[ndarray, list[str]]

Examples

Build from blocks and compute a time vector:

from pycsamt.seg.time_series import TSect, TSIO
from pycsamt.seg.time_series import TimeSeries

sect = TSect(sectid="TS", dt=0.25)
io = TSIO()  # filled elsewhere

ts = TimeSeries.from_io(sect, io)
hx = ts.get("HX")
t = ts.time("HX")

Round-trip to EDI blocks:

sect2, io2 = ts.to_io()
# pass sect2.write() and io2.write() to your writer

See also

pycsamt.seg.time_series.TSect

Header parser for >=TSERIESSECT.

pycsamt.seg.time_series.TSIO

Reader/writer for >TSERIES blocks.

References

[TimeSeries-1]

SEG EDI MT/EMAP standard (1987). MTNet. https://www.mtnet.info/docs/seg_mt_emap_1987.pdf

ids: list[str]#
data: dict[str, ndarray]#
dt_map: dict[str, float]#
npts_map: dict[str, int]#
extra_blocks: list[dict[str, object]]#
channels()#
Return type:

list[str]

get(cid)#
Parameters:

cid (str)

Return type:

ndarray

time(cid)#
Parameters:

cid (str)

Return type:

ndarray

classmethod from_io(sect, io, *, empty=None)#
Parameters:
Return type:

TimeSeries

to_io()#
Return type:

tuple[TSect, TSIO]

align(ids=None, *, fill=0.0)#
Parameters:
Return type:

tuple[ndarray, list[str]]

class pycsamt.seg.TSect(*args, verbose=0, logger=None, **kws)#

Bases: EDIComponentBase

Minimal container for the >=TSERIESSECT header block. It parses the section header and the ordered list of measurement IDs that follow the header. The class keeps a pointer to where the first >TSERIES data block starts so downstream readers can jump straight to the data.

Parameters:
  • verbose (int or bool, optional) – Verbosity flag inherited from EDIComponentBase.

  • logger (object, optional) – Logger instance inherited from EDIComponentBase.

  • **kws – Keyword overrides for any public attribute. Unknown keys are ignored.

  • args (Any)

Variables:
  • sectid (str or None) – Section identifier. If absent in file it remains None.

  • nchan (int or None) – Number of channels declared in the header.

  • nmeas (int or None) – Number of measurements declared in the header.

  • npts (int or None) – Number of samples per trace if provided.

  • maxblks (int or None) – Hint for the maximum number of data blocks.

  • dt (float or None) – Sampling interval in seconds when present.

  • meas_ids (list of str) – Ordered list of measurement IDs collected from the header tail. One ID per line.

  • extra (dict) – Any non standard key–value options preserved as strings.

  • start_data_lines_num (int or None) – Absolute line index where the first >TSERIES block begins. Useful for fast data scans.

from_file(edi_path)#

Parse a single >=TSERIESSECT from an EDI file. The method validates the file structure with validation.IsEdi._assert_edi() before parsing.

Parameters:

edi_path (str)

Return type:

TSect

write()#

Serialize the section back to EDI lines including the measurement ID list.

Return type:

list[str]

Notes

Parsing is tolerant. Unknown keys are stored in extra. Blank lines and comment lines beginning with // are ignored. If multiple time-series sections exist, call from_file() on the desired file view or use a higher level iterator to locate the right header first.

Examples

>>> sect = TSect.from_file("sound.edi")
>>> sect.nchan, sect.dt
(3, 0.01)
>>> print("IDs:", sect.meas_ids[:2])
IDs: ['HX', 'HY']

See also

TSIO

Reader and writer for >TSERIES data blocks.

validation.IsEdi

Lightweight EDI file validator used during reading.

References

[TSect-1]

SEG EDI MT/EMAP standard (1987). MTNet. https://www.mtnet.info/docs/seg_mt_emap_1987.pdf

KEY_ORDER: list[str] = ['sectid', 'nchan', 'nmeas', 'npts', 'maxblks', 'dt']#
sectid: str | None#
nchan: int | None#
nmeas: int | None#
npts: int | None#
maxblks: int | None#
dt: float | None#
meas_ids: list[str]#
extra: dict[str, Any]#
start_data_lines_num: int | None#
classmethod from_file(edi_path)#
Parameters:

edi_path (str)

Return type:

TSect

write()#
Return type:

list[str]

class pycsamt.seg.TSIO(*args, verbose=0, logger=None, **kws)#

Bases: EDIComponentBase

Reader and writer for >TSERIES data blocks. Each data block line starts with a flexible option list (e.g. ID=HX NPTS=4 DT=0.25) followed by a // N hint and then one or more lines of numeric samples.

Parameters:
  • verbose (int or bool, optional) – Verbosity flag inherited from EDIComponentBase.

  • logger (object, optional) – Logger instance inherited from EDIComponentBase.

  • **kws – Keyword overrides for public attributes.

  • args (Any)

Variables:

blocks (list of _TSBlock) –

Parsed time-series blocks in file order. Every block exposes:

  • options : dict of parsed header options.

  • nvals_hint : int or None from the // count.

  • values : list[float] of samples.

  • id : str or None (alias of options['id']).

  • npts : int or None (alias of options['npts']).

  • dt : float or None (alias of options['dt']).

from_file(edi_path, start_line=None, \*, verbose=0, logger=None)#

Parse all >TSERIES blocks starting at start_line. If start_line is None the first block is located automatically. The method assumes the file already passed validation.IsEdi._assert_edi() upstream.

Parameters:
Return type:

TSIO

write(per_line=None, float_fmt=None)#

Serialize every block. per_line controls how many samples are printed per line. float_fmt controls the numeric format (e.g. "{: .6E}").

Parameters:
  • per_line (int | None)

  • float_fmt (str | None)

Return type:

list[str]

Notes

Header options are typed heuristically. Integer-like tokens become integers. Otherwise they are parsed as floats when possible, and finally left as strings. The common aliases id, npts and dt are mirrored onto block fields for convenience.

Examples

>>> sect = TSect.from_file("sound.edi")
>>> io = TSIO.from_file("sound.edi", start_line=sect.start_data_lines_num)
>>> len(io.blocks)
2
>>> io.blocks[0].id, io.blocks[0].dt
('HX', 0.25)
>>> lines = io.write(per_line=5, float_fmt="{: .3E}")
>>> print("".join(lines).splitlines()[0])
>TSERIES ID=HX NPTS=4 DT=0.25 // 4

See also

TSect

Header reader for >=TSERIESSECT.

SpectraIO

Similar reader for >SPECTRA blocks.

References

[TSIO-1]

SEG EDI MT/EMAP standard (1987). MTNet. https://www.mtnet.info/docs/seg_mt_emap_1987.pdf

blocks: list[_TSBlock]#
classmethod from_file(edi_path, start_line=None, *, verbose=0, logger=None)#
Parameters:
Return type:

TSIO

write(per_line=None, float_fmt=None)#
Parameters:
  • per_line (int | None)

  • float_fmt (str | None)

Return type:

list[str]

class pycsamt.seg.OtherSECT(*args, verbose=0, logger=None, **kws)#

Bases: EDIComponentBase

Header container for >=OTHERSECT blocks.

This lightweight container parses the header that opens an Other Data Section as defined by the SEG-EDI spec. It collects canonical options, any extra key/value pairs, and the ordered list of measurement IDs that may follow the options list.

Parameters:
  • *args (Any) – Unused positional arguments. Present for MRO safety.

  • verbose (int or bool, optional) – Verbosity level. Propagated by EDIComponentBase.

  • logger (object, optional) – Logger instance. If None, a null logger is used.

  • **kws – Field overrides to pre-populate attributes.

Variables:
  • sectid (str or None) – Section identifier. Often mirrors DATAID.

  • nchan (int or None) – Number of channels in this section.

  • nfreq (int or None) – Number of frequencies, if provided.

  • maxblks (int or None) – Upper bound on the number of data blocks.

  • ndipole (int or None) – EMAP-style dipole count, if present.

  • type (str or None) – Free-form type tag found in the wild (e.g. FREE).

  • extra (dict) – Any unrecognized header keys are stored here.

  • meas_ids (list of str) – Measurement IDs listed under the header.

  • start_data_lines_num (int or None) – Absolute line index where the first >BLOCK begins.

from_file(path)#

Parse the first >=OTHERSECT in an EDI file. The file is validated by IsEdi.

Parameters:

edi_path (str)

Return type:

OtherSECT

write()#

Serialize the header back to EDI text lines.

Return type:

list[str]

Notes

Unknown header keys are preserved in extra so round-tripping does not lose information. Measurement IDs are appended in the order they appear in the file.

Examples

>>> hdr = OtherSECT.from_file("site.edi")
>>> hdr.sectid
'B1'
>>> hdr.meas_ids[:2]
['HX', 'HY']
>>> lines = hdr.write()
>>> print("".join(lines).splitlines()[0])
>=OTHERSECT

See also

OtherIO

Read and write generic >BLOCK data.

OtherMixin

Convenience helpers for host classes.

References

[OtherSECT-1]

SEG EDI Standard (MT/EMAP), 1987. MTNet archive.

KEY_ORDER: list[str] = ['sectid', 'nchan', 'nfreq', 'maxblks', 'ndipole', 'type']#
sectid: str | None#
nchan: int | None#
nfreq: int | None#
maxblks: int | None#
ndipole: int | None#
type: str | None#
extra: dict[str, Any]#
meas_ids: list[str]#
start_data_lines_num: int | None#
classmethod from_file(edi_path)#
Parameters:

edi_path (str)

Return type:

OtherSECT

write()#
Return type:

list[str]

class pycsamt.seg.OtherIO(*args, verbose=0, logger=None, **kws)#

Bases: EDIComponentBase

Reader/writer for generic >BLOCK data under OTHERSECT.

The class iterates over consecutive >KEY data blocks that follow an >=OTHERSECT header, and stores each as a small record. Numeric rows are collected into _OtherBlock.values. Non-numeric rows are kept in _OtherBlock.raw_lines to preserve content that cannot be parsed as floats.

Parameters:
  • *args (Any) – Unused positional arguments. Present for MRO safety.

  • verbose (int or bool, optional) – Verbosity level. Propagated by EDIComponentBase.

  • logger (object, optional) – Logger instance. If None, a null logger is used.

  • **kws – Field overrides to pre-populate attributes.

Variables:

blocks (list of _OtherBlock) – Parsed data blocks in file order.

from_file(path, start_line=None, verbose=0, logger=None)#

Parse all >BLOCK entries. If start_line is None, the reader seeks the first >OTHER or the line after >=OTHERSECT. Raises EdIDataError when no blocks are found.

Parameters:
Return type:

OtherIO

write()#

Serialize the parsed blocks to EDI text lines.

Return type:

list[str]

Notes

Typing strategy. Option values are parsed with a best-effort rule: integers first, then floats, else kept as strings. Numeric table rows are formatted using FLOAT_FMT and wrapped using PER_LINE.

Examples

>>> hdr = OtherSECT.from_file("site.edi")
>>> io = OtherIO.from_file("site.edi", start_line=hdr.start_data_lines_num)
>>> [b.keyword for b in io.blocks]
['>COH', '>ANNO']
>>> out = io.write()
>>> print(out[0].strip().split()[0])
>COH

See also

OtherSECT

Header that precedes the data blocks.

OtherMixin

Helper methods for host classes.

References

[OtherIO-1]

SEG EDI Standard (MT/EMAP), 1987. MTNet archive.

blocks: list[_OtherBlock]#
classmethod from_file(edi_path, start_line=None, *, verbose=0, logger=None)#
Parameters:
Return type:

OtherIO

write()#
Return type:

list[str]

class pycsamt.seg.IsEdi#

Bases: ABC

Abstract base for SEG-EDI validation helpers.

Subclasses implement IsEdi.is_valid. The static method _assert_edi() provides a robust, file-level validator that accepts either a path or an existing IsEdi instance. The check is heuristic, fast, and tolerant of minor formatting issues.

The validator recognizes three EDI families:

  1. Impedance-style files that contain a >FREQ block and a measurement section such as >=MTSECT, >=DEFINEMEAS, >=EMAPSECT or >=OTHERSECT.

  2. Spectra-style files that include >=SPECTRASECT and or at least one >SPECTRA block.

  3. Time-series files that include >=TSERIESSECT and or at least one >TSERIES block.

In addition, the first top-level tag must be >HEAD and the last must be >END. On failure the method raises a descriptive EdIDataError.

Notes

The check is structural rather than semantic. It does not validate numerical values or cross-block consistency. Files are read as text using utf-8-sig with errors="replace" to gracefully handle odd encodings.

Examples

Validate an EDI file path:

>>> from pycsamt.seg.validation import IsEdi
>>> IsEdi._assert_edi("path/to/site.edi")
True

Use with an object that implements is_valid:

>>> class MyEdi(IsEdi):
...     @property
...     def is_valid(self):
...         return True
>>> IsEdi._assert_edi(MyEdi())
True

See also

pycsamt.seg.mtemap.MTEMAP.from_file

Parse >=MTSECT / >=EMAPSECT headers.

pycsamt.seg.spectra.SpectraSECT.from_file

Parse >=SPECTRASECT headers.

pycsamt.seg.time_series.TSect.from_file

Parse >=TSERIESSECT headers.

References

[IsEdi-1]

SEG (1987). MT/EMAP EDI Format Standard. Society of Exploration Geophysicists. Available online: https://www.mtnet.info/docs/seg_mt_emap_1987.pdf

abstract property is_valid: bool#

Indicate whether the current instance represents a structurally valid EDI object.

This property is used by IsEdi._assert_edi() when a concrete IsEdi instance is provided instead of a file path.

Returns:

True when the instance is valid. Subclasses decide the exact criteria.

Return type:

bool

Notes

Implementations should be lightweight and side-effect free. Heavy validation belongs to dedicated readers (e.g., MTEMAP, Spectra, Time-Series parsers).

class pycsamt.seg.SurveyBase(*, verbose=0)#

Bases: object

Parameters:

verbose (int)

summary_dict()#
Return type:

dict[str, Any]

format_table(rows=None, *, cols=None, max_rows=24)#
Parameters:
Return type:

str

class pycsamt.seg.EDIProfile(items, *, verbose=0)#

Bases: SurveyBase

Profile helper for one or many EDIFile objects. Computes small-area geometry (easting/northing), cumulative distance along line, profile azimuth, and exposes utilities to adjust coordinates and push them back into EDI headers.

The class accepts a single file, an iterable of files, or an EDICollection. Coordinates are read from >HEAD and converted to working planar coordinates. For short lines the equirectangular approximation is used, and distances/azimuth are computed in that local frame.

Parameters:
  • items (EDIFile or iterable of EDIFile or EDICollection) – The input sites to include in the profile.

  • verbose (int, default 0) – Verbosity level forwarded to internal helpers.

Variables:
  • stations (list of str) – Station identifiers resolved from DATAID or file name.

  • lon (lat,) – Geographic coordinates (degrees).

  • elev (ndarray of float) – Elevations when present, missing values become 0.

  • distance (ndarray of float) – Cumulative distance from the first site (meters).

  • azimuth (float) – Bearing of the profile in degrees, clockwise from North, in [0, 360).

  • xy (tuple of ndarray) – Working (easting, northing) arrays in meters.

  • table (list of dict) – Row-wise view exposing station, lat, lon, elev, easting, northing, and UTM zone (when available).

get_bearing(method='endpoints')#

Compute bearing from either endpoints or a PCA-like fit of the track.

Parameters:

method (str)

Return type:

float | None

get_step()#

Return cumulative distance and cache it for reuse.

Parameters:
Return type:

float | ndarray

adjust(origin=None, azimuth=None, spacing=None, use_mean=True)#

Build an idealized straight profile and compute adjusted positions and lat/lon.

Parameters:
Return type:

EDIProfile

update(use_adjusted=True, update_elev=False)#

Write back adjusted coordinates to each site’s header.

Parameters:
  • use_adjusted (bool)

  • update_elev (bool)

Return type:

EDIProfile

plot_profile(use_adjusted=False, annotate=True, title=None)#

Plot elevation against along-profile distance.

Parameters:
Return type:

Axes

plot_track(use_adjusted=False, title=None)#

Plot plan-view easting/northing track.

Parameters:
  • ax (Axes | None)

  • use_adjusted (bool)

  • title (str | None)

Return type:

Axes

Notes

The small-area equirectangular frame is adequate for short profiles. For long lines or large latitude spans prefer a full projection workflow. When the UTM zone can be determined, adjusted coordinates are converted back to geographic using that zone.

Examples

Load two sites and compute azimuth and distance:

prof = EDIProfile([ed1, ed2])
print(float(prof.azimuth))
d = prof.distance

Adjust to a regular spacing and push back to headers:

prof.adjust(spacing=50.0).update()

See also

Stations

Tabular view of station metadata and projected coordinates.

Topography

Elevation profile builder with smoothing and trend tools.

References

[EDIProfile-1]

SEG EDI MT/EMAP standard (1987), MTNet. https://www.mtnet.info/docs/seg_mt_emap_1987.pdf

[EDIProfile-2]

Snyder, J. P. (1987). Map Projections – A Working Manual, USGS Prof. Paper 1395.

property stations: list[str]#
property lat: ndarray#
property lon: ndarray#
property elev: ndarray#
property xy: tuple[ndarray, ndarray]#
property distance: ndarray#
property azimuth: float#
get_bearing(*, method='endpoints')#

Estimate the survey bearing (azimuth) in degrees.

Parameters:

method ({‘endpoints’, ‘linear’}, default 'endpoints') – With 'endpoints' the azimuth is computed from the first to the last station. With 'linear' a best-fit axis is estimated by SVD of centered coordinates.

Returns:

Bearing in [0, 360) (clockwise from north), or None if fewer than two valid stations exist.

Return type:

float or None

Notes

Uses working projected coordinates (easting/northing), suitable for small-area profiles.

get_step(*, method='mean', as_array=False)#

Derive the inter-station spacing from the track.

Parameters:
  • method ({‘mean’, ‘median’}, default 'mean') – Aggregation used when returning a scalar spacing.

  • as_array (bool, default False) – If True, return the pairwise segment lengths as a 1-D array of size n-1. If False, return a single spacing computed with method.

Returns:

Either a scalar spacing or the per-segment distances.

Return type:

float or ndarray

Notes

Distances are computed from consecutive projected coordinates; missing stations are ignored.

adjust(*, origin=None, azimuth=None, spacing=None, step=None, use_mean=True)#

Build an idealized, straightened profile and store the adjusted coordinates.

Parameters:
  • origin (tuple(float, float), optional) – Reference (easting, northing) for the first station. Defaults to the first raw station.

  • azimuth (float, optional) – Bearing of the adjusted line in degrees. Defaults to get_bearing().

  • spacing (float, optional) – Fixed spacing between consecutive stations (meters).

  • step (float, optional) – Alias for spacing for convenience.

  • use_mean (bool, default True) – When both spacing and step are None, compute spacing from observed distances using mean if True or median if False.

Returns:

The instance (allows chaining).

Return type:

EDIProfile

Notes

Adjusted easting/northing are projected back to latitude and longitude using the dominant UTM zone of the track. Results are stored in _adj_e/_adj_n/_adj_lat/_adj_lon.

update(*, use_adjusted=True, update_elev=False)#

Push current coordinates back into the underlying EDI headers.

Parameters:
  • use_adjusted (bool, default True) – If True write adjusted lat/lon (from adjust()). If no adjusted coordinates exist, fall back to raw lat/lon.

  • update_elev (bool, default False) – Also write elevations when present in the profile table.

Returns:

The instance (allows chaining).

Return type:

EDIProfile

Notes

This mutates the in-memory EDIFile objects held by the profile; it does not write to disk.

plot_profile(*, ax=None, use_adjusted=False, annotate=True, title=None)#

Plot elevation versus along-profile distance.

Parameters:
  • ax (matplotlib.axes.Axes, optional) – Target axes. If omitted, a new figure/axes is made.

  • use_adjusted (bool, default False) – If True recompute distances from adjusted coordinates for the overlay. Raw elevation values are used in both cases.

  • annotate (bool, default True) – Draw station labels next to points.

  • title (str, optional) – Axes title.

Returns:

The axes with the profile plot.

Return type:

matplotlib.axes.Axes

Notes

Uses the profile’s cached cumulative distances. Call adjust() first to visualize an adjusted line.

plot_track(*, ax=None, use_adjusted=False, title=None)#

Plot plan-view station positions (easting vs. northing).

Parameters:
  • ax (matplotlib.axes.Axes, optional) – Target axes. If omitted, a new figure/axes is made.

  • use_adjusted (bool, default False) – Plot the straightened track if adjusted coordinates exist; otherwise plot raw positions.

  • title (str, optional) – Axes title.

Returns:

The axes with the track plot.

Return type:

matplotlib.axes.Axes

Notes

Axes aspect is set to equal for a faithful plan view.

as_table()#
Return type:

list[dict[str, object]]

class pycsamt.seg.Stations(items, *, verbose=0)#

Bases: SurveyBase

Lightweight table view for station metadata derived from EDIFile objects. Provides quick access to names, geographic coordinates, optional elevations, and working projected coordinates to support survey tasks.

Parameters:
  • items (EDIFile or iterable of EDIFile or EDICollection) – The sites to summarize.

  • verbose (int, default 0) – Verbosity level for logging.

Variables:
  • count (int) – Number of valid stations (rows with lat/lon).

  • stations (list of str) – Station identifiers sourced from headers or filenames.

table()#

Materialize the rows as a list of dicts with keys station, lat, lon, elev, e (east), n (north), zone and path.

Return type:

list[dict[str, object]]

to_dataframe()#

Convert the table to a pandas DataFrame when pandas is available.

Parameters:
Return type:

DataFrame

bounds()#

Geographic bounding box as (min_lat, min_lon, max_lat, max_lon).

select(keys=None, pattern=None, regex=None, pred=None)#

Return a filtered view. Multiple filters are combined with logical AND. See the method docstring for details.

Parameters:
Return type:

Stations

sort(by='station', reverse=False, inplace=True)#

Sort rows by a column (e.g. 'station', 'lat', 'lon', 'elev', 'e', 'n'). Returns this instance or a new view depending on inplace.

Parameters:
Return type:

Stations

offsets(origin=None, azimuth=None)#

Compute along-line and cross-line offsets in meters from projected coordinates. The profile axis is set by azimuth or inferred from endpoints.

Parameters:
Return type:

tuple[ndarray, ndarray]

set_coords(key, \*, lat=None, lon=None, elev=None)#

Update coordinates for a single station. When the backing EDIFile is available its >HEAD values are kept in sync.

Parameters:
Return type:

None

Notes

The class performs minimal validation. Rows missing lat/lon are skipped. Projected coordinates are intended for short-range work; for mapping at scale prefer the GIS utilities provided elsewhere in the package.

Examples

Build a table and print a compact view:

sts = Stations(coll)
for r in sts.table():
    print(r["station"], r["lat"], r["lon"])

Filter, sort, and compute offsets:

sel = sts.select(pattern="K*", pred=lambda r: r["elev"] > 800)
sel.sort(by="e")
along, across = sel.offsets()

See also

EDIProfile

Track-aware helper that computes distance and azimuth.

Topography

Produces elevation profiles from stations or profiles.

References

[Stations-1]

SEG EDI MT/EMAP standard (1987), MTNet. https://www.mtnet.info/docs/seg_mt_emap_1987.pdf

names()#
Return type:

list[str]

table()#
Return type:

list[dict[str, object]]

get(key)#
Parameters:

key (str)

Return type:

EDIFile | None

row(key)#
Parameters:

key (str)

Return type:

dict[str, object] | None

select(*, keys=None, pattern=None, regex=None, pred=None)#

Return a filtered view of the stations table.

Multiple filters are combined with logical AND. When no filter is given the original view is returned.

Parameters:
  • keys (sequence of str, optional) – Station identifiers to keep. Unknown ids are ignored.

  • pattern (str, optional) – Glob-like pattern matched against station ids (e.g. 'AB*'). Case-sensitive.

  • regex (str, optional) – Regular expression matched against station ids using re.search().

  • pred (callable, optional) – A predicate pred(row) -> bool evaluated on each row dict. Keep rows for which the predicate returns True.

Returns:

A new Stations view with rows that match the filters.

Return type:

Stations

Notes

Filtering does not modify the original container. Rows lacking a station id are always dropped.

Examples

Keep stations starting with 'K' and above 800 m:

sel = sts.select(pattern="K*", pred=lambda r: r["elev"] > 800)
sort(*, by='station', reverse=False, inplace=True)#

Sort the stations table by a column.

Parameters:
  • by (str, default 'station') – Column name to sort by (e.g. 'station', 'lat', 'lon', 'elev', 'e', 'n').

  • reverse (bool, default False) – If True sort in descending order.

  • inplace (bool, default True) – If True modify this instance and return it. Otherwise return a new sorted view.

Returns:

The sorted Stations object (self or a copy).

Return type:

Stations

Notes

Missing values are placed at the end. Unknown columns raise a KeyError.

offsets(*, origin=None, azimuth=None)#

Compute along-line and cross-line offsets (meters).

Offsets are computed from projected coordinates. The along-line axis is defined by the given azimuth; the cross-line axis is perpendicular to it.

Parameters:
  • origin (tuple of float, optional) – Reference point (easting, northing) in meters. Defaults to the first valid station.

  • azimuth (float, optional) – Bearing in degrees, clockwise from North. When omitted it is inferred from the first and last stations.

Returns:

  • along (ndarray of float) – Distances projected on the profile axis.

  • across (ndarray of float) – Signed distances perpendicular to the profile axis.

Return type:

tuple[ndarray, ndarray]

Notes

Rows without valid projected coordinates are skipped in the computation and do not contribute to the result.

set_coords(key, *, lat=None, lon=None, elev=None)#

Update coordinates for a single station.

Parameters:
  • key (str) – Station identifier to modify.

  • lat (float, optional) – New latitude in decimal degrees.

  • lon (float, optional) – New longitude in decimal degrees.

  • elev (float, optional) – New elevation in meters.

Return type:

None

Notes

The in-memory row is updated. If the backing EDIFile is attached for that station, its >HEAD values are also updated to keep them in sync. Unknown station ids raise a KeyError.

to_dataframe(*, columns=None, index='station', coerce_numeric=True)#

Return a pandas DataFrame view of the station table.

Parameters:
  • columns (sequence of str, optional) – Subset and ordering of columns to include. When omitted, a sensible default is used: ('station','lat','lon','elev','e','n','zone', 'path'). Missing names are ignored.

  • index (str or None, default 'station') – Column to set as the DataFrame index. If the name is not present, no index is set. Use None to leave the default RangeIndex.

  • coerce_numeric (bool, default True) – Try converting known numeric columns (lat, lon, elev, e, n) to numeric dtypes. Non convertible values become NaN.

Returns:

A DataFrame with one row per station.

Return type:

pandas.DataFrame

Notes

pandas is imported lazily. If it is not available, an ImportError is raised. The method is read-only and does not mutate the underlying table.

Examples

Basic usage:

df = Stations(coll).to_dataframe()
print(df.head())

Custom subset and index:

df = Stations(coll).to_dataframe(
    columns=("station", "elev", "e", "n"),
    index="station",
)

See also

table

List-of-dicts representation of the rows.

bounds

Geographic bounding box.

select

Filter rows prior to conversion.

class pycsamt.seg.Topography(items, *, use_profile_step=True, verbose=0)#

Bases: SurveyBase

Elevation profile helper. Builds paired arrays of distance and elevation from an EDIProfile, Stations, a collection, or raw EDIFile inputs. Includes smoothing, detrending, resampling, and quick plotting.

Parameters:
  • items (EDIProfile or Stations or EDICollection or EDIFile ) – or iterable of EDIFile Source of station positions and elevations. When an EDIProfile is given, along-line distances are reused by default.

  • use_profile_step (bool, default True) – If True and items is an EDIProfile, copy its along-profile distances; otherwise recompute distances from planar coordinates.

  • verbose (int, default 0) – Verbosity level for diagnostics.

Variables:
  • distance (ndarray of float) – Along-track distances in meters.

  • elevation (ndarray of float) – Elevation values aligned with distance.

  • trend (ndarray of float or None) – Fitted linear trend after detrend(). Otherwise None.

smooth(window=5, method='median')#

Apply moving median or mean smoothing to elevation.

Parameters:
Return type:

Topography

detrend()#

Remove a best-fit linear trend and keep it for plotting.

Return type:

Topography

resample(step)#

Resample to a fixed distance step using interpolation.

Parameters:

step (float)

Return type:

Topography

gradient(as_degrees=False)#

First derivative of elevation vs distance; optionally in degrees.

Parameters:

as_degrees (bool)

Return type:

ndarray

plot(ax=None, title=None, show_trend=True)#

Quick plot of elevation vs distance.

Parameters:
  • ax (Axes | None)

  • title (str | None)

  • show_trend (bool)

Return type:

Axes

as_arrays()#

Return (distance, elevation) copies.

Return type:

tuple[ndarray, ndarray]

to_dict()#

Return a dict with distance and elevation keys.

Return type:

dict[str, object]

Notes

If the input is an EDIProfile that has not yet computed distances, they are derived automatically. When distances or elevations are missing the result is empty.

Examples

From a profile and detrend before plotting:

topo = Topography(prof).detrend().smooth(window=7)
ax = topo.plot(title="Detrended topography")

From a list of files, resampled every 25 m:

topo = Topography(edis).resample(step=25.0)

See also

EDIProfile

Provides distances and azimuth and can adjust station positions.

Stations

Tabular access to station metadata.

References

[Topography-1]

SEG EDI MT/EMAP standard (1987), MTNet. https://www.mtnet.info/docs/seg_mt_emap_1987.pdf

property distance: ndarray#
property elevation: ndarray#
smooth(*, window=5, method='median')#

Smooth the elevation series with a sliding window.

Parameters:
  • window (int, default 5) – Window length (samples). Values <=1 skip smoothing.

  • method ({‘median’, ‘mean’}, default 'median') – Smoothing kernel. 'median' is robust to spikes; 'mean' uses a simple moving average.

Returns:

The instance (in place), allowing chaining.

Return type:

Topography

Notes

Edge handling is performed by shrinking the window near the bounds. This modifies the internal elevation array.

detrend()#
Return type:

Topography

resample(*, step)#

Resample distance/elevation to a fixed along-line step.

Parameters:

step (float) – Target spacing in meters for the resampled profile.

Returns:

The instance (in place), allowing chaining.

Return type:

Topography

Notes

Distances are regridded on [dmin, dmax] with uniform spacing and elevations are linearly interpolated.

gradient(*, as_degrees=False)#

Compute local slope between consecutive samples.

Parameters:

as_degrees (bool, default False) – If True, return the slope angle in degrees. If False, return the rise-over-run ratio.

Returns:

Array of length len(distance) - 1 with per-segment slopes (or angles when requested).

Return type:

ndarray

plot(*, ax=None, title=None, show_trend=True)#

Plot elevation versus distance.

Parameters:
  • ax (matplotlib.axes.Axes, optional) – Target axes. A new figure/axes is created when omitted.

  • title (str, optional) – Title for the axes.

  • show_trend (bool, default True) – Overlay the last computed trend line (from detrend()) when available.

Returns:

The axes with the rendered profile.

Return type:

matplotlib.axes.Axes

as_arrays()#
Return type:

tuple[ndarray, ndarray]

to_dict()#
Return type:

dict[str, object]

class pycsamt.seg.XAMixin#

Bases: object

A mixin that adds convenient xarray exports to collection classes.

This mixin provides a bridge between collection-like objects (such as EDICollection) and the powerful, multi-dimensional data structures offered by the xarray library. Any class that is iterable over EDIFile instances can inherit from this mixin to gain methods for data conversion and metadata extraction.

to_xarray(drop_empty=True)#

Converts the entire collection into a single, comprehensive xarray.Dataset. This method leverages build_dataset() to handle the conversion and concatenation of multiple EDI files.

Parameters:

drop_empty (bool)

Return type:

Dataset

meta_table()#

Extracts only the site-level metadata (e.g., coordinates, filenames, data quality flags) from the collection and returns it as a clean, tabular xarray.Dataset, omitting the bulky transfer function data.

Return type:

Dataset

Notes

  • This mixin is designed to be lightweight and does not impose any specific storage or indexing strategy on the host class; it only requires that the host class implements the __iter__ method to yield EDIFile objects.

  • The site identifiers used in the resulting datasets follow the same robust inference rules as build_dataset().

See also

build_dataset

The core function that performs the conversion.

EDICollection

A primary user of this mixin.

EDIAcc

The accessor for interacting with the created dataset.

Examples

To use this mixin, simply inherit from it in your collection class.

>>> from pycsamt.seg.edi import EDIFile
>>> class MyEDICollection(XAMixin):
...     def __init__(self, items):
...         self._items = list(items)
...
...     def __iter__(self):
...         return iter(self._items)
>>> # Assume "site1.edi" and "site2.edi" exist
>>> edi_files = [
...     EDIFile("data/edis/S01.edi"),
...     EDIFile("data/edis/S02.edi"),
... ]
>>> collection = MyEDICollection(edi_files)
>>>
>>> # Convert the entire collection to an xarray Dataset
>>> ds = collection.to_xarray()
>>> print(ds.site.values)
['S01' 'S02']
>>>
>>> # Get a summary table of just the metadata
>>> metadata_ds = collection.meta_table()
>>> print(metadata_ds[["lat", "lon"]])
<xarray.Dataset>
Dimensions:  (site: 2)
Coordinates:
  * site     (site) object 'S01' 'S02'
Data variables:
    lat      (site) float64 26.05 26.05
    lon      (site) float64 -10.33 -10.33
to_xarray(*, drop_empty=True)#
Parameters:

drop_empty (bool)

Return type:

Dataset

meta_table()#

Extracts site-level metadata into a new Dataset.

Return type:

Dataset

pycsamt.seg.build_dataset(edis, *, drop_empty=True)#

Build a multi-site xarray Dataset from an iterable of EDIFile.

This function iterates through a collection of parsed EDIFile objects, converts each one into a single-site xarray.Dataset, and then concatenates them into a unified, multi-site dataset.

Parameters:
  • edis (Iterable of EDIFile) – An iterable (e.g., a list or a EDICollection) of parsed EDI objects.

  • drop_empty (bool, default=True) – If True, any EDIFile object that contains no frequency data will be skipped and excluded from the final dataset.

Returns:

A single dataset containing data from all valid EDI files. The dataset is indexed by a site dimension, and site-specific metadata (latitude, longitude, etc.) are stored as non-dimensional coordinates aligned with this dimension.

Return type:

xr.Dataset

Notes

The resulting dataset is structured with dimensions for sites, frequencies, and tensor components. This structure is ideal for vectorized computations and advanced plotting across multiple sites.

This function correctly handles site-specific metadata by assigning it to coordinates, preventing data loss during concatenation, which is a common pitfall when storing metadata in global attributes.

See also

EDICollection.to_xarray

A convenient wrapper around this function.

EDIFile

The per-item reader that provides the source data.

EDIAcc

An accessor for interacting with the created dataset.

Examples

>>> from pycsamt.seg import EDICollection, build_dataset
>>> # Create a collection of EDI files
>>> edi_collection = EDICollection.from_sources("data/edis/")
>>> # Build the xarray dataset
>>> ds = build_dataset(edi_collection)
>>> print(ds)
<xarray.Dataset>
Dimensions:      (site: 2, freq: 60, ...)
Coordinates:
  * site         (site) object 'S01' 'S02'
  * freq         (freq) float64 320.0 286.9 ...
    ...
    lat          (site) float64 26.05 26.05
    lon          (site) float64 -10.33 -10.33
Data variables:
    z            (site, freq, output_ch, input_ch) complex128 ...
    z_err        (site, freq, output_ch, input_ch) float64 ...
    ...
class pycsamt.seg.EDIAcc(ds)#

Bases: object

An xarray accessor for convenient interaction with EDI datasets.

This accessor is registered under the .edi namespace and provides domain-specific methods and properties for datasets created by build_dataset(). It simplifies common data selection and visualization tasks that are specific to MT/EM (magnetotelluric/electromagnetic) data.

2.9. Properties#

stationslist[str]

A list of all unique station or site names present in the dataset’s site coordinate.

get(site)#

Selects and returns a new xarray.Dataset containing data for only a single site, specified by its name. The selection is case-insensitive.

Parameters:

site (str)

Return type:

Dataset

band(fmin=None, fmax=None)#

Filters the dataset to a specific frequency range. Returns a new dataset containing only the data within the inclusive frequency bounds.

Parameters:
Return type:

Dataset

plot_apparent_resistivity(site, \*\*kwargs)#

Generates a standard plot of apparent resistivity and phase curves for the off-diagonal tensor components (XY and YX) of a specified site.

Parameters:
attrs()#

Returns a dictionary of the dataset’s global attributes.

Return type:

dict[str, object]

See also

build_dataset

The function used to create datasets compatible with this accessor.

Examples

>>> from pycsamt.seg import EDICollection, build_dataset
>>> edi_collection = EDICollection.from_sources("data/edis/")
>>> ds = build_dataset(edi_collection)
>>>
>>> # Get a list of all station names
>>> print(ds.edi.stations)
['S01', 'S02', 'S03', ...]
>>>
>>> # Select data for a single station (case-insensitive)
>>> site_data = ds.edi.get("s01")
>>>
>>> # Filter the data to a specific frequency band (e.g., 1 to 100 Hz)
>>> filtered_ds = ds.edi.band(fmin=1.0, fmax=100.0)
>>>
>>> # Create a standard plot for a site
>>> fig, axes = ds.edi.plot_apparent_resistivity(site="S01")
>>> # fig.show() # Uncomment to display plot
property stations: list[str]#
get(site)#

Selects data for a single site (case-insensitive).

Parameters:

site (str)

Return type:

Dataset

plot_apparent_resistivity(site, components=None, phase_mod=None, figsize=(8, 8), show_grid=True, grid_props=None, savefig=None, **plot_kwargs)#

Generates a standard plot of apparent resistivity and phase.

This method provides a flexible interface for visualizing MT (magnetotelluric) data, allowing customization of components, phase wrapping, and plot aesthetics.

Parameters:
  • site (str) – The site identifier to plot.

  • components (list of str, default=["xy", "yx"]) – A list of tensor components to plot (e.g., “xy”, “yx”, “xx”). The selection is case-insensitive.

  • phase_mod (int, optional) – If provided, wraps the phase to a specific quadrant. For example, phase_mod=90 will display phases in the [0, 90] degree range, useful for visualizing data in a single quadrant.

  • figsize (tuple[int, int], default=(8, 6)) – The figure size for the plot.

  • show_grid (bool, default=True) – Whether to display a grid on both subplots.

  • grid_props (dict, optional) – Additional properties to customize the grid lines (e.g., {'color': 'grey', 'linestyle': '--', 'linewidth': 0.5}).

  • savefig (str, optional) – If a path is provided, the plot will be saved to that file.

  • **plot_kwargs – Additional keyword arguments passed directly to xarray’s .plot.line() method for customizing the lines.

Returns:

  • fig (matplotlib.figure.Figure) – The matplotlib Figure object.

  • axes (np.ndarray of matplotlib.axes.Axes) – An array containing the two subplot Axes objects.

Examples

>>> # Basic plot of off-diagonal components
>>> fig, axes = ds.edi.plot_apparent_resistivity(site="S01")
>>> # fig.show()
>>> # Plot all components and save the figure
>>> fig, axes = ds.edi.plot_apparent_resistivity(
...     site="S01",
...     components=["xy", "yx", "xx", "yy"],
...     savefig="S01_all_components.png",
... )
>>> # Plot with phase wrapped to the first quadrant and custom styling
>>> fig, axes = ds.edi.plot_apparent_resistivity(
...     site="S01",
...     phase_mod=90,
...     grid_props={"color": "red", "linestyle": ":"},
...     marker="o",  # passed to plot.line
... )
attrs()#

Returns the global attributes of the Dataset.

Return type:

dict[str, object]

band(fmin=None, fmax=None)#
Parameters:
Return type:

Dataset

has_spectra()#
Return type:

bool

spectra()#
Return type:

Dataset

has_timeseries()#
Return type:

bool

timeseries()#
Return type:

Dataset

Parameters:

ds (xr.Dataset)

2.9.1. SEG Modules#

pycsamt.seg.base

Base classes for SEG-EDI objects and components.

pycsamt.seg.cbase

pycsamt.seg.collection

pycsamt.seg.components

pycsamt.seg.edi

pycsamt.seg.heads

pycsamt.seg.meas

pycsamt.seg.mtemap

pycsamt.seg.ops

pycsamt.seg.other

pycsamt.seg.property

SEG-EDI metadata.

pycsamt.seg.schema

SEG-EDI schema (keywords, options, ordering).

pycsamt.seg.sections

pycsamt.seg.spectra

pycsamt.seg.survey

pycsamt.seg.time_series

pycsamt.seg.utils

SEG-EDI helpers: minimal parsing and serialization utilities.

pycsamt.seg.validation

SEG-EDI validators (no base-class deps).

pycsamt.seg.xa