Skip to content

cschrupp/wellplot

Repository files navigation

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
Loading

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

About

No description, website, or topics provided.

Resources

License

Stars

6 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages