Metadata-Version: 2.4
Name: wellplot
Version: 0.3.0
Summary: Printable and interactive well-log display toolkit for LAS and DLIS data.
Author: Carlos Schrupp
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/cschrupp/wellplot
Project-URL: Documentation, https://wellplot.readthedocs.io/en/latest/
Project-URL: Source, https://github.com/cschrupp/wellplot
Project-URL: Issues, https://github.com/cschrupp/wellplot/issues
Keywords: well-log,petrophysics,las,dlis,visualization
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: jsonschema>=4.23
Requires-Dist: matplotlib>=3.10.8
Requires-Dist: numpy>=1.26
Requires-Dist: PyYAML>=6.0
Provides-Extra: las
Requires-Dist: lasio>=0.31; extra == "las"
Requires-Dist: setuptools<81,>=68; extra == "las"
Requires-Dist: welly>=0.5.2; extra == "las"
Provides-Extra: dlis
Requires-Dist: dlisio>=1.0; extra == "dlis"
Provides-Extra: pdf
Requires-Dist: matplotlib>=3.8; extra == "pdf"
Provides-Extra: interactive
Requires-Dist: plotly>=5.18; extra == "interactive"
Provides-Extra: mcp
Requires-Dist: mcp<2,>=1; extra == "mcp"
Provides-Extra: notebook
Requires-Dist: ipykernel>=6.29; extra == "notebook"
Provides-Extra: pandas
Requires-Dist: pandas>=2.2; extra == "pandas"
Provides-Extra: units
Requires-Dist: pint>=0.24; extra == "units"
Provides-Extra: all
Requires-Dist: lasio>=0.31; extra == "all"
Requires-Dist: setuptools<81,>=68; extra == "all"
Requires-Dist: welly>=0.5.2; extra == "all"
Requires-Dist: dlisio>=1.0; extra == "all"
Requires-Dist: matplotlib>=3.8; extra == "all"
Requires-Dist: plotly>=5.18; extra == "all"
Requires-Dist: mcp<2,>=1; extra == "all"
Requires-Dist: ipykernel>=6.29; extra == "all"
Requires-Dist: pandas>=2.2; extra == "all"
Requires-Dist: pint>=0.24; extra == "all"
Dynamic: license-file

# 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

```mermaid
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:

```bash
python -m pip install wellplot
```

Install optional adapters and notebook support as needed:

```bash
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:

```bash
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:

```bash
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:

```json
{
  "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:

```bash
uv sync
```

Install with optional LAS ingestion and PDF output:

```bash
uv sync --extra las --extra pdf --extra units
```

Install with optional pandas dataset adapters:

```bash
uv sync --extra pandas
```

With all optional backends:

```bash
uv sync --all-extras
```

Run tests:

```bash
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:

```bash
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:

```bash
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:
- [examples/api_end_to_end_demo.py](examples/api_end_to_end_demo.py)
- [examples/api_dataset_ingest_demo.py](examples/api_dataset_ingest_demo.py)
- [examples/notebooks/developer/api_dataset_ingest_demo.ipynb](examples/notebooks/developer/api_dataset_ingest_demo.ipynb)
- [examples/api_dataset_alignment_demo.py](examples/api_dataset_alignment_demo.py)
- [examples/api_dataset_merge_demo.py](examples/api_dataset_merge_demo.py)
- [examples/api_layout_render_demo.py](examples/api_layout_render_demo.py)
- [examples/notebooks/developer/api_layout_render_demo.ipynb](examples/notebooks/developer/api_layout_render_demo.ipynb)
- [examples/api_partial_render_demo.py](examples/api_partial_render_demo.py)
- [examples/api_notebook_bytes_demo.py](examples/api_notebook_bytes_demo.py)
- [examples/api_serialize_demo.py](examples/api_serialize_demo.py)
- [examples/mcp_workflow_demo.py](examples/mcp_workflow_demo.py)
- [examples/notebooks/developer/mcp_workflow_demo.ipynb](examples/notebooks/developer/mcp_workflow_demo.ipynb)

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](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](docs/programmatic-api-plan.md).

For a user-facing workflow explanation, see
[docs/library-workflow.md](docs/library-workflow.md).

For the package release workflow and TestPyPI/PyPI publishing process, see
[docs/release-process.md](docs/release-process.md).
Draft public release notes live in [CHANGELOG.md](CHANGELOG.md).

## Example Template

See [examples/triple_combo.yaml](examples/triple_combo.yaml).
For scale/grid behavior examples, see [examples/log_scale_options.log.yaml](examples/log_scale_options.log.yaml).
For resistivity-style scales and wrapped log demo, see
[examples/resistivity_scale_conventions.log.yaml](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_mvp.log.yaml)
- [examples/cbl_vdl_array_overlay.log.yaml](examples/cbl_vdl_array_overlay.log.yaml)
- [examples/cbl_comparison_feet.log.yaml](examples/cbl_comparison_feet.log.yaml)
- [examples/cbl_comparison_feet_full.log.yaml](examples/cbl_comparison_feet_full.log.yaml)
For fill and callout examples, see:
- [examples/fill_modes_showcase.log.yaml](examples/fill_modes_showcase.log.yaml)
- [examples/cbl_feature_showcase_full.log.yaml](examples/cbl_feature_showcase_full.log.yaml)
- [examples/curve_callouts_showcase.log.yaml](examples/curve_callouts_showcase.log.yaml)
- [examples/curve_callout_bands_showcase.log.yaml](examples/curve_callout_bands_showcase.log.yaml)
- [examples/curve_callout_bands_full.log.yaml](examples/curve_callout_bands_full.log.yaml)
- [examples/reference_track_overlays.log.yaml](examples/reference_track_overlays.log.yaml)
For annotation-track examples, see:
- [examples/annotation_track_showcase.log.yaml](examples/annotation_track_showcase.log.yaml)
- [examples/annotation_track_showcase_no_grid.log.yaml](examples/annotation_track_showcase_no_grid.log.yaml)
- [examples/annotation_track_objects_showcase.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](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:
  - [templates/wireline_base.template.yaml](templates/wireline_base.template.yaml)
- Create per-job savefiles that reference templates:
  - [examples/cbl_main.log.yaml](examples/cbl_main.log.yaml)

Savefiles use:

```yaml
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:

```yaml
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:

```yaml
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:

```yaml
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:

```yaml
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:

```yaml
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:

```yaml
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
```

```yaml
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):

```bash
wellplot render examples/cbl_main.log.yaml
```

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

```bash
wellplot validate examples/cbl_main.log.yaml
```

Optional output override:

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

Convenience wrapper:

```bash
python examples/real_data_demo.py
```

Or pass a specific log file:

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

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

```bash
python examples/cbl_vdl_array_mvp_demo.py
```

Use [templates/wireline_base.template.yaml](templates/wireline_base.template.yaml) as a reusable
layout template, then create/modify job savefiles like
[examples/cbl_main.log.yaml](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

- [docs/decision-log.md](docs/decision-log.md): agreed architectural and product decisions.
- [docs/roadmap.md](docs/roadmap.md): phased development plan and near-term priorities.
- [docs/rendering-workings.md](docs/rendering-workings.md): rendering flow and style-resolution model.
- [docs/library-workflow.md](docs/library-workflow.md): user-facing workflow of the library from
  data ingestion through rendering and YAML persistence.
- [docs/programmatic-api-plan.md](docs/programmatic-api-plan.md): concrete checklist for dataset
  ingestion, programmatic composition, partial renders, and notebook outputs.

## License

Apache-2.0
