pycsamt.site.utils#
Functions
|
Apply a function to an object with optional in-place semantics. |
|
Coerce edic into an |
|
Convert degrees to milliradians. |
|
Indices where frequency values match targets within tolerance. |
|
Build an index selection from ranges, slices or scalars. |
|
Read latitude, longitude and elevation from an EDI header. |
|
Return the frequency vector from the impedance section. |
Detect whether x is an iterable collection of EDI objects. |
|
|
Heuristically detect an EDI file object. |
|
Return True if x looks like a filesystem path. |
|
Iterate over EDI files contained in edic. |
|
Case-insensitive station-name matcher with flexible patterns. |
|
Attempt a deep copy of x, falling back to identity. |
|
Convert milliradians to degrees. |
|
Collect EDI-like objects whose station names match a pattern. |
|
Write latitude, longitude and/or elevation into the header. |
|
Set the station name on an EDI-like object and its header. |
|
Return a best-effort station name for an EDI-like object. |
|
Wrap an azimuth angle in degrees to the half-open range [0, 360). |
- pycsamt.site.utils.is_pathlike(x)[source]#
Return True if x looks like a filesystem path.
- Parameters:
x (Any) – Object to test.
- Returns:
True when x is a str or pathlib.Path, otherwise False.
- Return type:
Notes
This helper is intentionally conservative. Containers that happen to be iterable but are not paths will return False.
Examples
>>> from pathlib import Path >>> from pycsamt.site.utils import is_pathlike >>> is_pathlike("data/line01.edi") True >>> is_pathlike(Path("data/line01.edi")) True >>> is_pathlike(["a", "b"]) False
See also
- pycsamt.site.utils.is_edi_file(x)[source]#
Heuristically detect an EDI file object.
- Parameters:
x (Any) – Object to test.
- Returns:
True if x exposes both
get_sectionand an attribute namedZ(the impedance section). Otherwise False.- Return type:
Notes
This check is duck-typed and does not import the backend class. It is compatible with multiple EDI implementations.
Examples
>>> class Dummy: ... def get_section(self, *_): ... ... Z = object() ... >>> from pycsamt.site.utils import is_edi_file >>> is_edi_file(Dummy()) True >>> is_edi_file(object()) False
See also
- pycsamt.site.utils.is_edi_collection(x)[source]#
Detect whether x is an iterable collection of EDI objects.
- Parameters:
x (Any) – Candidate collection. May be an EDICollection instance, or any iterable containing EDI-like objects.
- Returns:
True if x looks like a collection of EDI files.
- Return type:
Notes
The check is non-destructive. It peeks at the first element of the iterable to decide. Pathlike objects are not treated as iterables here, even though str is iterable.
Examples
>>> from pycsamt.site.utils import is_edi_collection, is_edi_file >>> class E: ... def get_section(self, *_): ... ... Z = object() ... >>> is_edi_collection([E(), E()]) True >>> is_edi_collection("folder/*.edi") False >>> is_edi_collection(None) False
- pycsamt.site.utils.iter_edifiles(edic)[source]#
Iterate over EDI files contained in edic.
- Parameters:
edic (Any) – Single EDI object or an iterable of EDI objects. Pathlike inputs are ignored (yield nothing).
- Yields:
pycsamt.seg.edi.EDIFile – Each detected EDI-like object from edic.
- Return type:
Notes
This is a safe iterator. Non EDI elements are skipped. A single EDI object will be yielded once.
Examples
>>> from pycsamt.site.utils import iter_edifiles >>> class E: ... def get_section(self, *_): ... ... Z = object() ... >>> list(iter_edifiles(E())) [<E object at ...>] >>> lst = [E(), object(), E()] >>> len(list(iter_edifiles(lst))) 2
See also
- pycsamt.site.utils.as_edicollection(edic, *, recursive=True, strict=False, on_dup='replace', verbose=0)[source]#
Coerce edic into an
EDICollectionwhen possible.- Parameters:
edic (Any) – Single/sequence of EDI objects, an existing EDICollection, or path-like (file/dir/glob) input.
recursive (bool, default
True) – Whenedicis path-like (or a sequence of), recurse into directories while discovering files.strict (bool, default
False) – IfTrueandedicis path-like, propagate read errors; otherwise errors are captured in the parser.on_dup ({‘replace’, ‘keep’}, default
'replace') – Duplicate policy during path-like discovery. SeeEDICollection.from_sources().verbose (int, default
0) – Verbosity forwarded to underlying readers.
- Returns:
A new collection containing the input items, or
Noneif nothing EDI-like was found.- Return type:
EDICollection or None
Notes
If edic is path-like → use
EDICollection.from_sources(...)().If edic is already an EDICollection → return it.
Else → collect EDI-like items via
iter_edifiles(edic)and build a collection.
Examples
>>> from pycsamt.site.utils import as_edicollection >>> coll = as_edicollection("data/*.edi", recursive=True) >>> coll is not None >>> >>> class E: ... def get_section(self, *_): ... ... Z = object() ... >>> coll = as_edicollection([E(), E()]) >>> coll is not None True >>> as_edicollection([]) is None True
See also
References
True
- pycsamt.site.utils.station_name(ed)[source]#
Return a best-effort station name for an EDI-like object.
The lookup order is:
Attribute
stationon the object itself (if truthy).Header field
dataid(via an internal head accessor).Fallback attributes on the object:
name,site, ordataid(first non-empty one).Empty string when nothing suitable is found.
- Parameters:
ed (Any) – EDI-like object exposing either a header section or plain attributes for station naming.
- Returns:
The resolved station name, possibly an empty string.
- Return type:
Notes
This helper is intentionally tolerant so it can work across different EDI backends. It prefers explicit
stationon the object, then the headerdataidwhich is commonly used as the unique site identifier.Examples
>>> from types import SimpleNamespace >>> from pycsamt.site.utils import station_name >>> ed = SimpleNamespace(station="E01") >>> station_name(ed) 'E01' >>> ed = SimpleNamespace(name="Alt01") >>> station_name(ed) 'Alt01'
See also
set_station_nameSynchronously update object and header names.
get_coordsRead latitude, longitude and elevation from header.
- pycsamt.site.utils.set_station_name(ed, name=None, *, station_id=None, policy=None, inplace=True)[source]#
Set the station name on an EDI-like object and its header.
This function updates multiple common identifiers so that the object-level name and the header
dataidstay in sync.- Parameters:
ed (Any) – EDI-like object.
name (str, optional) – New station name. If not provided, it is derived from
station_idor apolicycallable.station_id (str or int, optional) – Alternate identifier used to build the new name when
nameis not explicitly given.policy (Any, optional) – A callable or policy object that can convert an input identifier to a station string, e.g.
lambda s: s.upper().inplace (bool, default True) – If True, mutate ed in place; otherwise return a modified copy when possible.
- Returns:
The same object (when
inplace=True) or a best-effort copy with the new identifiers applied.- Return type:
Notes
Internally, the header object is created if missing. Both
ed.stationandhead.dataidare written when available. Errors from non-writable fields are suppressed.Examples
>>> from types import SimpleNamespace >>> from pycsamt.site.utils import set_station_name, station_name >>> ed = SimpleNamespace() >>> set_station_name(ed, name="X01") ... <...> >>> station_name(ed) 'X01'
See also
station_nameRead the current station name.
maybe_copyLightweight deep-copy helper used when not in place.
- pycsamt.site.utils.get_coords(ed)[source]#
Read latitude, longitude and elevation from an EDI header.
- Parameters:
ed (Any) – EDI-like object holding a header section.
- Returns:
A small tuple-like object with fields
lat,lonandelev. Missing values are returned asnan.- Return type:
_Coord
Notes
Both
lonand legacylongheader attribute names are supported. If the header is missing or fields cannot be read, all values arenan.Examples
>>> from types import SimpleNamespace >>> from pycsamt.site.utils import get_coords >>> head = SimpleNamespace(lat=10.0, long=20.0, elev=300.0) >>> ed = SimpleNamespace(get_section=lambda *_: head) >>> c = get_coords(ed) >>> (c.lat, c.lon, c.elev) (10.0, 20.0, 300.0)
See also
set_coordsWrite latitude, longitude and elevation to header.
- pycsamt.site.utils.set_coords(ed, *, lat=None, lon=None, elev=None, inplace=True)[source]#
Write latitude, longitude and/or elevation into the header.
- Parameters:
ed (Any) – EDI-like object.
lat (float, optional) – Latitude in degrees. When None, the value is left unchanged.
lon (float, optional) – Longitude in degrees. When None, the value is left unchanged.
elev (float, optional) – Elevation in meters. When None, the value is left unchanged.
inplace (bool, default True) – If True, mutate ed in place; otherwise apply the update to a copy and return it.
- Returns:
The updated object (in place) or a best-effort copy.
- Return type:
Notes
Both
lonandlongattribute names are supported on the header. If neither exists, a small adapter header may be created and attached back to the object when possible. Failures to write individual fields are ignored to maximize portability.Examples
>>> from types import SimpleNamespace >>> from pycsamt.site.utils import set_coords, get_coords >>> head = SimpleNamespace(lat=0.0, long=0.0, elev=0.0) >>> ed = SimpleNamespace(get_section=lambda *_: head) >>> set_coords(ed, lat=12.5, lon=1.2, elev=350.0) ... <...> >>> tuple(map(float, get_coords(ed))) (12.5, 1.2, 350.0)
See also
get_coordsRead header coordinates.
maybe_copyHelper used when not writing in place.
- pycsamt.site.utils.maybe_copy(x)[source]#
Attempt a deep copy of x, falling back to identity.
- Parameters:
x (Any) – Object to copy.
- Returns:
A deep-copied object when possible; otherwise x itself.
- Return type:
Notes
Some EDI backends contain objects that are not deepcopyable. This helper catches such cases and simply returns the original object to avoid raising.
Examples
>>> from pycsamt.site.utils import maybe_copy >>> maybe_copy({"a": [1, 2]}) {'a': [1, 2]}
- pycsamt.site.utils.apply_inplace(x, fn, *, inplace=False)[source]#
Apply a function to an object with optional in-place semantics.
- Parameters:
- Returns:
The result of applying
fnto the chosen object.- Return type:
Notes
This utility centralizes a common pattern used across the package: honor an
inplaceflag while being resilient to objects that cannot be deep-copied.Examples
>>> from pycsamt.site.utils import apply_inplace >>> def inc(d): ... d["n"] = d.get("n", 0) + 1 ... return d ... >>> data = {} >>> r1 = apply_inplace(data, inc, inplace=False) >>> r1 is data False >>> r2 = apply_inplace(data, inc, inplace=True) >>> r2 is data True
- pycsamt.site.utils.get_freq(ed)[source]#
Return the frequency vector from the impedance section.
- Parameters:
ed (Any) – EDI-like object exposing a
Zsection with either a publicfreqarray or a private_freqarray.- Returns:
A 1-D float array sorted in ascending order. Returns an empty array when no frequency information is available.
- Return type:
Notes
This is a read-only accessor. When both
freqand_freqare absent or unreadable, an empty array is returned. Non-finite values are preserved; only ordering is enforced.Examples
>>> import numpy as np >>> from types import SimpleNamespace >>> from pycsamt.site.utils import get_freq >>> Z = SimpleNamespace(freq=np.array([2.0, 1.0])) >>> ed = SimpleNamespace(Z=Z) >>> get_freq(ed) array([1., 2..])
See also
freq_matchFind indices of exact matches within a tolerance.
freq_selectBuild index selections from ranges or scalars.
- pycsamt.site.utils.freq_match(f, target, *, tol=1e-06)[source]#
Indices where frequency values match targets within tolerance.
- Parameters:
f (numpy.ndarray) – Frequency array to search. It is cast to
float.target (float or Sequence[float]) – One or more target frequencies to match against
f.tol (float, default 1e-6) – Absolute tolerance for a match. A value
vinfis considered a match totwhen \(|v - t| \\leq \\mathrm{tol}\).
- Returns:
Sorted integer indices into
fwhere matches occur. May be empty when no value matches withintol.- Return type:
Notes
Non-finite values in
fnever match. Duplicates infare all returned if they fall within the tolerance window.Examples
>>> import numpy as np >>> from pycsamt.site.utils import freq_match >>> f = np.array([10.0, 20.0, 20.0000004, 30.0]) >>> freq_match(f, 20.0, tol=5e-7) array([1, 2]) >>> freq_match(f, [5.0, 30.0]) array([3])
See also
freq_selectMore general selection by ranges or slices.
get_freqRead the frequency vector from an EDI object.
- pycsamt.site.utils.freq_select(f, sel, *, tol=1e-06)[source]#
Build an index selection from ranges, slices or scalars.
- Parameters:
f (numpy.ndarray) – Frequency vector to index. Cast to
float.sel (slice or float or Sequence[float] or tuple(float, float)) –
- Selection specifier:
slice(lo, hi)inclusive on both ends (withtol).(lo, hi)tuple uses the same semantics as above.single
floatselects exact matches (withtol).sequence of floats selects those exact targets.
tol (float, default 1e-6) – Absolute tolerance used for end inclusions and exact matching.
- Returns:
Sorted integer indices into
fthat satisfy the selection. Returns an empty array when nothing matches.- Return type:
Notes
Bounds are treated as inclusive with a tolerance, i.e. \(\\mathrm{lo} - \\mathrm{tol} \\le f \\le \\mathrm{hi} + \\mathrm{tol}\). For exact targets, matching is performed by
freq_match().Examples
>>> import numpy as np >>> from pycsamt.site.utils import freq_select >>> f = np.array([1.0, 10.0, 100.0]) >>> freq_select(f, slice(5.0, 100.0)) array([1, 2]) >>> freq_select(f, (0.9, 1.1)) array([0]) >>> freq_select(f, [10.0, 999.0]) array([1])
See also
freq_matchMatching by exact targets with tolerance.
get_freqFrequency accessor for EDI objects.
- pycsamt.site.utils.select_by_name(edic, pat)[source]#
Collect EDI-like objects whose station names match a pattern.
- Parameters:
edic (Any) – Iterable of EDI-like objects. It may also be a single EDI object, in which case a list with zero or one element is returned depending on the match.
pat (str or Pattern or callable) – Pattern passed to
match_name().
- Returns:
Items whose names match
patin iteration order.- Return type:
Examples
>>> from types import SimpleNamespace >>> from pycsamt.site.utils import select_by_name >>> eds = [SimpleNamespace(station="E01"), ... SimpleNamespace(station="N10")] >>> keep = select_by_name(eds, "E*") >>> [getattr(x, "station") for x in keep] ['E01']
See also
match_nameFlexible single-name predicate.
iter_edifilesSafe iterator over EDI-like inputs.
- pycsamt.site.utils.match_name(pat, name)[source]#
Case-insensitive station-name matcher with flexible patterns.
- Parameters:
pat (str or Pattern or callable) – Matching strategy. If callable, it is invoked as
bool(pat(name)). If a compiled regex,pat.searchis used. If a string, the function tries in order: glob-like wildcards (*,?,[]), regex-looking strings, and finally a case-insensitive literal compare.name (str) – The candidate station name.
- Returns:
Trueif the pattern matchesname,Falseon any error or no match.- Return type:
Notes
Glob-like patterns are translated to regular expressions with
re.IGNORECASE. Strings that contain typical regex meta- characters (e.g.. ^ $ + | ( ) \\) are treated as regexes.Examples
>>> import re >>> from pycsamt.site.utils import match_name >>> match_name("E*", "E01") True >>> match_name(re.compile(r"^X\\d+$"), "X123") True >>> match_name(lambda s: s.endswith("99"), "A99") True
See also
select_by_nameCollect items from an iterable using a pattern.
- pycsamt.site.utils.wrap_azimuth(az)[source]#
Wrap an azimuth angle in degrees to the half-open range [0, 360).
- Parameters:
az (float) – Angle in degrees, possibly outside the canonical range.
- Returns:
Angle in degrees in the interval
[0, 360).- Return type:
Notes
The operation is equivalent to
az % 360with care for negative inputs.Examples
>>> from pycsamt.site.utils import wrap_azimuth >>> wrap_azimuth(370.0) 10.0 >>> wrap_azimuth(-10.0) 350.0
- pycsamt.site.utils.deg_to_mrad(x)[source]#
Convert degrees to milliradians.
- Parameters:
x (float or numpy.ndarray) – Angle(s) in degrees.
- Returns:
Converted values in milliradians.
- Return type:
Notes
The conversion uses: \(\\mathrm{mrad} = \\mathrm{deg} \\times \\pi / 180 \\times 1000\).
Examples
>>> import numpy as np >>> from pycsamt.site.utils import deg_to_mrad >>> float(deg_to_mrad(180.0)) 3141.592653589793 >>> deg_to_mrad(np.array([0.0, 90.0])) array([ 0. , 1570.79632679])
- pycsamt.site.utils.mrad_to_deg(x)[source]#
Convert milliradians to degrees.
- Parameters:
x (float or numpy.ndarray) – Angle(s) in milliradians.
- Returns:
Converted values in degrees.
- Return type:
Notes
The conversion uses: \(\\mathrm{deg} = \\mathrm{mrad} \\times 180 / (\\pi \\times 1000)\).
Examples
>>> import numpy as np >>> from pycsamt.site.utils import mrad_to_deg >>> float(mrad_to_deg(3141.592653589793)) 180.0 >>> mrad_to_deg(np.array([0.0, 1570.79632679])) array([ 0., 90.])