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
  • optional experimental MCP server support for local logfile validation, previews, starter example export, and canonical YAML save workflows
  • 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 public interaction layers now sit on top of that same render pipeline:

  • YAML templates and savefiles for declarative jobs
  • the Python surfaces exposed through wellplot and wellplot.api
  • the experimental stdio MCP server exposed through wellplot[mcp]

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.

Install From PyPI

Most users should install the published package from PyPI:

python -m pip install wellplot

Install optional adapters and notebook support as needed:

python -m pip install "wellplot[las]"
python -m pip install "wellplot[dlis]"
python -m pip install "wellplot[pandas]"
python -m pip install "wellplot[interactive]"
python -m pip install "wellplot[notebook]"
python -m pip install "wellplot[mcp]"

Common example-workflow installs:

python -m pip install "wellplot[las,notebook]"
python -m pip install "wellplot[dlis,notebook]"
python -m pip install "wellplot[las,dlis,pandas,notebook,interactive,units]"

Experimental MCP Server

The optional MCP surface is experimental and is exposed only through the optional dependency extra and the stdio entry point.

Install it with:

python -m pip install "wellplot[mcp]"

The server root is fixed to the current working directory when wellplot-mcp starts. Logfile paths, referenced source data, exported example bundles, saved YAML files, and explicit render outputs must all resolve inside that root.

Typical client registration:

{
  "mcpServers": {
    "wellplot": {
      "command": "wellplot-mcp",
      "cwd": "/absolute/path/to/job-root"
    }
  }
}

The MCP tool surface currently supports:

  • validate_logfile and inspect_logfile
  • preview_logfile_png plus explicit section/track/window preview tools
  • render_logfile_to_file
  • export_example_bundle
  • validate_logfile_text, format_logfile_text, and save_logfile_text

Packaged example resources and guided prompts are also exposed for MCP-aware clients.

Contributor 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
uv run --with mcp pytest tests/test_mcp_service.py tests/test_mcp_server.py

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

python -m venv .smoke-venv-mcp
./.smoke-venv-mcp/bin/pip install --upgrade pip
./.smoke-venv-mcp/bin/pip install dist/*.whl "mcp>=1,<2"
MPLBACKEND=Agg ./.smoke-venv-mcp/bin/python scripts/smoke_installed_wheel.py

Format and lint:

uv run ruff format .
uv run ruff check .

Programmatic API

The public programmatic API supports dataset construction, layout composition, rendering, partial renders, notebook bytes, and YAML serialization.

Implemented surfaces:

  • 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):

wellplot render examples/cbl_main.log.yaml

Validate a log-file against the JSON Schema before rendering:

wellplot validate examples/cbl_main.log.yaml

Optional output override:

wellplot render examples/cbl_main.log.yaml -o out.pdf

Convenience wrapper:

python examples/real_data_demo.py

Or pass a specific log file:

python examples/real_data_demo.py examples/cbl_main.log.yaml

Array-track demo with synthetic VDL data and logfile config:

python 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.3.0.tar.gz (216.0 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.3.0-py3-none-any.whl (182.3 kB view details)

Uploaded Python 3

File details

Details for the file wellplot-0.3.0.tar.gz.

File metadata

  • Download URL: wellplot-0.3.0.tar.gz
  • Upload date:
  • Size: 216.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wellplot-0.3.0.tar.gz
Algorithm Hash digest
SHA256 570ed0f0591576bc01220cf50802f751e91b02b7d08ac0a124964aabc2b62c47
MD5 ccc86661f45ae3e6e74bce12030bc9d5
BLAKE2b-256 c6743320048341f81170042b76ec96971be724cbd508cedd52cbce07f2269f4a

See more details on using hashes here.

Provenance

The following attestation bundles were made for wellplot-0.3.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.3.0-py3-none-any.whl.

File metadata

  • Download URL: wellplot-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 182.3 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 865640b894cbcf9da67e3ce2f3d21fbc9bf2d181ecfc2ef243d109bac44c12da
MD5 1846d3b37ad174fabd02eab14e821058
BLAKE2b-256 2f9d79e3a6d5437aac9f4bbfcbef6141baf76133a8344faac2e8514a9a23cfdc

See more details on using hashes here.

Provenance

The following attestation bundles were made for wellplot-0.3.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