7.4. PCBH — Common Borehole Format#
A field project rarely contains only one simple vertical log. Mining, groundwater, geotechnical, and geothermal work may combine many collars, deviation surveys, geological intervals, structures, samples, assays, water observations, and construction records. A table can carry part of that information, but it cannot reliably state how every depth, coordinate, code, and relationship should be interpreted.
The pyCSAMT Common Borehole Format (PCBH) is the human-readable exchange
contract for that complete project. A canonical *.pcbh.json file contains
one or more boreholes and remains independent of an inversion model. It can be
read without pyCSAMT as ordinary UTF-8 JSON and checked against the public JSON
Schema at https://pycsamt.org/schemas/pcbh/0.1/schema.json.
This page belongs in the geology guide because PCBH describes observed and interpreted borehole evidence. PCSF — Common Subsurface Format describes PCSF, which stores a subsurface resistivity model. The two formats can be associated for 3-D viewing, but neither is silently converted into the other.
Important
pycsamt.geology.Borehole is the established lightweight vertical
log used by profile calibration. PCBH is the multi-hole spatial exchange
format. Adapters connect them, so existing calibration workflows do not
need to be rewritten. See Borehole logs for the lightweight API.
7.4.1. What the document fixes explicitly#
PCBH removes several common sources of geological ambiguity:
one horizontal CRS and vertical reference apply to the whole document;
collar
x,y, andzare spatial coordinates, with elevation positive upward;measured depth (MD) starts at the collar and increases along the hole;
survey inclination is measured from vertical down, so 0 degrees is vertical, 90 degrees is horizontal, and values above 90 describe upward/re-entry segments;
intervals are
[from_md, to_md)and may contain gaps, which mean unlogged or unknown material rather than zero thickness;observed, interpreted, derived, and unknown data are distinguished through
data_nature;geological codes are defined in document vocabularies, with optional color, description, physical properties, and external identifiers;
unknown vertical datum is written as
"unknown"rather than being treated as mean sea level.
The core document has the following logical shape. Extensions are preserved when a reader does not understand them, provided their keys are namespaced.
project.pcbh.json
├── pcbh_version, document_id, title, provenance
├── crs # horizontal + vertical reference
├── units and conventions
├── dictionaries
│ ├── lithologies
│ └── formations
├── boreholes[]
│ ├── id, name, kind, status
│ ├── collar # x, y, z
│ ├── total_depth_md, diameter
│ ├── trajectory/stations[] # md, azimuth, inclination
│ ├── interval_logs/*[]
│ ├── structures[]
│ └── extensions
└── extensions
Kinds such as water, mining, geotechnical, geothermal,
petroleum, monitoring, and exploration share this core. Domain
records that have not yet been standardized as core classes use namespaced
extensions such as pcbh:water, pcbh:construction, pcbh:samples,
and pcbh:assays. An extension never changes the meaning of a core field.
7.4.2. Reading and validating a project#
The packaged minimal example is a vertical 80 m water borehole in UTM zone 29N with two lithology intervals. The following session uses only public APIs and shows the actual object returned by the reference reader:
>>> from importlib import resources
>>> from pycsamt.format import read_pcbh
>>> example = resources.files("pycsamt.format.borehole").joinpath(
... "examples/minimal-vertical.pcbh.json"
... )
>>> document = read_pcbh(example)
>>> document.document_id, len(document.boreholes)
('example:minimal-vertical', 1)
>>> hole = document.boreholes[0]
>>> hole.id, hole.kind, hole.total_depth_md
('BH-001', 'water', 80.0)
>>> [(item.from_md, item.to_md, item.code)
... for item in hole.interval_logs["lithology"]]
[(0.0, 12.0, 'SOIL'), (12.0, 80.0, 'GRAN')]
read_pcbh checks UTF-8 JSON, duplicate keys, compatible version, object
shape, finite numbers, resource limits, and semantic relationships. Semantic
validation catches problems that JSON Schema alone cannot express, including
duplicate borehole IDs, unknown vocabulary codes, overlapping categorical
logs, non-increasing survey stations, and observations below total depth.
Warning
Do not call read_pcbh(..., validate=False) for routine exchange. That
option supports diagnostics and migration of damaged historical data; it
does not make an invalid document scientifically safe.
Writing canonical JSON is symmetric and atomic:
>>> from pathlib import Path
>>> from tempfile import TemporaryDirectory
>>> from pycsamt.format import write_pcbh
>>> with TemporaryDirectory() as directory:
... output = write_pcbh(document, Path(directory) / "project.pcbh.json")
... print(output.name, output.exists())
project.pcbh.json True
The writer validates before replacing the destination and emits indented,
stable UTF-8 JSON. .pcbh.json is canonical. An explicit reader may open a
file called .pcbh, but software must confirm its JSON content and version
rather than trust the suffix.
7.4.3. Building a document without Python#
The web application’s Interpret → Borehole Builder page exposes the same contract through project/CRS fields and editors for collars, surveys, intervals, structures, water, construction, samples, and assays. Validation messages link back to the affected row. The 2-D log and 3-D trajectory previews are derived from the current draft; they do not replace validation.
The builder can download canonical PCBH JSON or embed it in an uploaded PCSF model. Drafts are recovered in browser-local storage, while exported scientific files remain explicit user actions. CSV mapping profiles are also local editing preferences and are not silently written into geological observations.
7.4.4. Importing repeated borehole rows from CSV#
The combined CSV importer is intended for the common layout in which collar values repeat on each geological interval row. It recognizes conventional headers, or accepts an explicit mapping from canonical fields to source columns:
>>> from pycsamt.format import boreholes_from_csv, write_pcbh
>>> document, report = boreholes_from_csv(
... "drilling_intervals.csv",
... columns={
... "borehole.id": "HoleID",
... "collar.x": "Easting",
... "collar.y": "Northing",
... "collar.z": "Elevation",
... "interval.from_md": "From",
... "interval.to_md": "To",
... "interval.lithology": "Lithology",
... },
... constants={"crs.horizontal": "EPSG:32629"},
... )
>>> write_pcbh(document, "drilling_intervals.pcbh.json")
The returned report records the source checksum, resolved mappings, inferences, accepted and rejected rows, and localized warnings/errors. Repeated collars and total depths must agree within a hole. Missing values remain missing; an unavailable elevation is never silently replaced with zero.
Use strict=False only when a workflow deliberately accepts valid rows from
a partially damaged source and archives the complete rejection report. For a
relational exchange with separate collars, surveys, logs, and structures,
use write_csv_directory() and
boreholes_from_csv_directory(); their manifest
retains identity, CRS, units, checksums, and table relationships.
7.4.5. Trajectories, intervals, and structures#
A vertical hole uses trajectory.method = "vertical" and needs no survey
stations. A deviated hole uses at least two stations with strictly increasing
MD. desurvey() applies minimum curvature and
returns deterministic MD, true vertical depth, XYZ, azimuth, and inclination
points. Source azimuth remains tied to its declared true, grid,
magnetic, or unknown north reference; pyCSAMT never invents magnetic
declination or grid convergence.
The standard categorical log families are lithology, formation, weathering,
alteration, mineralization, oxidation, hydrostratigraphy, geotechnical, and
interpretation. Intervals cannot overlap within one family, while different
families may overlap because they describe different properties. Continuous
LAS curves are depth-indexed values rather than categorical intervals and are
preserved inline under pcbh:continuous_curves.
Structural observations can represent a point or a depth zone. Global planes use dip and dip direction; global lines use trend and plunge. Core-relative alpha/beta values remain explicitly tagged and must not be treated as global orientation until combined with the borehole attitude at the observation MD.
7.4.6. Using PCBH with legacy calibration#
Promotion from pycsamt.geology.Borehole requires an absolute PCBH
collar. The legacy x value is profile chainage, not an easting, and is
retained only as metadata["legacy_profile_x"]. Downgrade requires the
inverse choice explicitly:
>>> from pycsamt.format.borehole import legacy_borehole_views
>>> views = legacy_borehole_views(
... document,
... profile_x={"BH-001": 425.0},
... )
>>> views[0].name, views[0].x, len(views[0].intervals)
('BH-001', 425.0, 2)
This explicit mapping prevents an absolute UTM easting from being passed to a 2-D profile calibrator as chainage. A legacy view is necessarily lossy: it cannot retain Y, CRS, deviation, multiple log families, structures, or most provenance.
7.4.7. Associating boreholes with PCSF#
embed_pcbh() places a validated PCBH document
inside a copied PCSF model. reference_pcbh()
instead stores a URI and SHA-256 checksum, and the association may carry both.
The PCBH evidence and PCSF inversion remain independently identifiable.
Before a viewer inserts trajectories into a 3-D block,
align_pcbh_to_pcsf() transforms horizontal
coordinates into the PCSF frame, applies model origin and rotation, and
reports whether each path is inside, outside, or intersects the block. If the
vertical references cannot be proven compatible, alignment stops until the
caller supplies an explicit vertical_offset.
Warning
A visually plausible collar is not evidence of correct vertical alignment. Record the vertical datum or a justified offset before interpreting contacts against model depth cells.
The shared render contract produced by
build_render_model() supplies centerlines,
colored interval segments, contacts, structure glyphs, material batches, and
scientific hover values to both application front ends. Physical diameter and
minimum visible display radius remain separate so a narrow hole stays visible
without falsifying its true size.
7.4.8. Interchange and visualization exports#
PCBH JSON remains authoritative. The other outputs serve narrower consumers:
Output |
Best use |
Important limitation |
|---|---|---|
LAS 2.0 |
One hole’s depth-indexed logs |
Cannot preserve the complete multi-hole project and rich CRS model |
relational CSV |
Database and spreadsheet exchange |
Requires its manifest to retain relationships and units |
GeoJSON |
WGS84 collars and trajectory map features |
Vertical CRS and detailed logs are not native GeoJSON semantics |
VTP |
Scientific 3-D geometry and scalar arrays |
Not a complete borehole archive |
glTF/GLB |
Browser-ready colored 3-D presentation |
Presentation geometry is not the source geological contract |
Every visualization exporter returns a
PCBHExportReport. Applications should expose
its loss records rather than imply that a derived file is a lossless PCBH
replacement.
7.4.9. Versioning and independent use#
PCBH is currently version 0.1.0: a stabilized pre-1.0 contract. Readers
must reject unknown major/minor versions rather than guess. The schema,
packaged reference fixture, semantic validator, governance rules, and reader
guide are distributed with pyCSAMT; the normative specification is available
as pycsamt/format/borehole/SPEC.md in the source and installed package.
The public API reference is under pycsamt.format. Extension authors
should also read GOVERNANCE.md in the PCBH package: public extension keys
use owner:name, while the pcbh namespace is reserved for project-owned
fields.