2.23. pycsamt.format#

Backend-neutral PCSF and PCSM representations for electromagnetic inversion results, plus the human-readable PCBH borehole exchange contract. The package provides shared schemas, serialization, conversion adapters, model/borehole association, rendering contracts, and provenance helpers.

For format concepts, complete workflows, and browsable file examples, see PCSF — Common Subsurface Format. For spatial multi-hole projects, see PCBH — Common Borehole Format.

2.23.1. Public facade#

The most commonly used classes and functions are re-exported directly from pycsamt.format.

pyCSAMT Common Subsurface Format (PCSF) — backend-neutral inversion results.

See PYCSAMT-PCSF-INVERSION-FORMAT-PLAN.md at the repository root for the full design and phase-by-phase status.

Public second-level namespaces (importable as pycsamt.format.<name> right after import pycsamt.format, not only via an explicit from pycsamt.format.<name> import ...):

  • pycsamt.format.adapters — per-backend converters (adapters.occam2d_to_pcsf, adapters.modem3d_to_pcsf, adapters.mare2dem_to_pcsf), plus a solver-agnostic adapters.grid2d_to_pcsf/adapters.grid3d_to_pcsf/ adapters.mesh_to_pcsf for any AI/DL inversion result (see pycsamt.format.adapters.generic and pycsamt.format.provenance).

  • pycsamt.format.schema — the dataclasses re-exported at this top level (Grid2DGeometry, PCSFModel, …).

  • pycsamt.format.ioread_pcsf/write_pcsf, also re-exported here.

  • pycsamt.format.textread_pcsm/write_pcsm/ pcsf_to_pcsm/pcsm_to_pcsf: PCSM, the hand-editable ASCII sibling of a .pcsf file, also re-exported here.

  • pycsamt.format.multiline, pycsamt.format.topography, pycsamt.format.pointcloud, pycsamt.format.regrid — likewise re-exported here.

pycsamt.format.read_pcbh(path, *, validate=True, max_bytes=16777216, max_boreholes=10000, max_intervals=1000000, max_nesting=32)#

Read a canonical PCBH JSON file.

Parameters:
  • path (path-like) – Input .pcbh.json file.

  • validate (bool, default True) – Run semantic validation before returning.

  • max_bytes (int) – Positive limits for untrusted input.

  • max_boreholes (int) – Positive limits for untrusted input.

  • max_intervals (int) – Positive limits for untrusted input.

  • max_nesting (int) – Positive limits for untrusted input.

Returns:

Parsed and optionally validated document.

Return type:

PCBHDocument

Raises:
  • OSError – If the file cannot be read.

  • UnicodeError – If the file is not UTF-8.

  • ValueError – If JSON, structure, version, or limits are invalid.

  • PCBHValidationError – If semantic validation fails.

pycsamt.format.write_pcbh(document, path, *, validate=True, indent=2)#

Atomically write a canonical PCBH JSON file.

Parameters:
  • document (PCBHDocument) – Document to serialize.

  • path (path-like) – Destination path. Parent directories are created when needed.

  • validate (bool, default True) – Run semantic validation before writing.

  • indent (int, default 2) – Positive JSON indentation width.

Returns:

Destination path.

Return type:

pathlib.Path

Raises:
  • TypeError – If document is not a PCBH document.

  • ValueError – If validation, JSON values, or indent are invalid.

  • OSError – If the atomic write fails.

Notes

The temporary file is created beside the destination so os.replace() remains an atomic same-filesystem operation.

pycsamt.format.desurvey(borehole, *, boundaries=())#

Generate a 3-D centerline for borehole.

Survey stations after total depth are rejected by schema validation. If the first survey station is below the collar, its attitude is extended back to MD 0. Likewise, the final attitude is extended to total depth.

Parameters:
Return type:

DesurveyedTrajectory

pycsamt.format.boreholes_from_csv(path, *, columns=None, constants=None, strict=True, delimiter=None, document_id=None, created_by='pycsamt CSV importer', max_bytes=10485760, max_rows=250000)#

Import a combined collar-and-interval CSV as a PCBH document.

Parameters:
  • path (path-like) – UTF-8 CSV containing repeated collar fields and interval rows.

  • columns (dict, optional) – Explicit {canonical_field: source_header} mapping. Canonical names use dotted paths such as borehole.id and interval.from_md.

  • constants (dict, optional) – Constant canonical values, commonly crs.horizontal. Constants take precedence over mapped row values.

  • strict (bool, default=True) – Raise PCBHCSVImportError if any error is recorded. In permissive mode, invalid rows are rejected and valid rows returned.

  • delimiter ({',', ';', 't', '|'}, optional) – Explicit delimiter. If omitted, detection is restricted to this set.

  • document_id (str, optional) – PCBH document identifier. Defaults to csv:<file stem>.

  • created_by (str, default='pycsamt CSV importer') – Provenance name stored on the document.

  • max_bytes (int, default=10485760) – Maximum source size in bytes.

  • max_rows (int, default=250000) – Maximum number of data rows.

Returns:

  • document (PCBHDocument) – Valid document containing every accepted row.

  • report (ImportReport) – Source checksum, mappings, inferences, counts, and diagnostics.

Raises:
  • PCBHCSVImportError – If the file structure is unusable, no valid boreholes remain, or strict mode records an error. The exception exposes report.

  • ValueError – If a resource limit or delimiter parameter is invalid.

Return type:

tuple[PCBHDocument, ImportReport]

Notes

Missing tokens are normalized before conversion and never stringified. Repeated collar and total-depth values must agree within a borehole.

pycsamt.format.boreholes_from_csv_directory(directory, *, manifest='import.yaml', strict=True, document_id=None, created_by='pycsamt relational CSV importer')#

Import joined collar, survey, log, structure, sample, assay tables.

Parameters:
Return type:

tuple[PCBHDocument, ImportReport]

pycsamt.format.write_csv_directory(document, directory)#

Export supported PCBH content as a relational CSV directory.

Parameters:
  • document (PCBHDocument)

  • directory (str | Path)

Return type:

Path

pycsamt.format.borehole_from_las(path, *, collar, crs_horizontal, kind='unknown', status='unknown', depth_curve='DEPT', resistivity_curve='RESD', lithology_curve='LITH', max_samples=250000)#

Import a LAS 2.0 curve subset while retaining curve metadata.

Parameters:
  • path (str | Path)

  • collar (Collar)

  • crs_horizontal (str)

  • kind (str)

  • status (str)

  • depth_curve (str)

  • resistivity_curve (str)

  • lithology_curve (str | None)

  • max_samples (int)

Return type:

tuple[PCBHDocument, ImportReport]

pycsamt.format.write_las_subset(borehole, path, *, null_value=-9999.25, company='pycsamt')#

Write inline PCBH curves, or an interval-derived LAS subset.

Parameters:
  • borehole (PCBHBorehole)

  • path (str | Path)

  • null_value (float)

  • company (str)

Return type:

tuple[Path, LASExportReport]

pycsamt.format.build_render_model(document, *, family='lithology', selected_ids=None, radius_policy=None, lod_tolerance=0.0, sampling_step_md=10.0, max_points_per_hole=10000)#

Build a render model with a short-lived builder.

Parameters mirror BoreholeRenderBuilder.build(). Distances are in the document coordinate/depth unit and no CRS transformation is applied.

Parameters:
  • document (PCBHDocument)

  • family (str)

  • selected_ids (set[str] | None)

  • radius_policy (DisplayRadiusPolicy | None)

  • lod_tolerance (float)

  • sampling_step_md (float)

  • max_points_per_hole (int)

Return type:

PCBHRenderModel

pycsamt.format.embed_pcbh(model, document, *, uri=None)#

Return a model copy with a validated portable PCBH attachment.

Parameters:
  • model (PCSFModel)

  • document (PCBHDocument)

  • uri (str | None)

Return type:

PCSFModel

pycsamt.format.reference_pcbh(model, uri, sha256)#

Return a model copy with an external PCBH reference.

Parameters:
Return type:

PCSFModel

pycsamt.format.extract_pcbh(model)#

Return embedded PCBH data without resolving external resources.

Parameters:

model (PCSFModel)

Return type:

PCBHDocument | None

pycsamt.format.align_pcbh_to_pcsf(document, model, *, vertical_offset=None, sampling_step_md=10.0)#

Transform PCBH paths into PCSF x/y/depth coordinates.

vertical_offset is required when PCBH and PCSF vertical references cannot be established as identical. It is added to PCBH elevations before conversion to PCSF depth-positive-down coordinates.

Parameters:
  • document (PCBHDocument)

  • model (PCSFModel)

  • vertical_offset (float | None)

  • sampling_step_md (float)

Return type:

PCBHAlignmentReport

pycsamt.format.write_geojson(document, path, *, target_crs='EPSG:4326', include_z=True)#

Write WGS84 collar Points and trajectory LineStrings as GeoJSON.

Parameters:
  • document (PCBHDocument)

  • path (str | Path)

  • target_crs (str)

  • include_z (bool)

Return type:

tuple[Path, PCBHExportReport]

pycsamt.format.write_vtp(document, path, *, family='lithology', sides=8)#

Write interval tubes as ASCII VTK XML PolyData (.vtp).

Parameters:
  • document (PCBHDocument)

  • path (str | Path)

  • family (str)

  • sides (int)

Return type:

tuple[Path, PCBHExportReport]

pycsamt.format.write_gltf(document, path, *, family='lithology', sides=8)#

Write browser-ready glTF 2.0 (.gltf) or binary GLB tubes.

Parameters:
  • document (PCBHDocument)

  • path (str | Path)

  • family (str)

  • sides (int)

Return type:

tuple[Path, PCBHExportReport]

class pycsamt.format.SourceKind(category, path, is_dir, backend=None, geometry=None, target_geometry=None, confidence='high', detail='', hints=<factory>)#

Bases: object

Outcome of detect_source().

Variables:
  • category (str) – "pcsf" | "pcsm" | "solver" | "ai_arrays" | "unknown".

  • path (pathlib.Path) – The path that was probed (a file, or a solver working directory).

  • is_dir (bool) – Whether path is a directory.

  • backend (str or None) – "occam2d" | "modem" | "mare2dem" for solver sources, else None.

  • geometry (str or None) – For pcsf / pcsm: the peeked geometry kind.

  • target_geometry (str or None) – For solver / ai_arrays: the PCSF geometry the conversion will produce.

  • confidence (str) – "high" | "medium" | "low".

  • detail (str) – Human-readable one-line explanation.

  • hints (dict) – Extra paths / keys the converter needs (see module docstring).

Parameters:
category: str#
path: Path#
is_dir: bool#
backend: str | None = None#
geometry: str | None = None#
target_geometry: str | None = None#
confidence: str = 'high'#
detail: str = ''#
hints: dict[str, Any]#
property convertible: bool#

True when this source can be fed to pycsamt format convert.

to_dict()#

JSON-friendly plain-dict view.

Return type:

dict[str, Any]

pycsamt.format.detect_source(path, *, solver_hint=None)#

Detect what path is and how it should convert to PCSF/PCSM.

Parameters:
  • path (path-like) – A .pcsf / .pcsm / .npz / .npy file, a solver-specific file (.iter, .rho, .poly, .resistivity, …), or a solver working directory.

  • solver_hint ({"occam2d", "modem", "mare2dem"}, optional) – Force the solver backend instead of fingerprinting it. Ignored for PCSF/PCSM/array sources.

Return type:

SourceKind

Raises:

FileNotFoundError – If path does not exist.

pycsamt.format.describe_source(sk)#

Return a short multi-line human summary of sk.

Parameters:

sk (SourceKind)

Return type:

str

class pycsamt.format.Grid2DGeometry(x, z, x_nodes=None, z_nodes=None, origin=None, azimuth_deg=None)#

Bases: PyCSAMTObject

Single-profile rectilinear geometry (Occam2D / DUHI-via-Occam2D).

Parameters:
  • x (ndarray (n_x,)) – Real station chainage, metres — never a solver’s mesh-local frame (see the Occam2D coordinate-frame note in pycsamt.interp._base.ResistivityModel.from_occam2d()).

  • z (ndarray (n_z,)) – Depth cell centres, metres, positive downward.

  • x_nodes (ndarray, optional) – Cell-edge coordinates, one longer than x/z.

  • z_nodes (ndarray, optional) – Cell-edge coordinates, one longer than x/z.

  • origin (ndarray (2,), optional) – Real-world offset when x is locally referenced.

  • azimuth_deg (float, optional) – Profile bearing, for georeferencing back to the survey line.

kind: ClassVar[str] = 'grid2d'#
x: ndarray#
z: ndarray#
x_nodes: ndarray | None = None#
z_nodes: ndarray | None = None#
origin: ndarray | None = None#
azimuth_deg: float | None = None#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

property resistivity_shape: tuple[int, int]#
class pycsamt.format.Grid3DGeometry(x, y, z, x_nodes=None, y_nodes=None, z_nodes=None, origin=None, rotation_deg=0.0, n_air=0)#

Bases: PyCSAMTObject

Native 3-D tensor volume geometry (ModEM).

Parameters:
  • x (ndarray) – Cell-centre coordinates, metres.

  • y (ndarray) – Cell-centre coordinates, metres.

  • z (ndarray) – Cell-centre coordinates, metres.

  • x_nodes (ndarray, optional) – Cell-edge coordinates.

  • y_nodes (ndarray, optional) – Cell-edge coordinates.

  • z_nodes (ndarray, optional) – Cell-edge coordinates.

  • origin (ndarray (3,), optional) – Real-world grid origin.

  • rotation_deg (float, default 0.0) – Grid rotation about the vertical axis.

  • n_air (int, default 0) – Explicit air-layer count (unlike Occam2D’s inferred count).

kind: ClassVar[str] = 'grid3d'#
x: ndarray#
y: ndarray#
z: ndarray#
x_nodes: ndarray | None = None#
y_nodes: ndarray | None = None#
z_nodes: ndarray | None = None#
origin: ndarray | None = None#
rotation_deg: float = 0.0#
n_air: int = 0#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

property resistivity_shape: tuple[int, int, int]#
class pycsamt.format.UnstructuredMeshGeometry(nodes, connectivity, region_ids, plane='xz')#

Bases: PyCSAMTObject

Native unstructured triangular mesh geometry (MARE2DEM).

Preserves the mesh as-is (no forced regrid onto a tensor grid), so a MARE2DEM result keeps its real element resolution.

Parameters:
  • nodes (ndarray (n, 2) or (n, 3)) – Node coordinates, metres.

  • connectivity (ndarray (m, 3), int) – Triangle node indices.

  • region_ids (ndarray (m,), int) – Region id per triangle.

  • plane ({"xz", "xy", "3d"}, default "xz") – Physical plane the mesh lives in. MARE2DEM profiles are conventionally in (y, z) but stored generically as plane="xz" with x holding the profile’s own along-line coordinate.

kind: ClassVar[str] = 'mesh_unstructured'#
nodes: ndarray#
connectivity: ndarray#
region_ids: ndarray#
plane: str = 'xz'#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

property n_regions: int#
class pycsamt.format.LineEntry(line_id, geometry, resistivity, offset_y=0.0, offset_kind='synthetic', azimuth_deg=None)#

Bases: PyCSAMTObject

One profile within a MultilineGeometry.

Parameters:
  • line_id (str) – Unique identifier for this line.

  • geometry (Grid2DGeometry) – The line’s own 2-D section geometry.

  • resistivity (ndarray (n_z, n_x)) – Canonical linear ohm.m resistivity for this line.

  • offset_y (float) – Cross-line position, metres.

  • offset_kind ({"real", "synthetic"}, default "synthetic") – Whether offset_y comes from real survey geometry or is a placeholder spacing for display only.

  • azimuth_deg (float, optional) – Line bearing.

line_id: str#
geometry: Grid2DGeometry#
resistivity: ndarray#
offset_y: float = 0.0#
offset_kind: str = 'synthetic'#
azimuth_deg: float | None = None#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

class pycsamt.format.DerivedVolume(grid, resistivity, derivation_method='linear_interp', derived_from=<factory>, synthesized=True)#

Bases: PyCSAMTObject

Optional cached 3-D volume synthesized from stacked lines.

Kept explicitly tagged as synthesized so a reader never mistakes a stack-interpolated volume for a native 3-D inversion (see derivation_method/synthesized in the design plan’s §2).

Parameters:
grid: Grid3DGeometry#
resistivity: ndarray#
derivation_method: str = 'linear_interp'#
derived_from: list[str]#
synthesized: bool = True#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

class pycsamt.format.MultilineGeometry(lines=<factory>, derived_volume=None)#

Bases: PyCSAMTObject

A set of profiles plus real line geometry (fence/block views).

Formalizes what pycsamt/app/web/callbacks/map3d.py currently reconstructs at render time from a stack of independent 2-D sections. Each line carries its own resistivity; the optional derived_volume is a documented, reproducible synthesis rather than a render-time-only side effect.

Parameters:
kind: ClassVar[str] = 'multiline'#
lines: list[LineEntry]#
derived_volume: DerivedVolume | None = None#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

class pycsamt.format.StationTable(name=<factory>, x=<factory>, y=<factory>, z=<factory>, line_id=None, lon=None, lat=None)#

Bases: PyCSAMTObject

Survey station positions, shared across geometry kinds.

x/y/z are geometry-local (along-profile chainage for grid2d, the model grid’s own frame for grid3d, whatever frame the caller supplied for mesh_unstructured) — never assumed to be real-world geographic coordinates, per SPEC.md’s own load_pcsf_lines convention. lon/lat, when present, are the one explicit, unambiguous carrier of real-world position: WGS84 decimal degrees, the same convention every other real-coordinate source in this codebase already uses (EDI headers, a ModEM .dat file’s GG_Lat/GG_Lon columns, pycsamt.map._core.StationRecord). A single-line grid2d (or any other kind’s) PCSF file that sets these needs no separate known_stations match to place its stations on a real basemap.

Parameters:
name: list[str]#
x: ndarray#
y: ndarray#
z: ndarray#
line_id: list[str] | None = None#
lon: ndarray | None = None#
lat: ndarray | None = None#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

class pycsamt.format.TopographyPerStation(station_id=<factory>, elevation=<factory>)#

Bases: PyCSAMTObject

Scalar-per-station topography (matches the existing convention in pycsamt.map.topo).

Parameters:
kind: ClassVar[str] = 'per_station'#
station_id: list[str]#
elevation: ndarray#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

class pycsamt.format.TopographyRaster(x, y, elevation)#

Bases: PyCSAMTObject

Gridded-DEM topography — a regular elevation surface independent of any station table.

Unlike TopographyPerStation, this carries no station identifiers at all: it is a standalone terrain surface a consumer can sample at any coordinate, not a per-station lookup table. It introduces no GDAL/rasterio-class dependency — construction is via plain x/y/elevation arrays a caller has already obtained by whatever means it likes (see pycsamt.format.topography.topography_from_grid()); PCSF itself never parses a georeferenced raster file format.

Parameters:
  • x (ndarray (n_x,)) – Grid x-coordinates (or longitude), increasing.

  • y (ndarray (n_y,)) – Grid y-coordinates (or latitude), increasing.

  • elevation (ndarray (n_y, n_x)) – Elevation surface, metres, sampled on the (y, x) meshgrid implied by x/y — the same row-major convention numpy.meshgrid(x, y) (default indexing="xy") produces.

kind: ClassVar[str] = 'raster'#
x: ndarray#
y: ndarray#
elevation: ndarray#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

class pycsamt.format.PCSFModel(geometry, resistivity=None, resistivity_native=None, resistivity_native_encoding=None, uncertainty=None, sensitivity=None, resistivity_by_region=None, resistivity_by_node=None, stations=None, topography=None, survey=<factory>, history=<factory>, source_backend='generic', created_by='', created_at='', crs=None, description='', metadata=<factory>, boreholes=None)#

Bases: PyCSAMTObject, MetadataMixin

Backend-neutral inversion-result container (one PCSF file).

Parameters:
  • geometry (Grid2DGeometry | Grid3DGeometry | UnstructuredMeshGeometry | MultilineGeometry) – The model’s geometry, discriminated by geometry.kind.

  • resistivity (ndarray, optional) – Canonical linear ohm.m resistivity. Required for grid2d/grid3d/mesh_unstructured geometries; must be None for multiline (each line carries its own resistivity — see LineEntry).

  • resistivity_native (ndarray, optional) – Passthrough of the source backend’s own array, for provenance.

  • resistivity_native_encoding ({"log10", "ln", "linear"}, optional) – Encoding of resistivity_native. Required whenever resistivity_native is given — never assumed.

  • uncertainty (ndarray, optional) – Same shape as resistivity, when available from the source.

  • sensitivity (ndarray, optional) – Same shape as resistivity, when available from the source.

  • resistivity_by_region (ndarray, optional) – Per-region resistivity table (mesh_unstructured only), alongside the per-cell resistivity expanded from it.

  • resistivity_by_node (ndarray, optional) – Per-node resistivity table (mesh_unstructured only), shape (n_nodes,) – the natural output shape of a graph-based model (e.g. a GCN) that predicts one value per mesh vertex rather than per cell. Kept alongside the per-cell resistivity expanded from it (see pycsamt.format.adapters.generic.mesh_to_pcsf()), the same provenance relationship resistivity_by_region has to its own per-cell expansion.

  • stations (StationTable, optional)

  • topography (TopographyPerStation | TopographyRaster, optional)

  • survey (dict) – Free-form survey metadata. Adapters populate this from pycsamt.metadata objects (SurveyMeta, BBox, ProvenanceMeta) via their own dict conversion; PCSF itself does not require a specific metadata class here.

  • history (dict of ndarray) – Optional per-iteration series (e.g. {"rms": ..., "lambda": ...}) from an InversionHistory-like source.

  • source_backend (str, default "generic") – "occam2d" | "modem3d" | "mare2dem" | "duhi" | "generic".

  • crs (str, optional) – A pyproj-compatible CRS string.

  • created_by (str)

  • created_at (str)

  • description (str)

  • metadata (dict[str, Any])

  • boreholes (Any | None)

geometry: Grid2DGeometry | Grid3DGeometry | UnstructuredMeshGeometry | MultilineGeometry#
resistivity: ndarray | None = None#
resistivity_native: ndarray | None = None#
resistivity_native_encoding: str | None = None#
uncertainty: ndarray | None = None#
sensitivity: ndarray | None = None#
resistivity_by_region: ndarray | None = None#
resistivity_by_node: ndarray | None = None#
stations: StationTable | None = None#
topography: TopographyPerStation | TopographyRaster | None = None#
survey: dict[str, Any]#
history: dict[str, ndarray]#
source_backend: str = 'generic'#
created_by: str = ''#
created_at: str = ''#
crs: str | None = None#
description: str = ''#
metadata: dict[str, Any]#
boreholes: Any | None = None#
property kind: str#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

pycsamt.format.read_pcsf(path)#

Read a PCSFModel back from a .pcsf (HDF5) file.

Parameters:

path (path-like) – Source file.

Returns:

Fully reconstructed and re-validated model.

Return type:

PCSFModel

Raises:

ValueError – If pcsf_version is missing, malformed, or names an unrecognised MAJOR version (see pycsamt/format/SPEC.md section 5); if geometry/kind is missing or not a recognised value; or if the reconstructed model fails PCSFModel.validate().

Warns:

UserWarning – If the file’s pcsf_version MINOR component is newer than this reader’s — fields added since then are silently ignored rather than causing a hard failure.

pycsamt.format.write_pcsf(model, path)#

Write a PCSFModel to a .pcsf (HDF5) file.

Parameters:
  • model (PCSFModel) – The model to serialize. Validated before anything is written.

  • path (path-like) – Destination file. Parent directories are created if missing.

Returns:

The path written to.

Return type:

pathlib.Path

Raises:

ValueError – If model fails PCSFModel.validate().

Examples

>>> import numpy as np
>>> from pycsamt.format import Grid2DGeometry, PCSFModel, write_pcsf, read_pcsf
>>> geometry = Grid2DGeometry(x=np.array([0.0, 100.0]), z=np.array([10.0, 50.0]))
>>> model = PCSFModel(
...     geometry=geometry,
...     resistivity=np.array([[100.0, 120.0], [50.0, 60.0]]),
...     source_backend="occam2d",
... )
>>> path = write_pcsf(model, "example.pcsf")
>>> round_tripped = read_pcsf(path)
pycsamt.format.read_pcsm(path)#

Read a PCSFModel back from a .pcsm (ASCII) file.

Parameters:

path (path-like) – Source file.

Returns:

Fully reconstructed and re-validated model.

Return type:

PCSFModel

Raises:

ValueError – If PCSM_VERSION is missing, malformed, or names an unrecognised MAJOR version (see pycsamt/format/SPEC.md section 5), if the file is otherwise malformed, or if the reconstructed model fails PCSFModel.validate().

pycsamt.format.write_pcsm(model, path, *, log10_view=False)#

Write a PCSFModel to a .pcsm (ASCII) file.

The same model write_pcsf() would encode as HDF5, encoded instead as human-readable, hand-editable text. See the module docstring for the grammar.

Every resistivity block carries its own encoding as an inline comment right next to the data, not only in a separate header line – e.g. RESISTIVITY  # linear ohm.m (canonical, ...) – so a reader never has to guess or scroll elsewhere to tell linear from log10. The canonical RESISTIVITY field is always linear ohm.m (PCSF’s own binding rule, SPEC.md section 2): this cannot be changed by an option, since a reader/consumer relies on that invariant unconditionally, and reversing a 10**log10(x) round-trip is not guaranteed bit-exact the way the rest of this format’s repr()-based float round-trip is.

Pass log10_view=True to additionally write a RESISTIVITY_LOG10 block (and, for a multiline file, one per line) alongside it – a convenience for reading resistivity in the log space many EM inversions (Occam2D among them) actually work in. This is a write-only, clearly-labelled derived view: read_pcsm() discards it, it never becomes part of the returned PCSFModel, and it never influences the canonical RESISTIVITY block. (An Occam2D-sourced model already carries its own real log10 array as RESISTIVITY_NATIVE whenever resistivity_native is set – that block is unaffected by this option and is written either way.)

A path ending in .gz (e.g. model.pcsm.gz) is written gzip-compressed. Large volumes (a native grid3d or a dense mesh_unstructured mesh) produce large plain-text files – the same trade-off ModEM’s own ASCII .rho format already accepts – and gzip substantially reduces that in practice (measured on the real, bundled willy_27freq_watex_line02_sample 590,400-cell ModEM volume, see examples/pcsm_conversion_demo: 19.7 MB plain, 3.9 MB gzipped – 5.0x smaller, real inverted resistivity compressing far better than synthetic/random test data would – vs. 4.1 MB for the equivalent .pcsf). A gzipped file is no longer something to casually open in a text editor, so this stays opt-in rather than the default: PCSM’s purpose is hand-editability, which a compressed file gives up.

Parameters:
  • model (PCSFModel) – The model to serialize. Validated before anything is written.

  • path (path-like) – Destination file. Parent directories are created if missing. A .gz suffix writes a gzip-compressed file.

  • log10_view (bool, default False) – Also write a RESISTIVITY_LOG10 convenience block (see above). Off by default so existing files/output size are unaffected unless explicitly requested.

Returns:

The path written to.

Return type:

pathlib.Path

Examples

>>> import numpy as np
>>> from pycsamt.format import Grid2DGeometry, PCSFModel
>>> from pycsamt.format.text import write_pcsm, read_pcsm
>>> geometry = Grid2DGeometry(x=np.array([0.0, 100.0]), z=np.array([10.0, 50.0]))
>>> model = PCSFModel(
...     geometry=geometry,
...     resistivity=np.array([[100.0, 120.0], [50.0, 60.0]]),
...     source_backend="occam2d",
... )
>>> path = write_pcsm(model, "example.pcsm")
>>> round_tripped = read_pcsm(path)
>>> gz_path = write_pcsm(model, "example.pcsm.gz")
>>> with_log10 = write_pcsm(model, "example_log10.pcsm", log10_view=True)
pycsamt.format.pcsf_to_pcsm(pcsf_path, pcsm_path, *, log10_view=False)#

Convert a .pcsf (HDF5) file to a .pcsm (ASCII) file.

log10_view is passed through to write_pcsm() — see there.

Parameters:
Return type:

Path

pycsamt.format.pcsm_to_pcsf(pcsm_path, pcsf_path)#

Convert a .pcsm (ASCII) file to a .pcsf (HDF5) file.

Parameters:
Return type:

Path

pycsamt.format.read_pcsf_or_pcsm(path)#

Read a PCSFModel from either encoding, by extension.

A single entry point for a consumer (e.g. app/mapview’s inversion-result importer) that wants to accept whichever of the two lossless PCSF encodings (see SPEC.md S9) a user hands it, without duplicating the .pcsf vs. .pcsm/.pcsm.gz dispatch itself. Extension is the only signal used – content is never sniffed – matching every other reader in this package.

Parameters:

path (path-like) – A .pcsf (binary HDF5), .pcsm (ASCII), or .pcsm.gz (gzip-compressed ASCII) file.

Raises:

ValueError – If path’s extension is none of the three recognised forms.

Return type:

PCSFModel

pycsamt.format.peek_kind(path)#

Return a PCSF/PCSM file’s geometry.kind without loading any array data – just the root/geometry attributes for .pcsf, or the GEOMETRY_KIND header line for .pcsm/.pcsm.gz.

Meant for a caller that wants to label several candidate files (e.g. a file picker UI, deciding which are grid2d/multiline and therefore importable by a given consumer) before committing to a full read_pcsf_or_pcsm() load of any one of them.

Raises:

ValueError – If path’s extension is not recognised, or the kind attribute/header cannot be found (e.g. a truncated file).

Parameters:

path (str | PathLike)

Return type:

str

pycsamt.format.build_multiline_pcsf(profiles, *, line_spacing=1.0, fallback_unit=1000.0, cache_derived_volume=True, topo=None, epsg=None, utm_zone=None, latlon=False, on_mismatch='raise', survey=None, source_backend='generic', created_by='', crs=None, description='')#

Build a multiline PCSFModel from a profiles dict.

Parameters:
  • profiles (mapping of str to mapping) – {line_name: {"x": (n_x,), "z": (n_z,), "rho": (n_z, n_x), ...}}, the exact shape map3d.py’s _profiles_from_pseudo/ _profiles_from_inversion_result already produce. rho must already be linear ohm.m (call _rho_log_to_ohm_m-equivalent conversion first, matching every other PCSF adapter’s canonical-linear convention). Optional per-line keys sta_x, sta_names, sta_elev, sta_lat, sta_lon populate PCSFModel.stations/ PCSFModel.topography when present. sta_lat/ sta_lon do double duty: line_offsets_from_stations() uses them (via pycsamt.map.geometry.survey_uv()) to compute each line’s real cross-strike offset when every line carries them, falling back to a synthetic index-based stack otherwise (see offset_kind below) – and the same values are also persisted into PCSFModel.stationslon/lat, so a loaded multiline file is self-sufficiently geo-referenced too, not just correctly spaced.

  • line_spacing (float) – Forwarded to stack_lines_to_common_grid() for the optional cached DerivedVolume (real per-line geometry itself never depends on these — only the synthetic offset fallback does).

  • fallback_unit (float) – Forwarded to stack_lines_to_common_grid() for the optional cached DerivedVolume (real per-line geometry itself never depends on these — only the synthetic offset fallback does).

  • cache_derived_volume (bool, default True) – When True and there are at least two lines, also cache a DerivedVolume (each line resampled onto a common grid) so a large multiline file does not need to re-resample on every render. Set False to keep the file smaller when that convenience volume is not needed.

  • topo (optional) – A “smart” real-coordinate source resolved via pycsamt.format.topo_source.resolve_topo() – see pycsamt.format.adapters.occam2d.occam2d_to_pcsf()’s identical parameter for the full description of accepted source types. Populates each line’s own sta_lat/sta_lon before the real-offset computation above runs, so passing topo is enough to get both a real cross-strike offset per line and a self-georeferenced file – no separate offset step is needed. Accepts either a single source matched by station name across every line combined (a .stn/.csv/Sites source covering the whole survey), or a {line_name: source} mapping / one-source-per-line sequence (in profiles’s own key order) for e.g. one .bln file per surveyed line. Takes precedence over any sta_lat/sta_lon already present in profiles for every station it resolves (with a UserWarning if both were supplied). If topo only partially covers a line’s stations, the resulting nan entries make line_offsets_from_stations() fall back to a synthetic offset for every line (mixing a real and a synthetic stack would look inconsistent – see that function’s own all-or-nothing behaviour) rather than silently using a partially-real one; the stations themselves still keep whatever real lon/lat topo did resolve. None (the default) leaves this function’s behaviour exactly as it was before topo existed.

  • epsg (int | None) – Forwarded to pycsamt.format.topo_source.resolve_topo(); see occam2d_to_pcsf’s identical parameters.

  • utm_zone (Any | None) – Forwarded to pycsamt.format.topo_source.resolve_topo(); see occam2d_to_pcsf’s identical parameters.

  • latlon (bool) – Forwarded to pycsamt.format.topo_source.resolve_topo(); see occam2d_to_pcsf’s identical parameters.

  • on_mismatch (str) – Forwarded to pycsamt.format.topo_source.resolve_topo(); see occam2d_to_pcsf’s identical parameters.

  • survey (Any | Mapping[str, Any] | None) – Passed straight through to PCSFModel.

  • source_backend (str) – Passed straight through to PCSFModel.

  • created_by (str) – Passed straight through to PCSFModel.

  • crs (str | None) – Passed straight through to PCSFModel.

  • description (str) – Passed straight through to PCSFModel.

Raises:

ValueError – If profiles is empty, or any line is missing x/z/rho.

Return type:

PCSFModel

Examples

>>> import numpy as np
>>> from pycsamt.format.multiline import build_multiline_pcsf
>>> profiles = {
...     "L1": {"x": np.array([0.0, 100.0]), "z": np.array([10.0, 50.0]),
...            "rho": np.array([[100.0, 110.0], [50.0, 55.0]])},
...     "L2": {"x": np.array([0.0, 100.0]), "z": np.array([10.0, 50.0]),
...            "rho": np.array([[200.0, 210.0], [90.0, 95.0]])},
... }
>>> model = build_multiline_pcsf(profiles, cache_derived_volume=False)
>>> model.kind
'multiline'
>>> [line.line_id for line in model.geometry.lines]
['L1', 'L2']
pycsamt.format.multiline_pcsf_to_profiles(model)#

Reconstruct a profiles dict from a multiline PCSFModel.

The exact inverse of build_multiline_pcsf()’s per-line conversion — the result is a drop-in alternate data source for map3d.py’s existing _build_fence_fig/_assemble_3d_grid/ _build_block_fig, which only need the {"x", "z", "rho", ...} shape, not any particular origin.

Parameters:

model (PCSFModel) – Must have geometry.kind == "multiline".

Returns:

{line_id: {"x", "z", "rho", "sta_x", "sta_names", "sta_elev", "sta_lat", "sta_lon"}}. Station keys are populated from PCSFModel.stations when present (matched to each line via StationTable.line_id); sta_lat/sta_lon come from StationTable.lat/.lon when the file has them (see build_multiline_pcsf()), else stay empty lists – an older file written before those fields existed round-trips the same way it always did.

Return type:

dict

Raises:

ValueError – If model is not a multiline geometry.

pycsamt.format.line_offsets_from_stations(profiles)#

Cross-strike offset (m) for each line, from real station lat/lon.

Same algorithm as map3d.py’s private _line_real_offsets (built on pycsamt.map.geometry.survey_uv()), so both the live-cache rendering path and this persisted-file path place lines identically. Requires every line to carry sta_lat/sta_lon/ sta_names of equal length; returns None otherwise so callers fall back to a synthetic index-based stack via resolve_offset().

Parameters:

profiles (Mapping[str, Mapping[str, Any]])

Return type:

dict[str, float] | None

pycsamt.format.stack_lines_to_common_grid(profiles, *, line_spacing=1.0, fallback_unit=1000.0)#

Resample every line onto the first line’s own (x, z) grid.

Same algorithm as map3d.py’s private _assemble_3d_grid: the first profile’s grid is the reference; other lines are resampled onto it via _resample_line_to_grid().

Returns:

  • x_arr, z_arr (ndarray) – The reference line’s own coordinates.

  • y_arr (ndarray, shape (n_lines,)) – Per-line cross-strike offset, from line_offsets_from_stations() when available, otherwise a synthetic idx * spacing * fallback_unit stack (see pycsamt.map.geometry.resolve_offset()).

  • rho_3d (ndarray, shape (n_lines, n_x, n_z))

Parameters:
Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

pycsamt.format.topography_from_map_data(data)#

Build topography from a MapData’s own station elevations (typically real, EDI-derived values).

Parameters:

data (MapData) – Survey data, e.g. from pycsamt.map.load_lines().

Returns:

None when no station carries a finite elevation, so callers can leave PCSFModel.topography unset rather than persisting an all-nan table.

Return type:

TopographyPerStation or None

Examples

>>> from pycsamt.map import load_lines
>>> from pycsamt.format.topography import topography_from_map_data
>>> data = load_lines("data/AMT/WILLY_DATA", detect="folder")
>>> topo = topography_from_map_data(data)
pycsamt.format.topography_from_elevation_file(content, filename)#

Build topography from an uploaded elevation file.

Thin wrapper around pycsamt.map.topo.parse_elevation_file() (CSV / HDF5 / NPZ, flexible station-id and elevation column/array names) — the same parser the “Upload file” elevation source in pycsamt.app.web uses, so a file that works there also works here.

Parameters:
  • content (str or bytes) – A Dash dcc.Upload-style data URI ("data:...;base64,..."), raw base64 text, or raw bytes — anything parse_elevation_file() already accepts.

  • filename (str) – Used only for its extension (.csv/.h5/.hdf5/.npz).

Returns:

None when the file cannot be parsed (unrecognised format, missing id/elevation column) — matches parse_elevation_file()’s own best-effort, non-raising contract.

Return type:

TopographyPerStation or None

pycsamt.format.topography_to_elev_map(topo)#

Return {station_id: elevation}, the inverse of both builders.

The same shape pycsamt.map.topo.apply_elevations() and pycsamt.map.topo.parse_elevation_file() already use, so a PCSF file’s topography can be applied straight back onto a MapData with no extra conversion.

Parameters:

topo (TopographyPerStation)

Return type:

dict[str, float]

pycsamt.format.topography_from_grid(x, y, elevation)#

Build a gridded-DEM TopographyRaster.

Parameters:
  • x (ndarray (n_x,)) – Grid x-coordinates (or longitude), increasing.

  • y (ndarray (n_y,)) – Grid y-coordinates (or latitude), increasing.

  • elevation (ndarray (n_y, n_x)) – Elevation surface, metres, on the (y, x) meshgrid implied by x/y (numpy.meshgrid(x, y)’s default row-major convention).

Return type:

TopographyRaster

Examples

>>> import numpy as np
>>> from pycsamt.format.topography import topography_from_grid
>>> x = np.linspace(0.0, 500.0, 6)
>>> y = np.linspace(0.0, 300.0, 4)
>>> elevation = 100.0 + 0.01 * np.add.outer(y, x)
>>> topo = topography_from_grid(x, y, elevation)
>>> topo.elevation.shape
(4, 6)
pycsamt.format.topography_raster_to_grid(topo)#

Return (x, y, elevation), the inverse of topography_from_grid().

Parameters:

topo (TopographyRaster)

Return type:

tuple[ndarray, ndarray, ndarray]

class pycsamt.format.TopoTable(lon, lat, elevation=None, names=None, source='<unknown>')#

Bases: object

A parsed topo source, before it is matched to any station names.

Variables:
  • names (list of str, optional) – Station identity carried by the source itself. None for a positional-only source (e.g. a bare .bln).

  • lat (lon,) – WGS84 decimal degrees — already converted from easting/northing if the source needed that.

  • elevation (ndarray, shape (n,), optional) – Metres, nan where genuinely unknown.

  • source (str) – Human-readable provenance (file path, or a short description for an in-memory source), surfaced in error/warning messages.

Parameters:
lon: ndarray#
lat: ndarray#
elevation: ndarray | None = None#
names: list[str] | None = None#
source: str = '<unknown>'#
property n: int#
class pycsamt.format.TopoAttribution(lon=<factory>, lat=<factory>, elevation=<factory>, matched=<factory>, unmatched_stations=<factory>, source='none')#

Bases: object

Per-station real coordinates resolved from a topo source, ready to populate lon/lat (and merge into elevation).

Parameters:
lon: dict[str, float]#
lat: dict[str, float]#
elevation: dict[str, float]#
matched: list[str]#
unmatched_stations: list[str]#
source: str = 'none'#
pycsamt.format.read_topo_file(path, *, epsg=None, utm_zone=None, latlon=False)#

Parse a .bln/.csv/.stn topo file into a TopoTable.

Parameters:
  • path (path-like) – A .bln, .csv/.txt, or .stn file.

  • epsg (int, optional) – EPSG code of the source’s projected CRS, when it stores easting/northing rather than lat/lon. Takes precedence over utm_zone when both are given (matches pycsamt.gis.utils.to_ll()’s own precedence).

  • utm_zone (optional) – UTM zone designator (e.g. "32N"), an alternative to epsg.

  • latlon (bool, default False) – .bln only: set True when the file’s own x, y columns are already lon, lat (a .bln carries no CRS metadata to detect this from). Ignored for .csv/.stn, which are only treated as already-geographic when their own header says lat/lon.

Raises:

ValueError – Unrecognised extension, a malformed .bln header/body, no recognisable coordinate columns in a .csv/.stn file, or projected coordinates with neither epsg nor utm_zone.

Return type:

TopoTable

pycsamt.format.topo_from_sites(source)#

Extract lon/lat/elevation from an already-geo-located Sites/MapData/iterable-of-station-record object.

Duck-typed on purpose: works with anything iterable whose items (or whose .stations attribute’s items, for a MapData-like container) expose a station identifier (id/name/ station) plus longitude/latitude and, optionally, elevationpycsamt.map._core.StationRecord and similar objects all qualify without any adapter code.

Raises:

ValueError – If no station in source carries a usable id + lon/lat pair.

Parameters:

source (Any)

Return type:

TopoTable

pycsamt.format.attribute_topo(topo, station_names, *, on_mismatch='raise')#

Match a parsed TopoTable onto station_names.

Name-based when topo.names is populated (exact match, then a normalized fallback); positional (in order, requiring an exact count match) otherwise. See the module docstring for the full rationale.

Parameters:
  • on_mismatch ({"raise", "warn"}, default "raise") – Only consulted for a positional (name-less) source. "raise" rejects a station-count mismatch outright; "warn" issues a UserWarning and attributes only the overlapping prefix (min(topo.n, len(station_names)) points, in order).

  • topo (TopoTable)

  • station_names (Sequence[str])

Return type:

TopoAttribution

pycsamt.format.resolve_topo(topo, station_names, *, epsg=None, utm_zone=None, latlon=False, on_mismatch='raise')#

Resolve any accepted topo= argument into a TopoAttribution against station_names.

Parameters:
  • topo (None, path-like, TopoTable, Sites/MapData-like, mapping, or sequence) –

    • None – returns an empty attribution (every adapter’s existing station_elevations/station_lonlat behaviour is unaffected).

    • a .bln/.csv/.stn path, or an already-parsed TopoTable.

    • a Sites/MapData/iterable-of-station-record object (see topo_from_sites()).

    • a plain {station_name: (lon, lat[, elevation])} mapping.

    • when station_names is a mapping (multiline, {line_id: [names, ...]}): a {line_id: <any of the above>} mapping, or a sequence with exactly one source per line, in the same order as station_names’s own keys. A single non-mapping, non-per-line-sequence source is instead matched by name across all lines’ stations combined – the natural choice for one combined .stn/.csv/Sites source covering a whole multiline survey.

  • station_names (sequence of str, or mapping of str to sequence of str) – The inversion’s own station names (flat), or {line_id: names} for a multiline build.

  • epsg (int | None) – Forwarded to read_topo_file()/attribute_topo() for every file-based source encountered.

  • utm_zone (Any | None) – Forwarded to read_topo_file()/attribute_topo() for every file-based source encountered.

  • latlon (bool) – Forwarded to read_topo_file()/attribute_topo() for every file-based source encountered.

  • on_mismatch (str) – Forwarded to read_topo_file()/attribute_topo() for every file-based source encountered.

Returns:

Empty (all fields blank, source="none") when topo is None.

Return type:

TopoAttribution

class pycsamt.format.PointCloud(x, y, z, value, label)#

Bases: object

Flat point cloud ready for a 3-D scatter view.

Variables:
  • z (x, y,) – Position, metres. z is elevation-like (positive up) — callers plotting depth sections see negative values below the surface, matching the sign convention already used by pycsamt.app.web.callbacks.map3d’s own 3-D views.

  • value (ndarray, shape (n,)) – \(\log_{10}(\rho / \Omega\mathrm{m})\).

  • label (str) – Short description of what was plotted (geometry kind + any subsampling applied), for a status bar / axis title.

Parameters:
x: ndarray#
y: ndarray#
z: ndarray#
value: ndarray#
label: str#
pycsamt.format.pcsf_to_point_cloud(model, *, max_points=200000, seed=0)#

Flatten any PCSFModel geometry into one 3-D point cloud.

Parameters:
  • model (PCSFModel) – Any geometry kind.

  • max_points (int, default 200_000) – Random (seeded, reproducible) subsample cap — a native grid3d/mesh_unstructured model can carry hundreds of thousands of cells, too many for an interactive scatter plot.

  • seed (int, default 0) – Subsampling RNG seed, for a reproducible view across renders.

Returns:

Non-finite values (masked cells, log of non-positive resistivity) are dropped, not zeroed.

Return type:

PointCloud

Raises:

ValueError – If model.geometry.kind is not one of GEOMETRY_KINDS.

Examples

>>> import numpy as np
>>> from pycsamt.format import Grid2DGeometry, PCSFModel
>>> from pycsamt.format.pointcloud import pcsf_to_point_cloud
>>> geometry = Grid2DGeometry(x=np.array([0.0, 100.0]), z=np.array([10.0, 50.0]))
>>> model = PCSFModel(geometry=geometry, resistivity=np.array([[100.0, 110.0], [50.0, 55.0]]))
>>> cloud = pcsf_to_point_cloud(model)
>>> cloud.x.shape
(4,)
class pycsamt.format.ModelProvenance(architecture='', framework='', framework_version='', checkpoint='', checkpoint_sha256='', training_data='', hyperparameters=<factory>, random_seed=None, git_commit='', authors=<factory>, contact='', notes='', extra=<factory>)#

Bases: PyCSAMTObject

Describe the AI/DL model that produced a resistivity result.

Parameters:
  • architecture (str, optional) – Free-text model family/architecture, e.g. "UNet", "GCN", "ResNet18", or any third-party name.

  • framework (str, optional) – e.g. "pytorch"/"2.3.0", "tensorflow"/"2.16.1".

  • framework_version (str, optional) – e.g. "pytorch"/"2.3.0", "tensorflow"/"2.16.1".

  • checkpoint (str, optional) – Path or identifier of the trained weights used to produce the result (e.g. a filename, a model-hub id, a DOI).

  • checkpoint_sha256 (str, optional) – SHA-256 hex digest of the checkpoint file, so a reader can verify they are re-running the exact weights this result claims – see compute_checkpoint_hash().

  • training_data (str, optional) – Free-text reference to the training dataset (name, DOI, path).

  • hyperparameters (dict, default {}) – Free-form training/model hyperparameters.

  • random_seed (int, optional) – Seed used for training and/or inference, when reproducibility depends on it.

  • git_commit (str, optional) – Commit hash of the code that produced this result.

  • authors (list of str, default []) – Model authors/maintainers.

  • contact (str, optional) – Contact e-mail or URL for questions about this result.

  • notes (str, optional) – Free-text notes not covered by the fields above.

  • extra (dict, default {}) – Unmodelled provenance fields, retained losslessly.

Examples

>>> from pycsamt.format.provenance import ModelProvenance
>>> prov = ModelProvenance(
...     architecture="UNet",
...     framework="pytorch",
...     framework_version="2.3.0",
...     checkpoint="unet_v3.pt",
...     random_seed=42,
... )
>>> prov.to_dict()["architecture"]
'UNet'
architecture: str = ''#
framework: str = ''#
framework_version: str = ''#
checkpoint: str = ''#
checkpoint_sha256: str = ''#
training_data: str = ''#
hyperparameters: dict[str, Any]#
random_seed: int | None = None#
git_commit: str = ''#
authors: list[str]#
contact: str = ''#
notes: str = ''#
extra: dict[str, Any]#
validate()#

Validate object state.

Subclasses can override this hook. The base implementation intentionally accepts all states.

Return type:

None

to_dict()#

Plain-dict form, suitable for PCSFModel.metadata['model_provenance'].

Return type:

dict[str, Any]

pycsamt.format.compute_checkpoint_hash(path, *, chunk_size=1048576)#

SHA-256 hex digest of a model-checkpoint file, streamed in chunks.

Parameters:
  • path (path-like) – The checkpoint file to hash (e.g. a .pt/.h5/.onnx file).

  • chunk_size (int, default 1 MiB) – Read block size; large checkpoints are hashed without loading the whole file into memory.

Returns:

Lowercase hex digest, directly comparable to ModelProvenance.checkpoint_sha256.

Return type:

str

Examples

>>> from pycsamt.format.provenance import compute_checkpoint_hash
>>> compute_checkpoint_hash("unet_v3.pt")
'3b1c...'
pycsamt.format.mesh_to_grid2d(model, *, nx=200, nz=150)#

Regrid a mesh_unstructured PCSF model onto a rectilinear grid2d approximation.

Parameters:
  • model (PCSFModel) – A model with geometry.kind == "mesh_unstructured" and per-cell resistivity (shape (n_triangles,) – every pycsamt.format.adapters writer already produces this; a region-collapsed (n_regions,) array must be expanded onto each triangle’s region id first).

  • nx (int, default 200, 150) – Grid resolution. Chosen independently of the source mesh’s own resolution – there is no natural rectilinear resolution to inherit from an unstructured mesh – the same kind of pragmatic, documented default the rest of this format already makes (cf. PCSM’s row-width cap, the per-station curtain’s n_z default) rather than leaving it unbounded.

  • nz (int, default 200, 150) – Grid resolution. Chosen independently of the source mesh’s own resolution – there is no natural rectilinear resolution to inherit from an unstructured mesh – the same kind of pragmatic, documented default the rest of this format already makes (cf. PCSM’s row-width cap, the per-station curtain’s n_z default) rather than leaving it unbounded.

Returns:

A new grid2d model. Grid points outside the source mesh’s triangulation are nan. source_backend, created_by, and crs are carried over from model; description and metadata record that this is a synthesized regrid, not a native inversion output.

Return type:

PCSFModel

Raises:
  • ValueError – If model is not mesh_unstructured, or its resistivity is not already per-cell.

  • NotImplementedError – If the mesh’s plane is not "xz" – the only plane any current adapter produces; a general 3-D mesh has no single rectilinear plane to regrid onto.

Examples

>>> import numpy as np
>>> from pycsamt.format.schema import PCSFModel, UnstructuredMeshGeometry
>>> from pycsamt.format.regrid import mesh_to_grid2d
>>> geometry = UnstructuredMeshGeometry(
...     nodes=np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]),
...     connectivity=np.array([[0, 1, 2], [1, 3, 2]]),
...     region_ids=np.array([0, 1]),
... )
>>> model = PCSFModel(geometry=geometry, resistivity=np.array([10.0, 20.0]))
>>> regridded = mesh_to_grid2d(model, nx=5, nz=5)
>>> regridded.kind
'grid2d'
>>> regridded.resistivity.shape
(5, 5)

2.23.2. Schema and serialization#

pycsamt.format.schema

In-memory schema for the pyCSAMT Common Subsurface Format (PCSF).

pycsamt.format.io

HDF5 reader/writer for the pyCSAMT Common Subsurface Format (PCSF).

pycsamt.format.text

PCSM — ASCII sibling of PCSF (pyCSAMT Common Subsurface Markup).

2.23.3. Backend adapters#

The adapter namespace contains converters for supported inversion codes and generic constructors for classical, machine-learning, and deep-learning results that already expose their geometry and resistivity arrays.

pycsamt.format.adapters

Per-backend converters into pycsamt.format.schema.PCSFModel.

pycsamt.format.adapters.generic

Generic array-based -> PCSF adapter, for any AI/DL inversion result.

pycsamt.format.adapters.occam2d

Occam2D -> PCSF adapter (Phase 2 of the PCSF format plan).

pycsamt.format.adapters.modem3d

ModEM 3-D -> PCSF adapter (Phase 3 of the PCSF format plan).

pycsamt.format.adapters.mare2dem

MARE2DEM -> PCSF adapter (Phase 4 of the PCSF format plan).

2.23.4. Geometry and visualization support#

pycsamt.format.multiline

Multiline PCSF builder/reader — Phase 5 of the PCSF format plan.

pycsamt.format.pointcloud

Generic point-cloud extraction from any PCSFModel geometry kind.

pycsamt.format.regrid

Regridded grid2d convenience export for mesh_unstructured models.

pycsamt.format.topography

Topography helpers — per_station and raster PCSF kinds.

pycsamt.format.topo_source

Attach real per-station spatial coordinates (and elevation) to a PCSF adapter's StationTable from an external topo source.

2.23.5. Provenance#

pycsamt.format.provenance

Machine-learning-model provenance for a PCSF/PCSM AI-inversion result.

2.23.6. Borehole exchange#

The pycsamt.format.borehole namespace contains PCBH schema objects, canonical JSON I/O, CSV/LAS adapters, trajectory derivation, PCSF association, and application-neutral 3-D render/export contracts.

pycsamt.format.borehole

PCBH - pyCSAMT Common Borehole Format.

pycsamt.format.borehole.schema

In-memory schema and semantic validation for PCBH 0.1.

pycsamt.format.borehole.jsonio

Canonical UTF-8 JSON reader and writer for PCBH 0.1.

pycsamt.format.borehole.csvio

Combined interval-CSV importer for PCBH 0.1.

pycsamt.format.borehole.lasio

Loss-explicit LAS 2.0 subset import and export for PCBH.

pycsamt.format.borehole.relational

Manifest-driven relational CSV import and export for PCBH.

pycsamt.format.borehole.trajectory

Deterministic 3-D trajectory derivation for PCBH boreholes.

pycsamt.format.borehole.pcsf

PCBH association and coordinate alignment for PCSF models.

pycsamt.format.borehole.render

Viewer-neutral render models for PCBH boreholes.

pycsamt.format.borehole.exports

GeoJSON, VTP, and glTF exports for PCBH visualization subsets.