Printable and interactive well-log display toolkit for LAS and DLIS data.
Project description
wellplot
wellplot is an open-source Python toolkit for building printable and interactive well-log layouts from LAS and DLIS data.
The project is intentionally renderer-first:
- normalize subsurface data into typed channels
- describe the sheet using templates and layout specs
- render the same document to static and interactive backends
Status
This repository currently contains the current MVP baseline:
- normalized data objects for scalar, array, and raster channels
- a printable log document model with tracks, styles, headers, and footers
- YAML template loading
- a physical page layout engine
- optional
matplotlibandplotlyrenderer backends - optional LAS and DLIS ingestion adapters
- DLIS VDL/WF1-style array support with derived micro-time sample axes
- printable VDL density and waveform array rendering
- scale-aware curve fills, including crossover, limit, and baseline modes
- track-header fill indicators that mirror the actual plotted fill behavior
- in-track curve callouts with section-relative repetition and collision avoidance
- reference-track scalar overlay modes (
curve,indicator,ticks) - reference-track event objects for local markers such as casing foot or readings start
- annotation tracks with typed
interval,text,marker,arrow, andglyphobjects - dedicated annotation label lanes for dense tracks
Architecture
The package separates three layers:
WellDataset: data and metadata normalized from LAS/DLIS inputsLogDocument: page, depth, track, and annotation specifications- renderers: backend-specific drawing implementations that consume the same document
Workflow
flowchart LR
subgraph Inputs
LAS[LAS / DLIS files]
PD[pandas / numpy results]
YAML[YAML templates / savefiles]
end
subgraph DataLayer[Data layer]
DS[WellDataset]
OPS[alignment / merge / validation]
end
subgraph Compose[Composition layer]
BLD[LogBuilder]
DOC[ProgrammaticLogSpec / LogDocument]
end
subgraph Render[Render layer]
FULL[render_report]
PART[render_section / render_track / render_window]
BYTES[render_png_bytes / render_svg_bytes]
end
subgraph Outputs
PDF[PDF report]
IMG[PNG / SVG / notebook image]
SAVE[report/document YAML]
end
LAS --> DS
PD --> DS
DS --> OPS
OPS --> DS
YAML --> DOC
DS --> BLD
BLD --> DOC
DOC --> FULL
DOC --> PART
DOC --> BYTES
DS --> FULL
DS --> PART
DS --> BYTES
FULL --> PDF
PART --> PDF
BYTES --> IMG
DOC --> SAVE
The intended workflow is:
- ingest or compute channels into
WellDataset - validate, align, and merge the dataset as needed
- compose the layout with YAML or
LogBuilder - render a full report or a partial view
- optionally serialize the layout back to YAML
The next development phase adds two public API surfaces on top of those layers:
- dataset ingestion for computed numpy/pandas results
- programmatic composition/rendering so users can build logs without hand-authoring YAML
Track types are explicit: reference, normal, array, and annotation
(with compatibility aliases depth, curve, image).
Array tracks can host raster data and scalar overlays, while normal/reference tracks do not accept raster elements.
Reference tracks can host scalar overlay curves and local reference events while still defining the
layout axis.
Annotation tracks host lane-local interval, text, marker, arrow, and glyph objects instead of
channel bindings, and reuse the standard per-track grid configuration when you want the
background grid on or off.
Set page.continuous: true in templates to render a single continuous-depth PDF page.
Set page.track_header_height_mm to reserve a dedicated per-track header band.
Track headers now support explicit object slots (title, scale, legend) with enabled,
reserve_space, and line_units controls to prevent overlap while keeping fixed spacing.
Depth grid density in continuous mode is controlled by depth.major_step and depth.minor_step.
Use top-level markers and zones sections to draw formation and event annotations.
Development Workflow
This project uses uv for environment management and dependency resolution.
The package continues to support Python >=3.11.
CI validates the project on Python 3.11, 3.12, 3.13, and 3.14.
Create or update the environment:
uv sync
Install with optional LAS ingestion and PDF output:
uv sync --extra las --extra pdf --extra units
Install with optional pandas dataset adapters:
uv sync --extra pandas
With all optional backends:
uv sync --all-extras
Run tests:
uv run python -m unittest discover -s tests -v
Build and smoke-test the wheel:
uv build
python -m venv .smoke-venv
./.smoke-venv/bin/pip install --upgrade pip
./.smoke-venv/bin/pip install dist/*.whl
./.smoke-venv/bin/wellplot --help
MPLBACKEND=Agg ./.smoke-venv/bin/python scripts/smoke_installed_wheel.py
Format and lint:
uv run ruff format .
uv run ruff check .
Programmatic API
The programmatic API phase is now underway.
Currently implemented:
- dataset ingestion into
WellDataset - pandas
Series/DataFrameadapters on top of the dataset ingestion API - dataset alignment and normalization helpers:
sort_index(...)convert_index_unit(...)reindex_to(...)
- dataset update/merge helpers:
rename_channel(...)merge(..., collision=\"error|replace|rename|skip\")- merge history recorded in
dataset.provenance["merge_history"]
- in-memory layout composition with
LogBuilder - rendering through the project layout with
render_report(...) - partial renders with:
render_section(...)render_track(...)render_window(...)
- notebook-friendly outputs:
- returned Matplotlib figures when no
output_pathis provided render_png_bytes(...)render_svg_bytes(...)render_section_png(...)render_track_png(...)render_window_png(...)
- returned Matplotlib figures when no
- serialization helpers:
document_to_dict(...)/document_from_dict(...)document_to_yaml(...)/document_from_yaml(...)report_to_dict(...)/report_from_dict(...)report_to_yaml(...)/report_from_yaml(...)save_document(...)/load_document_yaml(...)save_report(...)/load_report(...)
- builder/report persistence helpers:
LogBuilder.save_yaml(...)ProgrammaticLogSpec.to_yaml(...)LogBuilder.add_section(..., source_path=..., source_format=...)
Current public modules:
wellplot.api.datasetwellplot.api.builderwellplot.api.renderwellplot.api.serialize
Current examples:
- examples/api_end_to_end_demo.py
- examples/api_dataset_ingest_demo.py
- examples/notebooks/api_dataset_ingest_demo.ipynb
- examples/api_dataset_alignment_demo.py
- examples/api_dataset_merge_demo.py
- examples/api_layout_render_demo.py
- examples/notebooks/api_layout_render_demo.ipynb
- examples/api_partial_render_demo.py
- examples/api_notebook_bytes_demo.py
- examples/api_serialize_demo.py
Important current boundary:
document_*helpers round-trip the normalizedLogDocumenttemplate shapereport_*helpers round-trip logfile/programmatic layout mappingssave_*/load_*convenience wrappers delegate to those same normalized surfaces- in-memory dataset contents are not embedded into YAML; YAML persists layout/report structure and optional section source references, while datasets remain separate Python objects or file-backed sources
The guiding rule is:
- YAML remains a first-class saved format
- the in-memory model becomes the canonical authoring surface
If you want one script that exercises the whole workflow, start with examples/api_end_to_end_demo.py. It ingests raw curves, computes secondary channels, aligns and merges them, renders a full report plus a window preview, and serializes the report YAML from the same Python session.
The full implementation checklist lives in docs/programmatic-api-plan.md.
For a user-facing workflow explanation, see docs/library-workflow.md.
For the package release workflow and TestPyPI/PyPI publishing process, see docs/release-process.md. Draft public release notes live in CHANGELOG.md.
Example Template
See examples/triple_combo.yaml. For scale/grid behavior examples, see examples/log_scale_options.log.yaml. For resistivity-style scales and wrapped log demo, see examples/resistivity_scale_conventions.log.yaml. For VDL density, waveform overlay, and feet-based comparison examples, see:
- examples/cbl_vdl_array_mvp.log.yaml
- examples/cbl_vdl_array_overlay.log.yaml
- examples/cbl_comparison_feet.log.yaml
- examples/cbl_comparison_feet_full.log.yaml For fill and callout examples, see:
- examples/fill_modes_showcase.log.yaml
- examples/cbl_feature_showcase_full.log.yaml
- examples/curve_callouts_showcase.log.yaml
- examples/curve_callout_bands_showcase.log.yaml
- examples/curve_callout_bands_full.log.yaml
- examples/reference_track_overlays.log.yaml For annotation-track examples, see:
- examples/annotation_track_showcase.log.yaml
- examples/annotation_track_showcase_no_grid.log.yaml
- examples/annotation_track_objects_showcase.log.yaml For a coherent end-to-end cased-hole packet using heading, remarks, main/repeat sections, reference overlays, thresholded CBL QC, VDL, and restrained interval annotations, see:
- examples/cbl_job_demo.log.yaml
Template + Savefile Model
wellplot now supports YAML template inheritance for logfile configs.
- Put reusable layout defaults in template files, for example:
- Create per-job savefiles that reference templates:
Savefiles use:
template:
path: ../templates/wireline_base.template.yaml
Behavior:
- Savefile values override template values.
- Tracks are defined in
document.layout.log_sections[*].tracks. - Data sources are section-scoped via
document.layout.log_sections[*].data. If top-leveldatais provided, it acts as a default for sections that do not set one. - Channels are assigned in
document.bindings.channels(channel+track_id). - Curve scales support
linear,log/logarithmic, andtangential. - For log tracks, vertical grid can auto-follow scale bounds with:
grid.vertical.main.spacing_mode: scaleandgrid.vertical.secondary.spacing_mode: scale. This adapts cycles and spacing for ranges like2->200vs2->2000, including non-decade starts. - Use
spacing_mode: countwhen you want fixed/manual line density independent of curve bounds. - Curves support wrapping across curve-capable tracks (
reference,normal,array):wrap: trueto enable with default curve color.wrap: { enabled: true, color: "#ef4444" }to color wrapped segments explicitly.
- Curves support first-class fills:
between_curvesfor same-scale curve-vs-curve fillsbetween_instancesfor fills between specific rendered curve instancesto_lower_limitandto_upper_limitfor fills to the active scale boundsbaseline_splitfor two-color fills around a vertical baseline
- Lower/upper limit fills are tied to the active scale bounds, not to the physical left/right side of the screen. Reversed scales still behave correctly.
- When you need a fill between two rendered copies of the same channel, assign explicit element ids:
id: cbl_0_100fill.other_element_id: cbl_0_10
- Track headers render fill indicators that follow the same semantics as the plotted fill, including crossover splits and baseline orientation.
- Curves support in-track callouts via
callouts:- inline labels at explicit depths
- repeated labels from section
top,bottom, ortop_and_bottom - side, text position, font, arrow, and offset controls
- hard edge avoidance, label-label avoidance, and soft curve-overlap avoidance
- Reference-track curve overlays support
reference_overlay:mode: curvefor slim normalized overlay curvesmode: indicatorfor narrow indicator lanesmode: ticksfor thresholded event-tick rendering from scalar channels
- Reference-track headers can now keep the reference scale row while rendering overlay properties in
the legend slot when
track_header.legend.enabled: true. - Curve header labels can opt into two-line wrapping with
document.bindings.channels[*].header_display.wrap_name: true, which is useful for narrow track headers such as reference-track overlay legends. - Reference tracks support local event objects under
reference.eventsfor one-off markers such as casing shoe, readings start, or tool-state transitions. - Annotation tracks support first-class typed objects under
tracks[*].annotations:intervalfor facies/zone blockstextfor descriptive notes at a depth or over an intervalmarkerfor symbol-based point eventsarrowfor explicit leader/indicator geometryglyphfor compact symbols or short codes
- Annotation
markerandarrowlabels support:priorityfor dense-track placement orderlabel_mode: free | dedicated_lane | nonelabel_lane_start/label_lane_endwhen the label must live in a reserved sub-lane
- Callout repetition is section-relative.
top,bottom, andtop_and_bottomgenerate repeated depths from the log section bounds, then render each label inline at those generated depths. - Raster bindings support display controls:
profile(generic,vdl, orwaveform)normalization(auto,none,trace_maxabs,global_maxabs)colorbar(true/falseor{ enabled, label, position })sample_axis(true/falseor{ enabled, label, unit, ticks, min, max, source_origin, source_step })waveform(true/falseor{ enabled, stride, amplitude_scale, color, line_width, max_traces, fill, positive_fill_color, negative_fill_color, invert_fill_polarity })
- Multiple curves per track are supported by assigning multiple bindings to the same
track_id. - Section placeholders are first-class in YAML:
document.layout.headingdocument.layout.remarksdocument.layout.log_sectionsdocument.layout.tail
- Report heading and tail blocks are rendered from the shared report object:
headingrenders the full cover/detail blocktailreuses the same data in a compact summary block
document.layout.remarksrenders page-level notes/remarks in the lower half of the first page and is intended for disclaimers, acquisition notes, or other summary text.header.report.service_titlesaccepts either plain strings or styled objects:valuefont_sizeauto_adjustbolditalicalignment: left | center | right
- Template YAML files can be partial; the merged savefile result is what gets validated and rendered.
- Page spacing is YAML-configurable:
document.page.margin_left_mm(default:0)document.page.track_gap_mm(default:0)
- Track-header legend space now auto-expands based on curve count in each track.
- For continuous logs in PDF viewers, set
render.continuous_strip_page_height_mmto export depth-continuous strip segments without vertical blank gaps while keeping readability. - Matplotlib visuals can be configured in YAML using
render.matplotlib.styleinstead of hardcoded renderer values. - For DLIS array channels,
sample_axis.min/maxcrops the actual waveform/raster columns to the selected window. It does not relabel the full array. - When DLIS tool metadata exposes micro-time sampling, the loader derives the sample axis
automatically. When vendor output still needs alignment tuning, the final user can override:
sample_axis.source_originsample_axis.source_step
Example VDL binding with explicit user-tunable sample axis:
document:
bindings:
channels:
- section: main
channel: VDL
track_id: vdl
kind: raster
profile: vdl
style:
colormap: gray_r
sample_axis:
enabled: false
unit: us
source_origin: 40
source_step: 10
min: 200
max: 1200
ticks: 7
waveform:
enabled: true
stride: 6
amplitude_scale: 0.28
line_width: 0.16
Example report service titles with explicit formatting:
document:
layout:
heading:
provider_name: Company
service_titles:
- value: Cement Bond Log
font_size: 16
auto_adjust: true
bold: true
alignment: left
- value: Variable Density Log
font_size: 15
auto_adjust: true
italic: true
alignment: center
- value: Gamma Ray - CCL
font_size: 14
auto_adjust: true
alignment: right
Report page authoring rules:
document.layout.headinganddocument.layout.tailshare the same report object.headingrenders the full cover/detail block.tailis only a toggle (document.layout.tail.enabled) and reuses the same report data in a compact summary block.document.layout.remarksis a separate first-page report section for free-form notes.- The full heading selects exactly one detail table:
detail.kind: open_holedetail.kind: cased_hole
- Detail rows are fixed-row tables. Missing values stay empty; rows do not collapse.
- Use
label_cellswhen the left label column must be split. - Use
columns[].cellswhen a value column must be split into subcells.
Example report block:
document:
layout:
heading:
enabled: true
provider_name: Company
general_fields:
- key: company
label: Company
value: University of Utah
- key: well
label: Well
value: Forge 78B-32
- key: scale
label: Scale
value: ft 1:240
service_titles:
- value: Cement Bond Log
font_size: 16
auto_adjust: true
bold: true
alignment: left
detail:
kind: open_hole
rows:
- label_cells:
- Density
- Viscosity
columns:
- cells:
- G/L
- S
- cells:
- G/L
- S
- label: Logged Depth
values:
- ""
- ""
remarks:
- title: Remarks
lines:
- Summary note line 1.
- Summary note line 2.
alignment: left
tail:
enabled: true
Example instance-targeted fill between two rendered copies of the same channel:
document:
bindings:
channels:
- section: main
channel: CBL
track_id: cbl_fill
kind: curve
id: cbl_0_100
scale: { kind: linear, min: 0, max: 100 }
fill:
kind: between_instances
other_element_id: cbl_0_10
label: Scale Effect
crossover:
enabled: true
left_color: "#1f9d55"
right_color: "#d64545"
- section: main
channel: CBL
track_id: cbl_fill
kind: curve
id: cbl_0_10
scale: { kind: linear, min: 0, max: 10 }
Example curve callout with section-relative repetition:
document:
bindings:
channels:
- section: main
channel: CBL
track_id: cbl
kind: curve
scale: { kind: linear, min: 0, max: 100 }
callouts:
- depth: 672
label: CBL
placement: top_and_bottom
distance_from_top: 500
distance_from_bottom: 500
every: 1000
side: right
text_x: 0.83
font_size: 10.5
Example reference-track overlays and local events:
document:
layout:
log_sections:
- id: main
tracks:
- id: depth_overlay
kind: reference
width_mm: 20
reference:
axis: depth
define_layout: true
unit: ft
scale_ratio: 240
events:
- depth: 678
label: Casing Foot
tick_side: right
text_side: left
text_x: 0.72
track_header:
objects:
- kind: scale
enabled: true
line_units: 1
- kind: legend
enabled: true
line_units: 6
bindings:
channels:
- section: main
channel: TT
track_id: depth_overlay
kind: curve
reference_overlay:
mode: curve
lane_start: 0.06
lane_end: 0.24
- section: main
channel: TENS
track_id: depth_overlay
kind: curve
reference_overlay:
mode: indicator
lane_start: 0.78
lane_end: 0.94
- section: main
channel: CBL
track_id: depth_overlay
kind: curve
reference_overlay:
mode: ticks
tick_side: left
tick_length_ratio: 0.08
threshold: 100
render:
backend: matplotlib
output_path: ../workspace/renders/job.pdf
dpi: 300
matplotlib:
style:
track_header:
background_color: "#efefef"
track:
x_tick_labelsize: 7.5
grid:
depth_major_linewidth: 0.8
Real Data Demo
Use the master loader (single command for any log-file YAML):
uv run python -m wellplot.cli render examples/cbl_main.log.yaml
Validate a log-file against the JSON Schema before rendering:
uv run python -m wellplot.cli validate examples/cbl_main.log.yaml
Optional output override:
uv run python -m wellplot.cli render examples/cbl_main.log.yaml -o out.pdf
Convenience wrapper:
uv run examples/real_data_demo.py
Or pass a specific log file:
uv run examples/real_data_demo.py examples/cbl_main.log.yaml
Array-track demo with synthetic VDL data and logfile config:
uv run examples/cbl_vdl_array_mvp_demo.py
Use templates/wireline_base.template.yaml as a reusable layout template, then create/modify job savefiles like examples/cbl_main.log.yaml. Keep local input/output assets under:
workspace/data/for LAS/DLISworkspace/renders/for generated PDF/HTML/JSON outputs The entireworkspace/folder is excluded from git. Note: DLIS normalization now supports scalar channels plus VDL/WF1-style array channels with derived sample axes. Exact micro-time origin can still be tuned per savefile when matching vendor-generated logs.
Project Memory
- docs/decision-log.md: agreed architectural and product decisions.
- docs/roadmap.md: phased development plan and near-term priorities.
- docs/rendering-workings.md: rendering flow and style-resolution model.
- docs/library-workflow.md: user-facing workflow of the library from data ingestion through rendering and YAML persistence.
- docs/programmatic-api-plan.md: concrete checklist for dataset ingestion, programmatic composition, partial renders, and notebook outputs.
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wellplot-0.1.0.tar.gz.
File metadata
- Download URL: wellplot-0.1.0.tar.gz
- Upload date:
- Size: 191.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25f8607edfd7b55edc1a8e59d7120707b0cae8bd68e543748abfdeb0847efb50
|
|
| MD5 |
f72f2c5e307faa3c5cd85a8455838642
|
|
| BLAKE2b-256 |
0eb9fbf5021436be2426ebbfc0b4f4741c001c9e23825329edc7fcdad2abc203
|
Provenance
The following attestation bundles were made for wellplot-0.1.0.tar.gz:
Publisher:
release.yml on cschrupp/wellplot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wellplot-0.1.0.tar.gz -
Subject digest:
25f8607edfd7b55edc1a8e59d7120707b0cae8bd68e543748abfdeb0847efb50 - Sigstore transparency entry: 1357898269
- Sigstore integration time:
-
Permalink:
cschrupp/wellplot@47b1e5bb014b3c0423fafde6acaafda550d7ad3e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cschrupp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@47b1e5bb014b3c0423fafde6acaafda550d7ad3e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file wellplot-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wellplot-0.1.0-py3-none-any.whl
- Upload date:
- Size: 156.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb283468c163d1b56f627b1f7a1f33e972f07c26a6649f46880c5d5deea2bf63
|
|
| MD5 |
b31c2081fcd3e1c53a1b11235e939070
|
|
| BLAKE2b-256 |
a9818277bc1e5eedbb40d3747d80e612b247433e7a08f09b719176243f2da158
|
Provenance
The following attestation bundles were made for wellplot-0.1.0-py3-none-any.whl:
Publisher:
release.yml on cschrupp/wellplot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wellplot-0.1.0-py3-none-any.whl -
Subject digest:
fb283468c163d1b56f627b1f7a1f33e972f07c26a6649f46880c5d5deea2bf63 - Sigstore transparency entry: 1357898595
- Sigstore integration time:
-
Permalink:
cschrupp/wellplot@47b1e5bb014b3c0423fafde6acaafda550d7ad3e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cschrupp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@47b1e5bb014b3c0423fafde6acaafda550d7ad3e -
Trigger Event:
workflow_dispatch
-
Statement type: