pycsamt.seg.utils#

SEG-EDI helpers: minimal parsing and serialization utilities.

Utility helpers shared by pycsamt.seg sub-modules. The public API is intentionally small – only helpers that are useful outside the core package are exposed.

Functions

gather_measurement_key_value_with_str_parser(lines)

Parse measurement definitions from lines.

minimum_parser_to_write_edi(obj)

Write a minimal, valid SEG-EDI text from a structured mapping.

parse_kv_pairs(s)

Parse KEY=VALUE pairs from a single line.

quick_edi_stats(*, total, ok[, label, width])

One-line recap similar to legacy show_stats.

show_edi_stats(collected, succeeded, *[, ...])

Pretty print collection statistics.

sort_edis_by_location(edi_objs, *[, by, ...])

Order EDI sites according to geographic criteria.

strip_item(item_to_clean[, item, multi_space])

Strip a token repeatedly from both ends of strings.

pycsamt.seg.utils.sort_edis_by_location(edi_objs, *, by='index', method='strict', metric='cartesian', reference=None, verbose=0)[source]#

Order EDI sites according to geographic criteria.

Parameters:
  • edi_objs (Iterable[_Edi]) – Iterable of Edi objects.

  • by (str) –

    Sorting key:

    • "ll" or "lonlat" – ascending lon then lat;

    • "latlon" – ascending lat then lon;

    • "distance" – distance from reference;

    • "name" – basename of .edi file;

    • "dataid":pyattr:`~pycsamt.seg.heads.Head.dataid`;

    • "index" – first integer found in the filename (default, mimics legacy behaviour).

  • method (str) – "strict" (default) uses the selected key only. "naive" falls back to filename order when information is ambiguous (identical coordinates).

  • metric (str) – Distance metric when by is "distance" – one of "cartesian" or "haversine".

  • reference (tuple[float, float] | None) – (lat, lon) of the origin for distance sorting. When None the first site becomes the origin.

  • verbose (int) – 0 → silent, ≥1 prints a quick recap through :pyfunc:`~pycsamt.seg.stats.quick_edi_stats`.

Returns:

edi_sorted is a (N, ) numpy array of EDI objects; names the corresponding filenames (without directory).

Return type:

edi_sorted, names

Notes

The function never mutates the input list.

Examples

>>> from pycsamt.seg.utils import sort_edis_by_location
>>> edis, names = sort_edis_by_location(my_edi_list, by="lonlat")
pycsamt.seg.utils.parse_kv_pairs(s)[source]#

Parse KEY=VALUE pairs from a single line.

Handles quoted strings, numeric literals (including Fortran ‘D’ exponents), and D:M:S lat/long. Returns a dictionary with best-effort typing.

Examples

>>> parse_kv_pairs('ID=10011.001 CHTYPE=HX X=0.0 Y=0.0 AZM=0')
{'ID': 10011.001, 'CHTYPE': 'HX', 'X': 0.0, 'Y': 0.0, 'AZM': 0}
Parameters:

s (str)

Return type:

dict[str, Any]

pycsamt.seg.utils.gather_measurement_key_value_with_str_parser(lines)[source]#

Parse measurement definitions from lines.

This recognizes blocks like:

>HMEAS ID=... CHTYPE=HX X=... Y=... Z=... AZM=...
>EMEAS ID=... CHTYPE=EX X=... Y=... X2=... Y2=...

and returns a list of dicts with a mandatory "KIND" key ("HMEAS" or "EMEAS") plus all parsed key/value pairs.

Parameters:

lines (iterable of str) – Raw lines from the EDI file.

Returns:

Each dict contains "KIND" and the parsed keys.

Return type:

list of dict

Notes

  • DMS coordinates (e.g. 26:35:11.0) are converted to decimal degrees.

  • Quoted strings are unquoted.

  • Numeric tokens (including Fortran D exponents) are converted to numbers.

Examples

>>> src = [
...   '>HMEAS ID=10011.001 CHTYPE=HX X=0.0 Y=0.0 AZM=0',
...   '>EMEAS ID=10014.001 CHTYPE=EX X=-10.0 Y=0.0 X2=10.0 Y2=0.0',
... ]
>>> gather_measurement_key_value_with_str_parser(src)[0]["CHTYPE"]
'HX'
pycsamt.seg.utils.quick_edi_stats(*, total, ok, label='EDI', width=None)[source]#

One-line recap similar to legacy show_stats.

Parameters:
Return type:

None

pycsamt.seg.utils.minimum_parser_to_write_edi(obj)[source]#

Write a minimal, valid SEG-EDI text from a structured mapping.

This is deliberately small and conservative; it supports the headers and blocks present in your examples and is intended as the last step of the pipeline (the higher-level Edi class should prepare this structure).

Expected structure (keys are optional unless noted):

  • head (mapping):

    Keys such as DATAID, ACQBY, ACQDATE, FILEDATE, PROSPECT, LAT, LONG, ELEV, STDVERS, PROGVERS, PROGDATE, MAXSECT, EMPTY. Values are serialized with KEY=VALUE (quoted if needed).

  • info (str or list[str]): free text under >INFO.

  • definemeas (mapping):

    Keys like MAXCHAN, MAXRUN, MAXMEAS, UNITS, REFTYPE, REFLAT, REFLONG, REFELEV.

  • measurements (list[dict]): entries produced by

    gather_measurement_key_value_with_str_parser(). Each must include "KIND".

  • mtsect (mapping):

    Must include SECTID (str) and NFREQ (int). May include channel bindings (HX, HY, HZ, EX, EY, RX, RY).

  • freq (1-D array-like of float): frequency list.

  • zrot (1-D array-like of float): rotation angles.

  • impedance (mapping): optional numeric blocks with any of

    the standard keys (ZXXR, ZXXI, ZXX.VAR, …).

  • resistivity (mapping): optional RHOXY, RHOXY.ERR,

    RHOYX, RHOYX.ERR, PHSXY, PHSXY.ERR, PHSYX, PHSYX.ERR.

  • coherence (list[tuple[str, Sequence[float]]]): optional

    pairs of a labelled preamble (e.g. 'COH MEAS1=1 MEAS2=5 ROT=NORTH') and the numeric block.

Parameters:

obj (mapping) – Structured content as described above.

Returns:

Text of the EDI file.

Return type:

str

Notes

  • Numeric blocks are formatted at 6 values per line using scientific notation.

  • If counts are present, // N is emitted; otherwise they are inferred from the array length.

  • The function does not touch data ordering; it is up to the caller to ensure freq, zrot and component arrays are aligned.

Examples

>>> edi_text = minimum_parser_to_write_edi({
...   "head": {"DATAID": "E1_2", "STDVERS": "SEG 1.0", "EMPTY": 1e32},
...   "info": "Processed by pyCSAMT",
...   "definemeas": {"MAXCHAN": 16, "UNITS": "M"},
...   "measurements": [{"KIND": "HMEAS", "ID": 1.001, "CHTYPE": "HX"}],
...   "mtsect": {"SECTID": "E1_2", "NFREQ": 2, "HX": 1.001},
...   "freq": [7e4, 5.88e4],
...   "zrot": [0.0, 0.0],
... })
>>> edi_text.startswith(">HEAD")
True
pycsamt.seg.utils.strip_item(item_to_clean, item=None, multi_space=12)[source]#

Strip a token repeatedly from both ends of strings.

This utility cleans leading/trailing repetitions of a token (default: whitespace) in a flexible way. It accepts a scalar string, a list of strings, or a NumPy array of strings and returns the same container type.

If the input (or all items within) becomes empty after sanitization, None is returned and a warning is issued. This mirrors the legacy behavior where completely blank values are treated as missing.

Parameters:
  • item_to_clean ({str, list of str, np.ndarray of str}, optional) – The text or collection of texts to sanitize. If None, returns None.

  • item (str, optional) – The token to strip from both ends. If None or a blank string, standard whitespace stripping (str.strip()) is used. For multi-character tokens (e.g., "//"), the token is removed as repeated whole substrings rather than character sets.

  • multi_space (int, default=12) – Maximum repetition count to consider at each end for a multi-character token (upper bound in the regex quantifier). Must be a positive integer.

Returns:

Sanitized output preserving the input container type, or None if content is effectively empty after cleaning.

Return type:

{str, list of str, np.ndarray of str} or None

Notes

  • For item that is None/blank, this function applies str.strip() (whitespace). For a non-blank item, it removes repeated occurrences of that exact token from both ends using a compiled regular expression anchored at the start and end of the string.

  • Returning None for fully empty results keeps backward compatibility with legacy usage that treats blank fields as missing.

Examples

>>> strip_item("    ss_data   ")
'ss_data'
>>> strip_item(["  a  ", "   b"], item=None)
['a', 'b']
>>> arr = np.array(["////name////", "////x"], dtype="<U16")
>>> strip_item(arr, item="//")
array(['name', 'x'], dtype='<U16')
>>> strip_item(["      ", "   "]) is None
True
pycsamt.seg.utils.show_edi_stats(collected, succeeded, *, failed=None, elapsed=None, title='EDI', width=72, sep='~', stream=None)[source]#

Pretty print collection statistics.

Parameters:
  • collected (int | Iterable[Any]) – Number of items attempted or an iterable of those items (length will be taken).

  • succeeded (int | Iterable[Any]) – Number of items successfully processed or an iterable (length will be taken).

  • failed (int | Iterable[Any] | None) – Number of failed items or an iterable. If None it is inferred: failed = collected - succeeded.

  • optional – Number of failed items or an iterable. If None it is inferred: failed = collected - succeeded.

  • elapsed (float | None) – Wall-clock time in seconds.

  • optional – Wall-clock time in seconds.

  • title (str) – Short label for the object type ("EDI" by default).

  • width (int) – Total line width for the banner.

  • sep (str) – Character used to draw the banner.

  • stream (TextIO | None) – Output stream (defaults to :pydata:`sys.stdout`).

  • optional – Output stream (defaults to :pydata:`sys.stdout`).

Return type:

None