Metadata-Version: 2.4
Name: sldread
Version: 0.1.0
Summary: Eager, process-isolated reader for SlideBook .sld microscopy files via Bio-Formats, with metadata and optional napari support
Project-URL: Homepage, https://github.com/unige-biochem/sldread
Project-URL: Repository, https://github.com/unige-biochem/sldread
Project-URL: Issues, https://github.com/unige-biochem/sldread/issues
Author-email: Nicolas Chiaruttini <nicolas.chiaruttini@gmail.com>
License: MIT
License-File: LICENSE
Keywords: bio-formats,imaging,microscopy,napari,ome,sld,slidebook
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Scientific/Engineering :: Image Processing
Requires-Python: >=3.10
Requires-Dist: numpy
Requires-Dist: scyjava
Provides-Extra: napari
Requires-Dist: napari; extra == 'napari'
Requires-Dist: pyqt6; extra == 'napari'
Description-Content-Type: text/markdown

# sldread

Read SlideBook `.sld` microscopy containers in Python via Bio-Formats, with the
vendor SlideBook reader jar added to the JVM classpath.

A `.sld` file is a proprietary container holding multiple images (*captures* /
*scenes*). `sldread` reads a chosen scene **eagerly** into a NumPy array
`(C, Z, Y, X)` together with basic metadata (scene/channel names, physical pixel
sizes), and can hand a scene to [napari](https://napari.org).

Runtime dependencies are minimal: **`numpy` + `scyjava` only**. `scyjava` pulls
in JPype, provisions a JVM (via `cjdk`) on first use, and fetches Bio-Formats
from Maven. `napari` is an optional extra.

## Why does this repo exist? (and when it should disappear)

This is **not meant to be yet another microscopy reader.** It exists only to work
around a single, specific bug in the upstream SlideBook Bio-Formats reader:

- The native reader (`loci.formats.in.SlideBook6Reader`) keeps a **single,
  process-global file session**. It is not re-entrant and not safe across the
  open/close/reopen and threading patterns that modern lazy I/O stacks rely on.
- Because of this, **[bioio](https://github.com/bioio-devs/bioio) /
  `bioio-bioformats` cannot read SlideBook pixels reliably**: their lazy/dask
  path opens short-lived helper readers and suspends/reopens the file, which
  trips the global session (`AssertionError: File already open.` /
  `File not open.`). It is fundamentally a threading / multiple-open problem, not
  something a wrapper can configure away.
- The only reliable pattern is **one uninterrupted `setId() → openBytes() →
  close()` session per process**. `sldread` does exactly that, eagerly, in an
  isolated subprocess — nothing more.

So `sldread` is deliberately tiny and single-purpose. **If the upstream issue is
fixed** — i.e. a future SlideBook reader build gives each reader an independent
native session so that reopen-after-close and concurrent/threaded readers work —
then this package becomes pointless and **bioio should be used directly instead.
At that point this repository will be archived and forgotten.** Please prefer the
upstream tools the moment they work. (The bug has been reported to the vendor, 3i.)

> **Read this first.** SlideBook's native reader has a hard constraint that
> shapes the whole design — see [Limitations](#limitations-and-what-not-to-implement).
> In short: a `.sld` file can only be read reliably in **one uninterrupted
> reader session per process**, so lazy/dask access does not work. `sldread`
> reads eagerly and isolates each read in its own subprocess.

## Install

```bash
pip install sldread
# with napari support:
pip install "sldread[napari]"
```

Or with [uv](https://docs.astral.sh/uv/) for development:

```bash
uv sync
```

Requires Python ≥ 3.10 (the floor set by `scyjava`).

### The SlideBook reader jar (proprietary — not bundled)

The `SlideBook6Reader.jar` is **proprietary and is NOT redistributed** with this
package. You must obtain it yourself, subject to the vendor's (3i / SlideBook)
terms. `sldread` locates it, in order, from:

1. the `jar_path=` argument (or `--jar` on the CLI),
2. the `SLIDEBOOK_READER_JAR` environment variable,
3. `./jars/SlideBook6Reader.jar` (relative to the working directory).

A convenience downloader is provided (standard-library only, no extra deps),
which fetches the pinned build from the SlideBook ImageJ update site:

```bash
sld-read download-jar            # -> ./jars/SlideBook6Reader.jar
sld-read download-jar /some/dir  # custom destination
```

Downloading the jar is your responsibility and governed by the vendor's
licensing terms; this convenience command does not grant any rights to it.

The verified build is `SlideBook6Reader.jar-20250730183505` from
`https://sites.imagej.net/SlideBook/jars/bio-formats/`.

## Usage

### Library

```python
from sldread import list_sld_scenes, read_sld_scene, sld_scene_to_napari

list_sld_scenes("file.sld")                  # -> ['undiff_WT_...', ...]

data, meta = read_sld_scene("file.sld", scene=0)
# data: numpy array, axis order (C, Z, Y, X)
# meta: scene_name, n_scenes, all_scene_names, channel_names,
#       physical_pixel_sizes {z, y, x in µm}, scale_zyx, shape, dtype,
#       dims_order, n_timepoints, timepoint

# napari (each channel becomes a layer, with physical scale on Z, Y, X):
import napari
img, kwargs, _ = sld_scene_to_napari("file.sld", scene=0)[0]
napari.view_image(img, **kwargs)
```

Public API:

```python
read_sld_scene(path, scene=0, *, t=0, jar_path=None) -> (ndarray (C,Z,Y,X), dict)
list_sld_scenes(path, *, jar_path=None) -> list[str]
sld_scene_to_napari(path, scene=0, *, t=0, jar_path=None) -> napari LayerData list
napari_get_reader(path) -> reader callable or None
```

If `sldread[napari]` is installed, the `.sld` reader is also registered as a
napari plugin (via a napari manifest), so files open through napari's
File → Open.

A ready-to-run demo lives in [`examples/view_scene_napari.py`](examples/view_scene_napari.py):

```bash
uv run --extra napari python examples/view_scene_napari.py path/to/file.sld 5
```

### Command line

```bash
sld-read list "file.sld"           # list scenes
sld-read read "file.sld" 0         # read scene 0, print a summary
sld-read read "file.sld" 0 --view  # open scene 0 in napari (needs the napari extra)
sld-read download-jar              # fetch the reader jar into ./jars
```

The jar is found via `--jar`, `SLIDEBOOK_READER_JAR`, or `./jars/`.

## Limitations (and what NOT to implement)

These come from the **SlideBook native Bio-Formats reader**
(`loci.formats.in.SlideBook6Reader`), not from this code:

- **One open per process.** The reader keeps a single, process-global native
  file session. Opening the same file a second time in one process is unreliable
  (intermittent `AssertionError: File already open.`), and closing one of two
  readers that have the file open deterministically breaks the other
  (`AssertionError: File not open.`). The only reliable pattern is a single,
  uninterrupted `setId() -> openBytes()... -> close()` session — which is why
  each read here runs in a fresh subprocess. (Reported upstream to the vendor, 3i.)
- **Eager only.** Reads load a whole scene into memory (≈ `C·Z·Y·X·bytes`; e.g.
  ~99 MB for a 3×12×1200×1200 uint16 scene). Fine for typical scenes; not
  suitable for very large time-lapses in one shot.
- **Single timepoint by default.** `read_sld_scene` reads `t=0`; pass `t=` for
  another timepoint. All Z and channels of that timepoint are read.
- **No RGB/interleaved planes.** Expects one sample per plane (true for the
  SlideBook files seen here) and raises `NotImplementedError` otherwise, rather
  than returning wrong data.

**Do not** spend time on the following — they are blocked by the reader
limitation above, not by missing effort:

- **Lazy / dask-backed loading** of SlideBook pixels. Any access pattern that
  opens the file more than once per process, or keeps a reader open and reopens
  it later, fails non-deterministically.
- **A long-lived shared reader** reused across many calls in one process.
- **Using `bioio` / `bffile` for pixel data.** Their lazy pixel path opens/closes
  helper readers and hits the one-open-per-process limit.

If a future SlideBook build gives each reader an independent native session,
these constraints can be revisited — until then, eager + process-isolated is the
correct design.

## Development

```bash
uv sync
uv run pytest                 # API tests run anywhere; the read test is gated
uv build                      # build sdist + wheel into dist/
```

The end-to-end read test is skipped unless both `SLIDEBOOK_READER_JAR` and
`SLDREAD_TEST_SLD` (a sample `.sld` file) are set, so CI and other contributors
can run the suite without proprietary data.

### Publishing

```bash
uv build
uv publish                    # uploads dist/* to PyPI
```

`uv publish` accepts a token via `--token` / `UV_PUBLISH_TOKEN`, or use PyPI
[trusted publishing](https://docs.pypi.org/trusted-publishers/) from CI (no
long-lived token needed).

## License

MIT (this package). The SlideBook reader jar is **not** covered by this license
and is not distributed here.
