Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ env:

jobs:
build-and-push:
name: Build Docker image
name: Build ${{ matrix.variant }} Docker image
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- variant: lean
dockerfile: docker/Dockerfile
suffix: ''
- variant: larcv
dockerfile: docker/Dockerfile.larcv
suffix: '-larcv'
permissions:
contents: read
packages: write
Expand All @@ -40,16 +50,18 @@ jobs:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
# For PRs, just tag with pr-number (don't push)
type=ref,event=pr
# For version tags: v0.1.1 -> 0.1.1, 0.1, latest
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=ref,event=pr,suffix=${{ matrix.suffix }}
# Lean: 1.2.0, 1.2, latest. LArCV: 1.2.0-larcv, 1.2-larcv, larcv.
type=semver,pattern={{version}},suffix=${{ matrix.suffix }}
type=semver,pattern={{major}}.{{minor}},suffix=${{ matrix.suffix }}
type=raw,value=latest,enable=${{ matrix.variant == 'lean' && startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=larcv,enable=${{ matrix.variant == 'larcv' && startsWith(github.ref, 'refs/tags/v') }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.dockerfile }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,26 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [1.2.0] - 2026-09-18

### Added
- **Direct LArCV input**: Load LArCV2 ROOT files through SPINE v1.2.4 using an explicitly selected, versioned detector conversion bundle from `spine-prod`.
- **LArCV container flavor**: Publish a separate ROOT/LArCV-enabled image while retaining the existing lean image and tags as the default.

### Changed
- **Docker layout**: Keep both image definitions, the local run helper, and container-build documentation under `docker/`.
- **Contextual LArCV configuration**: Request a detector converter only after opening a detected ROOT source, leaving the control hidden for other source types.
- **Source navigation**: Start newly opened files at entry 0 instead of carrying over the previous file's entry index.
- **Unified source entry**: Accept server paths and public URLs through one Path field, using the existing status indicator for remote loading.
- **Remote ROOT staging**: Prompt for a LArCV converter from a remote ``.root`` URL before downloading it, then validate its content while the standard loading indicator reports the transfer.
- **Converter labels**: Distinguish multiple detector-production converters using tags maintained by spine-prod rather than duplicating detector knowledge in Spinal Tap.

### Fixed
- **Particle vertices from LArCV**: Build truth interactions alongside particles so the interaction-vertex overlay works in particle mode.
- **Cached URL inspection**: Refresh temporary-file retention without changing modification timestamps, preserving reader and built-event cache hits when inspecting objects from downloaded sources.
- **LArCV converter errors**: Validate required ROOT trees before constructing LArCV chains, reporting incompatible converter selections in the application instead of emitting ROOT-only terminal diagnostics.
- **LArCV fragment mode**: Preserve SPINE's particle dependency when building interactions alongside truth fragments for vertex-capable displays.
- **LArCV manifests**: Request and preserve one converter for homogeneous manifests of LArCV ROOT files.

## [1.1.2] - 2026-09-12

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Spinal Tap is a Dash application that provides simple visualization tools for
the Scalable Particle Imaging With Neural Embeddings
([SPINE](https://github.com/DeepLearnPhysics/spine)) package.

The standard installation and container remain lean and read SPINE HDF5 files.
An optional LArCV container adds ROOT, LArCV2, and maintained
[`spine-prod`](https://github.com/DeepLearnPhysics/spine-prod) detector
conversion bundles so raw LArCV ROOT files can be visualized directly.


## Installation

Expand Down Expand Up @@ -85,12 +90,26 @@ Docker images are automatically built and published to GitHub Container Registry
docker pull ghcr.io/deeplearnphysics/spinal-tap:latest
```

Use the separate LArCV flavor only when direct ROOT input is needed:

```bash
docker pull ghcr.io/deeplearnphysics/spinal-tap:larcv
```

To run locally with Docker:

```bash
docker run -p 8888:8888 ghcr.io/deeplearnphysics/spinal-tap:latest
```

For LArCV input, run the LArCV image, mount the source data, and select the
matching dated detector converter in the **Data** panel:

```bash
docker run --rm -p 8888:8888 -v /host/data:/data:ro \
ghcr.io/deeplearnphysics/spinal-tap:larcv
```

## Development & CI/CD

- Code style is enforced with black, isort, and flake8 (pre-commit and CI).
Expand Down
File renamed without changes.
40 changes: 40 additions & 0 deletions docker/Dockerfile.larcv
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# syntax=docker/dockerfile:1

ARG LARCV_TAG=runtime-ubuntu24.04
ARG SPINE_PROD_REF=3cb7ff9ac5fb1baf1a5958bc67e36be616adab52

FROM alpine:3.22 AS configs
ARG SPINE_PROD_REF
ADD https://github.com/DeepLearnPhysics/spine-prod/archive/${SPINE_PROD_REF}.tar.gz /tmp/spine-prod.tar.gz
RUN mkdir -p /opt/spine-prod && \
tar -xzf /tmp/spine-prod.tar.gz --strip-components=1 -C /opt/spine-prod

FROM ghcr.io/deeplearnphysics/larcv2:${LARCV_TAG}

USER root
WORKDIR /app

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_BREAK_SYSTEM_PACKAGES=1 \
SPINE_CONFIG_PATH=/opt/spine-prod/config

RUN apt-get update && \
apt-get install -y --no-install-recommends python3-pip && \
rm -rf /var/lib/apt/lists/*

COPY pyproject.toml MANIFEST.in README.md ./
COPY src/ ./src/

# Keep the NumPy ABI used to compile LArCV's PyUtil bridge. Declaring the exact
# already-installed version prevents pip from trying to replace the Debian
# package with a newer, independently built NumPy wheel.
RUN pip3 install "numpy==1.26.4" .

COPY --from=configs /opt/spine-prod/config /opt/spine-prod/config

EXPOSE 8888
ENTRYPOINT ["spinal-tap"]
CMD ["--host", "0.0.0.0", "--port", "8888"]
40 changes: 31 additions & 9 deletions docs/DOCKER_STRATEGY.md → docker/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
# Docker Image Build Strategy

## Repository Layout

All container-specific build files live in this directory:

- `Dockerfile` builds the lean SPINE HDF5 viewer.
- `Dockerfile.larcv` builds the optional ROOT/LArCV flavor.
- `run.sh` builds and starts the lean image for local development.

Both Dockerfiles use the repository root as their build context:

```bash
docker build -f docker/Dockerfile -t spinal-tap:local .
docker build -f docker/Dockerfile.larcv -t spinal-tap:larcv-local .
```

The shared `.dockerignore` remains at the repository root because Docker reads
it from the build-context root, not from the Dockerfile directory.

## Build Triggers

The Docker image is built and published **only when**:

1. **A version tag is pushed** (e.g., `v0.1.1`, `v1.0.0`)
- Creates versioned tags: `0.1.1`, `0.1`, and `latest`
- Builds both the lean and LArCV image flavors
- Creates lean tags: `0.1.1`, `0.1`, and `latest`
- Creates LArCV tags: `0.1.1-larcv`, `0.1-larcv`, and `larcv`

2. **Pull requests** (for testing only)
- Builds the image but doesn't push to registry
Expand All @@ -32,12 +52,16 @@ When you create a tag like `v0.1.1`:
ghcr.io/deeplearnphysics/spinal-tap:0.1.1 (full version)
ghcr.io/deeplearnphysics/spinal-tap:0.1 (minor version)
ghcr.io/deeplearnphysics/spinal-tap:latest (latest release)
ghcr.io/deeplearnphysics/spinal-tap:0.1.1-larcv (LArCV full version)
ghcr.io/deeplearnphysics/spinal-tap:0.1-larcv (LArCV minor version)
ghcr.io/deeplearnphysics/spinal-tap:larcv (latest LArCV release)
```

Benefits:
- Users can pin to exact version: `image: spinal-tap:0.1.1`
- Users can auto-update minor versions: `image: spinal-tap:0.1`
- Users can always get latest: `image: spinal-tap:latest`
- ROOT/LArCV dependencies remain isolated in the `-larcv`/`larcv` flavor

## Automatic Cleanup

Expand All @@ -55,12 +79,9 @@ The workflow includes automatic cleanup:
- ✅ For public packages: **No practical limit**

### Image Size Optimization:
Our Dockerfile uses:
- **Python slim base** (~150MB vs 1GB for full Python)
- **Multi-stage builds** would further reduce size (can add if needed)
- **Layer caching** - Only changed layers are rebuilt

Current estimated image size: **~500MB - 1GB** (depending on spine dependencies)
The lean Dockerfile uses a Python slim base and remains free of ROOT and LArCV.
The optional multi-stage LArCV Dockerfile starts from the dedicated
LArCV runtime image and copies only the `spine-prod` configuration tree.

## Workflow Summary

Expand All @@ -69,9 +90,10 @@ Developer creates tag v0.1.1
↓
GitHub Action triggered
↓
Build Docker image
Build lean and LArCV Docker images
↓
Push with tags: 0.1.1, 0.1, latest
Push lean tags: 0.1.1, 0.1, latest
Push LArCV tags: 0.1.1-larcv, 0.1-larcv, larcv
↓
Cleanup old untagged images
↓
Expand Down
8 changes: 5 additions & 3 deletions docker_run.sh → docker/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ set -e
IMAGE_NAME="spinal-tap"
IMAGE_TAG="local"
PORT=8888
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

echo "🔨 Building Docker image..."
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
docker build -f "${REPO_ROOT}/docker/Dockerfile" \
-t "${IMAGE_NAME}:${IMAGE_TAG}" "${REPO_ROOT}"

echo "✅ Build complete!"
echo ""
Expand All @@ -18,6 +20,6 @@ echo "🛑 Press Ctrl+C to stop"
echo ""

docker run --rm -it \
-p ${PORT}:${PORT} \
-p "${PORT}:${PORT}" \
--name spinal-tap-dev \
${IMAGE_NAME}:${IMAGE_TAG}
"${IMAGE_NAME}:${IMAGE_TAG}"
7 changes: 4 additions & 3 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ one browser application.

The application is intended for three common workflows:

* inspect a SPINE HDF5 file on the same machine as the server;
* inspect a SPINE HDF5 file, or a LArCV ROOT file with the optional container;
* share a reproducible event view with another collaborator; and
* deploy a controlled, authenticated display beside experiment data.

Expand Down Expand Up @@ -52,8 +52,9 @@ Install and launch Spinal Tap:
spinal-tap

Open ``http://localhost:8888``, enter an HDF5 path in **Path**, select **Open**,
and navigate with **Go** or the left/right arrows. See :doc:`sources` for
manifests, uploads, URLs, and shared view JSON files.
and navigate with **Go** or the left/right arrows. The optional LArCV image can
open ROOT input after selecting its detector conversion bundle. See
:doc:`sources` for manifests, uploads, URLs, and shared view JSON files.

Choosing a renderer
-------------------
Expand Down
16 changes: 15 additions & 1 deletion docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,21 @@ Released images are published to the GitHub Container Registry:
docker run --rm -p 8888:8888 \
ghcr.io/deeplearnphysics/spinal-tap:latest

Mount data into the container when using **Path** sources:
The default image is the lean SPINE HDF5 viewer and does not include ROOT or
LArCV. A separate image supports direct LArCV ROOT input:

.. code-block:: bash

docker pull ghcr.io/deeplearnphysics/spinal-tap:larcv
docker run --rm -p 8888:8888 \
-v /host/data:/data:ro \
ghcr.io/deeplearnphysics/spinal-tap:larcv

Release-specific tags use ``<version>-larcv``. The LArCV image includes the
ROOT/LArCV runtime and versioned detector conversion bundles from
``spine-prod``; the Python package and default image deliberately do not.

Mount data into the container when using local **Path** sources:

.. code-block:: bash

Expand Down
6 changes: 3 additions & 3 deletions docs/source/sharing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ View JSON
---------

**Export JSON** downloads the same reproducible view state as a file. Open that
JSON through Path, Browse/Upload, or URL just like any other source. Loading the
view opens its underlying HDF5 source and applies the saved state once the event
is available.
JSON through Path or Browse/Upload just like any other source. The Path field
also accepts URLs. Loading the view opens its underlying HDF5 source and applies
the saved state once the event is available.

The view is a starting point rather than a lock: after it loads, normal event
navigation preserves the user's current camera behavior instead of repeatedly
Expand Down
43 changes: 37 additions & 6 deletions docs/source/sources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,19 @@ arrows to load entries.
- Use it for
- Behavior
* - Path
- A path or glob visible to the server.
- Opens the host file directly without copying it.
- A server-visible path or glob, or a public HTTP(S) URL.
- Opens paths directly. Downloads URLs into the bounded temporary cache
before opening them.
* - Browse
- A local file selected with the operating-system picker.
- Transfers the selected file into a private temporary cache. This label
is used by an unauthenticated local deployment.
* - Upload
- A workstation file sent to an authenticated hosted deployment.
- Uses the same private temporary cache as Browse, in retryable chunks.
* - URL
- A public HTTP or HTTPS source.
- Downloads the source into the bounded temporary cache before opening it.

Pressing Enter in a Path or URL field performs the same action as **Open**.
The Path field also accepts public URLs. Pressing Enter performs the same action
as **Open**.
Pressing Enter in the event selector performs the same action as **Go**.

Accepted source documents
Expand All @@ -38,9 +37,41 @@ Accepted source documents
Spinal Tap identifies exact files by content rather than extension. It accepts:

* SPINE HDF5 output;
* LArCV2 ROOT input when running the LArCV container flavor;
* a saved Spinal Tap view JSON document; or
* a UTF-8 file manifest.

LArCV conversion bundles
------------------------

LArCV tree names and detector geometry are producer-specific. Open a ROOT
source first; after Spinal Tap detects its content, it reveals **LArCV
converter** beneath the Open button and waits for a matching dated bundle.
Selecting the bundle resumes the load automatically and dismisses the chooser.
When a detector has multiple bundles, their labels include compatibility tags
maintained by ``spine-prod``, such as cryostat layout, CRT availability, and
optical-system generation.
For a remote URL whose path ends in ``.root``, the chooser appears immediately
and the download begins only after selection, with progress reported by the
standard status indicator. The suffix is only an early UI hint: Spinal Tap
still validates the downloaded content. Extensionless remote sources must be
downloaded before their type can be detected. The control stays hidden for
other source types.
Spinal Tap then parses and builds the SPINE truth representation in memory; it
does not write an intermediate HDF5 file. A manifest may contain several LArCV
files that use the same bundle, but it cannot mix LArCV and HDF5 files.

Detector conversion knowledge is maintained in ``spine-prod`` and copied into
the LArCV image at build time. Install an additional configuration tree by
setting ``SPINAL_TAP_LARCV_CONFIG_ROOT`` (or ``SPINE_CONFIG_PATH``) to its
``config`` directory. ``SPINAL_TAP_LARCV_CONFIG`` can set a default converter
ID or an explicit bundle path for non-interactive deployments.

Spinal Tap intentionally does not guess a detector or schema from tree names:
different production vintages can use overlapping names with different
semantics. If a bundle reports missing trees, select the matching vintage or
add the required variant in ``spine-prod``.

A manifest contains one source path per line. Blank lines and records beginning
with ``#`` are ignored. Relative records are resolved beside the manifest, and
globs are supported both in the Path field and in manifest records. The file
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ keywords = [
"WebGL"
]
dependencies = [
"spine[viz]>=1.2.1",
"spine[viz]>=1.2.4",
"dash>=4.4.0",
"flask>=2.0.0",
"flask-session>=0.5.0",
Expand Down
Loading
Loading