Source code for pycsamt.tdem.workflow
"""High-level TEMAVG survey conversion workflows."""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Union
from ..api.property import PyCSAMTObject
from ._base import TEMSounding
from .survey import TEMSurvey, read_temavg_survey
PathLike = Union[str, Path]
__all__ = [
"TEMAVGConversion",
"read_temavg_soundings",
"transform_temavg_survey",
]
[docs]
@dataclass(repr=False)
class TEMAVGConversion(PyCSAMTObject):
"""Result bundle returned by TEMAVG workflow helpers.
Parameters
----------
survey : TEMSurvey
Parsed survey folder, including companion files and
optional coordinate table.
soundings : list of TEMSounding
Station-wise soundings built from TEMAVG rows.
results : list of dict
Frequency-domain transform result dictionaries.
collection : object, optional
EDI collection created when ``return_collection=True``.
written_paths : list of str
EDI file paths written when ``savepath`` is supplied.
"""
survey: TEMSurvey
soundings: list[TEMSounding]
results: list[dict[str, Any]] = field(default_factory=list)
collection: Any | None = None
written_paths: list[str] = field(default_factory=list)
verbose: int = 0
logger: object | None = None
[docs]
@property
def n_soundings(self) -> int:
"""Number of generated station soundings."""
return len(self.soundings)
[docs]
@property
def n_results(self) -> int:
"""Number of transformed frequency-domain results."""
return len(self.results)
[docs]
@property
def stations(self) -> list[str]:
"""Station names represented by the generated soundings."""
return [snd.station_name for snd in self.soundings]
[docs]
def read_temavg_soundings(
path: PathLike,
*,
pattern: str = "*.AVG",
coordinate_file: PathLike | None = None,
stems: list[str] | None = None,
component: str = "Hz",
frequency: float | None = None,
data_column: str = "magnitude",
magnitude_unit: str = "uV/A",
data_type: str = "voltage",
rx_turns: int = 1,
tx_turns: int = 1,
min_gates: int = 1,
verbose: int = 0,
logger: object | None = None,
) -> list[TEMSounding]:
"""Read a TEMAVG folder and return station soundings.
This helper performs the first half of the TEMAVG pipeline:
parse a survey folder, attach coordinates when available, and
build one :class:`TEMSounding` per station.
Parameters
----------
path : path-like
Folder containing TEMAVG ``.AVG`` files.
pattern : str, default "*.AVG"
Glob pattern used to select processed average files.
coordinate_file : path-like, optional
Coordinate table to attach by ``(profile, station)``.
If omitted, common coordinate filenames are discovered
automatically.
stems : list of str, optional
File stems to convert, for example ``["TEM100"]``.
When omitted, all parsed ``.AVG`` files are used.
component : str, default "Hz"
TEMAVG component to select.
frequency : float, optional
Repetition frequency to select.
data_column : {"magnitude"}, default "magnitude"
TEMAVG transient column used for the sounding decay.
magnitude_unit : str, default "uV/A"
Unit of the TEMAVG magnitude column.
data_type : str, default "voltage"
Data convention stored in each sounding.
rx_turns, tx_turns : int, default 1
Receiver and transmitter turn counts.
min_gates : int, default 1
Minimum number of selected gates required per station.
Returns
-------
list of TEMSounding
Station-wise soundings ready for transformation.
"""
survey = read_temavg_survey(
path,
pattern=pattern,
coordinate_file=coordinate_file,
verbose=verbose,
logger=logger,
)
return survey.to_soundings(
stems=stems,
component=component,
frequency=frequency,
data_column=data_column,
magnitude_unit=magnitude_unit,
data_type=data_type,
rx_turns=rx_turns,
tx_turns=tx_turns,
min_gates=min_gates,
verbose=verbose,
logger=logger,
)
[docs]
def transform_temavg_survey(
path: PathLike,
*,
pattern: str = "*.AVG",
coordinate_file: PathLike | None = None,
stems: list[str] | None = None,
component: str = "Hz",
frequency: float | None = None,
data_column: str = "magnitude",
magnitude_unit: str = "uV/A",
data_type: str = "voltage",
rx_turns: int = 1,
tx_turns: int = 1,
min_gates: int = 1,
method: str = "late_time",
freq_convention: str = "skin_depth",
phase_mode: str = "homogeneous",
loop_geometry_correction: bool = True,
return_collection: bool = False,
savepath: PathLike | None = None,
verbose: int = 0,
logger: object | None = None,
) -> TEMAVGConversion:
"""Run a complete TEMAVG-to-frequency workflow.
The function reads a TEMAVG survey folder, builds station
soundings, transforms them to pseudo-frequency apparent
resistivity and impedance, and optionally creates or writes EDI
output.
Parameters
----------
path : path-like
Folder containing TEMAVG ``.AVG`` files.
pattern : str, default "*.AVG"
Glob pattern used to select processed average files.
coordinate_file : path-like, optional
Coordinate table to attach by profile and station.
stems : list of str, optional
File stems to convert. Use this to process one profile
before scaling to a full survey folder.
component : str, default "Hz"
TEMAVG component to select.
frequency : float, optional
Repetition frequency to select.
data_column : {"magnitude"}, default "magnitude"
TEMAVG transient column used for the sounding decay.
magnitude_unit : str, default "uV/A"
Unit of the TEMAVG magnitude column.
data_type : str, default "voltage"
Data convention stored in each sounding.
rx_turns, tx_turns : int, default 1
Receiver and transmitter turn counts.
min_gates : int, default 1
Minimum number of selected gates required per station.
method : {"late_time", "fourier"}, default "late_time"
Transformation method used by :class:`TEMtoEDI`.
freq_convention : str, default "skin_depth"
Pseudo-frequency convention for late-time transforms.
phase_mode : str, default "homogeneous"
Phase model used for synthetic impedance.
loop_geometry_correction : bool, default True
Whether to apply in-loop geometry corrections.
return_collection : bool, default False
If ``True``, also build an EDI collection in memory.
savepath : path-like, optional
Directory where synthetic EDI files are written.
Returns
-------
TEMAVGConversion
Bundle containing the parsed survey, station soundings,
transform results, and optional EDI outputs.
Examples
--------
>>> from pycsamt.tdem import transform_temavg_survey
>>> out = transform_temavg_survey(
... "data/TEMAVG/JIANGSU",
... stems=["TEM100"],
... )
>>> out.n_soundings > 0
True
"""
from .transform import (
FourierTransform,
LateTimeTransform,
TEMtoEDI,
)
survey = read_temavg_survey(
path,
pattern=pattern,
coordinate_file=coordinate_file,
verbose=verbose,
logger=logger,
)
soundings = survey.to_soundings(
stems=stems,
component=component,
frequency=frequency,
data_column=data_column,
magnitude_unit=magnitude_unit,
data_type=data_type,
rx_turns=rx_turns,
tx_turns=tx_turns,
min_gates=min_gates,
verbose=verbose,
logger=logger,
)
method_name = method.lower()
if method_name == "late_time":
transformer = LateTimeTransform(
freq_convention=freq_convention,
phase_mode=phase_mode,
loop_geometry_correction=loop_geometry_correction,
verbose=verbose,
logger=logger,
)
elif method_name == "fourier":
transformer = FourierTransform(
verbose=verbose,
logger=logger,
)
else:
msg = "method must be 'late_time' or 'fourier'."
raise ValueError(msg)
results = transformer.transform_many(soundings)
converter = None
if return_collection or savepath is not None:
converter = TEMtoEDI(
method=method_name,
freq_convention=freq_convention,
phase_mode=phase_mode,
loop_geometry_correction=loop_geometry_correction,
out_dir=savepath or "edi_out/tem",
verbose=verbose,
logger=logger,
)
collection = (
converter.transform_many(soundings)
if return_collection and converter is not None
else None
)
written_paths = (
converter.save(soundings, path=savepath)
if savepath is not None and converter is not None
else []
)
return TEMAVGConversion(
survey=survey,
soundings=soundings,
results=results,
collection=collection,
written_paths=written_paths,
verbose=verbose,
logger=logger,
)