Skip to main content

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 matplotlib and plotly renderer 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, and glyph objects
  • dedicated annotation label lanes for dense tracks

Architecture

The package separates three layers:

  • WellDataset: data and metadata normalized from LAS/DLIS inputs
  • LogDocument: 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 / DataFrame adapters 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_path is provided
    • render_png_bytes(...)
    • render_svg_bytes(...)
    • render_section_png(...)
    • render_track_png(...)
    • render_window_png(...)
  • 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.dataset
  • wellplot.api.builder
  • wellplot.api.render
  • wellplot.api.serialize

Current examples:

Important current boundary:

  • document_* helpers round-trip the normalized LogDocument template shape
  • report_* helpers round-trip logfile/programmatic layout mappings
  • save_* / 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:

Template + Savefile Model

wellplot now supports YAML template inheritance for logfile configs.

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-level data is 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, and tangential.
  • For log tracks, vertical grid can auto-follow scale bounds with: grid.vertical.main.spacing_mode: scale and grid.vertical.secondary.spacing_mode: scale. This adapts cycles and spacing for ranges like 2->200 vs 2->2000, including non-decade starts.
  • Use spacing_mode: count when you want fixed/manual line density independent of curve bounds.
  • Curves support wrapping across curve-capable tracks (reference, normal, array):
    • wrap: true to enable with default curve color.
    • wrap: { enabled: true, color: "#ef4444" } to color wrapped segments explicitly.
  • Curves support first-class fills:
    • between_curves for same-scale curve-vs-curve fills
    • between_instances for fills between specific rendered curve instances
    • to_lower_limit and to_upper_limit for fills to the active scale bounds
    • baseline_split for 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_100
    • fill.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, or top_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: curve for slim normalized overlay curves
    • mode: indicator for narrow indicator lanes
    • mode: ticks for 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.events for one-off markers such as casing shoe, readings start, or tool-state transitions.
  • Annotation tracks support first-class typed objects under tracks[*].annotations:
    • interval for facies/zone blocks
    • text for descriptive notes at a depth or over an interval
    • marker for symbol-based point events
    • arrow for explicit leader/indicator geometry
    • glyph for compact symbols or short codes
  • Annotation marker and arrow labels support:
    • priority for dense-track placement order
    • label_mode: free | dedicated_lane | none
    • label_lane_start / label_lane_end when the label must live in a reserved sub-lane
  • Callout repetition is section-relative. top, bottom, and top_and_bottom generate repeated depths from the log section bounds, then render each label inline at those generated depths.
  • Raster bindings support display controls:
    • profile (generic, vdl, or waveform)
    • normalization (auto, none, trace_maxabs, global_maxabs)
    • colorbar (true/false or { enabled, label, position })
    • sample_axis (true/false or { enabled, label, unit, ticks, min, max, source_origin, source_step })
    • waveform (true/false or { 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.heading
    • document.layout.remarks
    • document.layout.log_sections
    • document.layout.tail
  • Report heading and tail blocks are rendered from the shared report object:
    • heading renders the full cover/detail block
    • tail reuses the same data in a compact summary block
  • document.layout.remarks renders 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_titles accepts either plain strings or styled objects:
    • value
    • font_size
    • auto_adjust
    • bold
    • italic
    • alignment: 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_mm to export depth-continuous strip segments without vertical blank gaps while keeping readability.
  • Matplotlib visuals can be configured in YAML using render.matplotlib.style instead of hardcoded renderer values.
  • For DLIS array channels, sample_axis.min/max crops 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_origin
    • sample_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.heading and document.layout.tail share the same report object.
  • heading renders the full cover/detail block.
  • tail is only a toggle (document.layout.tail.enabled) and reuses the same report data in a compact summary block.
  • document.layout.remarks is a separate first-page report section for free-form notes.
  • The full heading selects exactly one detail table:
    • detail.kind: open_hole
    • detail.kind: cased_hole
  • Detail rows are fixed-row tables. Missing values stay empty; rows do not collapse.
  • Use label_cells when the left label column must be split.
  • Use columns[].cells when 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/DLIS
  • workspace/renders/ for generated PDF/HTML/JSON outputs The entire workspace/ 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

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wellplot-0.1.0.tar.gz (191.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wellplot-0.1.0-py3-none-any.whl (156.5 kB view details)

Uploaded Python 3

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

Hashes for wellplot-0.1.0.tar.gz
Algorithm Hash digest
SHA256 25f8607edfd7b55edc1a8e59d7120707b0cae8bd68e543748abfdeb0847efb50
MD5 f72f2c5e307faa3c5cd85a8455838642
BLAKE2b-256 0eb9fbf5021436be2426ebbfc0b4f4741c001c9e23825329edc7fcdad2abc203

See more details on using hashes here.

Provenance

The following attestation bundles were made for wellplot-0.1.0.tar.gz:

Publisher: release.yml on cschrupp/wellplot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for wellplot-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fb283468c163d1b56f627b1f7a1f33e972f07c26a6649f46880c5d5deea2bf63
MD5 b31c2081fcd3e1c53a1b11235e939070
BLAKE2b-256 a9818277bc1e5eedbb40d3747d80e612b247433e7a08f09b719176243f2da158

See more details on using hashes here.

Provenance

The following attestation bundles were made for wellplot-0.1.0-py3-none-any.whl:

Publisher: release.yml on cschrupp/wellplot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page