Skip to main content

Multi-view light sheet microscopy image processing pipeline

Project description

IsoView Light Sheet Microscopy Pipeline

Input is a flat directory of raw SPC##_TM#####_ANG###_CM#_CHN##_PH#.stack files (little-endian uint16) with a push_config XML per channel describing the acquisition (dimensions, pixel/axial spacing, wavelength, exposure, stage, etc.).

Quickstart

  1. Point input_dir in pipeline/correct_stack.py at your raw data directory.
  2. Run pipeline/correct_stack.py (correction) then pipeline/multi_fuse.py (fusion).
  3. Each step appends its config + isoview_version to isoview_config.json next to the data, so every run is reproducible.

Usage

Two entrypoints, both take one ProcessingConfig: run correct_stack(config) then multi_fuse(config). Mode (timelapse vs tiled) is auto-detected from the raw filenames — you do not set it. Editable templates live in pipeline/correct_stack.py and pipeline/multi_fuse.py.

Timelapse (multiple TM, one or more specimens)

from pathlib import Path
from isoview import ProcessingConfig, correct_stack, multi_fuse

config = ProcessingConfig(
    input_dir=Path(r"E:\isoview\dataset"),  # flat SPC##_TM#####_*.stack files
    timepoints=None,                        # default=None · auto-detect all TM## (or pass [0, 1, 2])
    workers=4,                              # default=1 · process timepoints in parallel
    blending_method="geometric",            # default="geometric"
)

correct_stack(config)   # -> dataset.corrected/SPM00/TM000000/...
multi_fuse(config)      # -> dataset.fused/SPM00/TM000000/...

Tiled (single TM, multiple SPC)

Same calls — auto-detected as tiled. Per-specimen crop overrides go through tile_crops, keyed by SPM##, then crop param, then camera index:

config = ProcessingConfig(
    input_dir=Path(r"E:\isoview\tiled_dataset"),
    tile_crops={
        "SPM00": {"crop_front": {0: 40}, "crop_depth": {0: 450}},
        "SPM01": {"crop_depth": {1: 382, 3: 400}},
    },
)

correct_stack(config)   # -> tiled_dataset.corrected/SPM00/, SPM01/, ...
multi_fuse(config)      # -> tiled_dataset.fused/SPM00/, ...

BigStitcher export (optional stage 3)

Export the two fused views as a BigDataViewer/BigStitcher dataset, then register VW00↔VW90 in the BigStitcher GUI (see bigstitcher.md):

from isoview import generate_bigstitcher_xml

generate_bigstitcher_xml(config)   # -> dataset.stitcher/dataset.xml + dataset.ome.zarr/

All parameters

Every field with its default and tuning guidance. leave = don't change unless you have a reason (hardware/structural/auto-derived); tune = worth adjusting per dataset, with a suggested range. The Gaussian segmentation kernels (gauss_kernel, gauss_sigma) should be left as-is.

config = ProcessingConfig(
    # paths & selection
    input_dir=Path(r"E:\isoview\dataset"),  # required · folder of flat SPC##_TM#####_*.stack files
    output_dir=None,            # default=None · auto = <raw>.corrected[_suffix]; leave
    projection_dir=None,        # default=None · auto; leave
    specimen=0,                 # default=0 · timelapse specimen index; leave
    specimens=None,             # default=None · auto-detect SPC##; leave
    timepoints=None,            # default=None · auto-detect TM##; set a list for a subset
    cameras=None,               # default=None · derived from camera_pairs; leave
    views=[0, 90],              # default=[0, 90] · hardware-defined; leave
    output_suffix="",           # default="" · names a variant (.corrected_<x>/.fused_<x>); optional
    stitcher_suffix="",         # default="" · BigStitcher dir variant; leave

    # output format
    output_format="zarr",       # default="zarr" · zarr | tif | klb (pick one, not a tuning knob)
    compression="zstd",         # default="zstd" · zstd|lzw|deflate (tif), blosc-zstd (zarr), or None
    compression_level=3,        # default=3 · 1–22 zstd / 1–9 others · tune 1–9, diminishing past ~6
    zarr_chunks=None,           # default=None · auto (1, Y, X) one plane/chunk; leave unless tuning IO
    zarr_shards=None,           # default=None · auto; leave
    pyramid=True,               # default=True · leave
    pyramid_max_layers=4,       # default=4 · 0–~6; leave

    # parallelism & logging
    workers=1,                  # default=1 · 1–CPU cores · TUNE to machine; fusion is RAM-heavy, use fewer
    log=True,                   # default=True · leave
    overwrite=False,            # default=False · True to recompute existing outputs

    # dead-pixel correction
    median_kernel=(3, 3),       # default=(3, 3) · leave (None disables); larger blurs real signal
    background_percentile=5.0,  # default=5.0 · 0–~20 · leave (dark-current estimate)
    mask_percentile=1.0,        # default=1.0 · 0–~5 · leave
    subsample_factor=100,       # default=100 · 1–~1000 · speed only; higher = faster/coarser percentiles

    # segmentation
    segment_mode=1,             # default=1 · 0=off, 1=generate+save masks (2/3 not implemented)
    gauss_kernel=5,             # default=5 · LEAVE AS-IS
    gauss_sigma=2.0,            # default=2.0 · LEAVE AS-IS
    segment_threshold=0.4,      # default=0.4 · 0–1 · MAIN TUNE: lower = more foreground (try 0.2–0.6)
    splitting=10,               # default=10 · 1+ · memory knob; raise if OOM (more slabs = less RAM)
    apply_segmentation_mask=False,  # default=False · True zeros background; masks are saved either way

    # transforms (fusion; applied to the 2nd camera in each pair)
    rotation=0,                 # default=0 · -1|0|1 = 90ccw|none|90cw · hardware-defined; leave
    flip_horizontal=True,       # default=True · hardware-defined; leave
    flip_vertical=False,        # default=False · leave
    flip_z=False,               # default=False · True if opposing cameras need a Z flip

    # camera-camera blending
    blending_method="geometric",  # default="geometric" · geometric | adaptive | average (or auto)
    blending_range=4,           # default=4 · 1–~20 · tune 2–10 (Z-plane transition width)
    transition_plane=None,      # default=None=center · set a Z-index to move the crossover (geometric)
    front_flag=1,               # default=1 · 1|2 · which camera is sharp at low Z; flip if wrong

    # registration (coarse grid search + gradient-descent refine)
    search_offsets_x=(-50, 50, 10),  # default=(-50, 50, 10) · (start, stop, step) px · widen/finer if misaligned
    search_offsets_y=(-50, 50, 10),  # default=(-50, 50, 10) · same

    # microscope (read from XML when present)
    pixel_spacing_z=None,       # default=None · XML axial_step; set only if no XML
    detection_objective_mag=None,   # default=None · XML objective mag; set only if no XML
    pixel_spacing_camera=6.5,   # default=6.5 · sensor pixel size (µm); leave
    camera_view_map=None,       # default=None -> {0:0, 1:0, 2:90, 3:90}; leave
    camera_pairs=None,          # default=None -> [(0, 1), (2, 3)]; change only for different wiring

    # cropping (fusion; keyed by CAMERA index 0=CM00, 1=CM01, ...)
    crop_front=None,            # default=None · {camera: z-start}
    crop_depth=None,            # default=None · {camera: z-count}
    crop_top=None,              # default=None · {camera: y-start}
    crop_height=None,           # default=None · {camera: y-count}
    crop_left=None,             # default=None · {camera: x-start}
    crop_width=None,            # default=None · {camera: x-count}
    tile_crops=None,            # default=None · per-specimen overrides {"SPM03": {"crop_depth": {1: 460}}}

    # diagnostics
    do_tenengrad=False,         # default=False · True for per-Z focus (Tenengrad) QC plots
    diagnostics_dir=None,       # default=None · auto = output_dir/diagnostics; leave
)

Per-view fusion overrides — blending_method_by_view, blending_range_by_view, transition_plane_by_view, front_flag_by_view, flip_z_by_view, flip_horizontal_by_view, flip_vertical_by_view, rotation_by_view, search_offsets_x_by_view, search_offsets_y_by_view — each take a {view: value} dict (0=VW00, 90=VW90) and override the matching scalar above for that view only.

Acquisition Modes

Raw input is always flat — SPC##_TM#####_ANG###_CM#_CHN##_PH#.stack files in a single directory. Mode is auto-detected from SPC/TM counts:

Condition Mode Description
Multiple TM values timelapse time series, any number of specimens
Single TM + multiple SPC tiled spatial tiles, one timepoint
Single TM + single SPC single treated as timelapse with 1 timepoint

XML Metadata

read_xml_metadata(xml_path) parses one push_config XML; read_all_xml_metadata(input_dir, specimen) returns (common, per_camera) — fields equal across cameras vs camera-specific.

Field Type Source Notes
data_header str @data_header acquisition session label
specimen_name str @specimen_name
timestamp str @timestamp acquisition datetime
time_point int @time_point
specimen_XYZT str @specimen_XYZT also parsed → stage_x/y/z (float, µm)
angle float @angle
camera_index str @camera_index comma-separated per camera
camera_type str @camera_type
camera_roi str @camera_roi
wavelength str @wavelength emission, per camera
illumination_arms str @illumination_arms per camera
illumination_filter str @illumination_filter
exposure_time float @exposure_time ms
detection_filter str @detection_filter
detection_objective str @detection_objective also parsed → objective_mag (float)
dimensions ndarray @dimensions (n_cameras, 3) from "WxHxD,WxHxD,…"
z_step float @z_step µm; VW00 (Z-scan)
y_step float @y_step µm; VW90 (Y-scan)
stack_direction str @stack_direction drives camera_view_map
planes str @planes
laser_power str @laser_power
experiment_notes str @experiment_notes
zplanes int derived dimensions[0][-1]
fps float derived 1000.0 / exposure_time
vps float derived fps / zplanes
camera_pixel_size_um float constant 6.5 (Hamamatsu C11440-22C)
pixel_resolution_um float derived camera_pixel_size_um / objective_mag
axial_step float merged unifies z_step / y_step across XMLs
camera_view_map dict synthesized from stack_direction: Z-scan → {0:0,1:0}, Y-scan → {2:90,3:90}

XML discovery order (in read_all_xml_metadata): ch*_spec{NN}.xmlch*.xml*_CHN*.xml. Channel inferred from filename via ch(\d+)CHN(\d+)VW(\d+).

Filename Tags

Tag Meaning Raw Corrected Fused
SPC## / SPM## specimen / tile SPC00 SPM00 SPM00
TM###### timepoint TM00000 TM000000 TM000000
CM## camera CM0 CM00 CM00_CM01 (pair)
CHN## acquisition channel CHN00, CHN01 CHN00, CHN01 CHN00, CHN01
VW## fused view (scan axis) VW00 (z-scan), VW90 (y-scan)
ANG### illumination angle ANG000
PH# phase PH0

Correction iterates over all (camera, channel) pairs present in the raw data, so dual-channel acquisitions (same camera capturing multiple wavelengths) produce one corrected volume per (CM, CHN). Fusion fuses each camera pair separately for every channel both cameras share.

Pipeline & Output Layout

Three stages run in sequence, each writing a sibling directory next to the raw input. The data reduces at every step — 4 cameras → 2 fused views → 1 registered volume:

Stage Call Output dir Reduces
1. Correction correct_stack <raw>.corrected/ raw 4 cameras → 4 corrected volumes (CM00–CM03)
2. Fusion multi_fuse <raw>.fused/ 4 cameras → 2 orthogonal views (VW00, VW90)
3. Stitch export generate_bigstitcher_xml <raw>.stitcher/ 2 views → 1 registered volume (in BigStitcher)
  • .corrected — per-camera correction: dead-pixel removal, background subtraction, and segmentation masks. Produces one corrected volume per camera, kept in raw orientation; rotation and flips are deferred to fusion.
  • .fused — fuses each opposing camera pair into a single view: CM00+CM01 → VW00 (Z-scan), CM02+CM03 → VW90 (Y-scan). Applies the rotation/flip and intensity correction, then blends the two cameras along Z. Four cameras become two orthogonal views.
  • .stitcher — exports the two views as a BigDataViewer/BigStitcher dataset (zarr + dataset.xml). BigStitcher registers VW00↔VW90 into one isotropic volume; that registration runs in the BigStitcher GUI, not here (see bigstitcher.md).

All three are siblings of the raw input directory. output_suffix is one shared field appended to every stage dir (.corrected_<x>, .fused_<x>, .stitcher_<x>); empty gives the bare .corrected / .fused / .stitcher.

Correction (correct_stack.py)

Mode Path Filename
Timelapse root.corrected/SPM00/TM000000/ SPM00_TM000000_CM00_CHN00.ome.tif
Tiled root.corrected/SPM00/ SPM00_CM00_CHN00.ome.tif

Each tile gets a SPM0N folder, where N is the tile index. Each SPM## dir also contains a projections/ subfolder with per-view XY MIPs and a raw.xyProjection counterpart for QC. Masks share the same prefix with suffixes .segmentationMask, .xyMask, .xzMask. minIntensity.npz carries the percentile values used downstream.

Fusion (multi_fuse)

Mode Path Filename
Timelapse root.fused/SPM00/TM000000/ SPM00_TM000000_CM00_CM01_VW00_CHN00.ome.zarr
Tiled root.fused/SPM00/ SPM00_CM00_CM01_VW00_CHN00.ome.zarr

Fused volumes go directly under each SPM## (mirroring the corrected tree — no per-method subfolder). Projections for QC land in a single shared root.fused/projections/ sibling of the SPM## dirs.

Default pairs: [(0, 1), (2, 3)] — cameras 0,1 fuse to VW00 (z-scan); cameras 2,3 fuse to VW90 (y-scan). The CHN value reflects the acquisition channel of the pair (in the standard Keller IsoView wiring, CHN00 for CM0/1 and CHN01 for CM2/3). For dual-channel acquisitions, each pair fuses once per shared channel, producing multiple outputs with the same VW## but different CHN##.

Only the second camera in each pair gets rotation/flip transforms.

Supported Output Formats

Format Extension Notes
OME-TIFF .ome.tif with metadata, optional resolution pyramids
Zarr v3 ome.zarr OME-NGFF metadata
KLB .klb Keller Lab Block (bzip2)

MATLAB Parity

The correction and fusion math is a faithful port of the original IsoView MATLAB pipeline, verified against processTimepoint_RC.m, clusterPT_RC.m, and multiFuse.m.

Preserved end-to-end:

Stage Algorithm
Dead-pixel detection std/mean projection, knee threshold, per-Z 2D median replacement
Knee threshold 50k subsample, max-distance-from-line
Anisotropic Gaussian separable, [k, k, max(1, k/scaling)]
Slab smoothing margin = 2·kernelSize, crop-interior reassembly
Adaptive threshold level = minI + (meanI − minI)·threshold
Background Nth percentile of nonzero voxels, subsampled
Masks uint16 0/1
Camera registration per-pair X/Y offset + rotation
Intensity correction overlap-sum ratio, dimmer view scaled up
Blending adaptive (mask) and geometric (crossover) paths

Intentionally omitted (multi-channel features unused on single-color hardware): reference/dependent channel groups, cross-channel mask OR-fusion, the 3-pass global temporal mask, and per-channel-per-camera nesting. Rotation and cropping are deferred from correction to fusion. apply_segmentation_mask defaults off (masks are saved, not baked into the corrected volume). Downstream stages — temporal fusion, drift correction, dF/F, isotropic interpolation — are out of scope.

Known minor differences: median-filter border handling (symmetric vs cv2 replicate, ~1px edge); coordinate masks use a mask-weighted centroid (equivalent to the MATLAB NaN-mean for binary masks) with 0-based vs 1-based coordinates.

.stack Reading

Raw .stack is little-endian uint16, shape (D, H, W), memmappable. (W, H) from XML @dimensions[0]; D derived from file size (stat().st_size // 2 // (H*W)) — XML's D may be stale on aborted acquisitions.

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

isoview-0.5.1.tar.gz (2.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

isoview-0.5.1-py3-none-any.whl (127.1 kB view details)

Uploaded Python 3

File details

Details for the file isoview-0.5.1.tar.gz.

File metadata

  • Download URL: isoview-0.5.1.tar.gz
  • Upload date:
  • Size: 2.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.2

File hashes

Hashes for isoview-0.5.1.tar.gz
Algorithm Hash digest
SHA256 7a80708b89a6378c775c0284a4981a33d94a086f78ca949ceaaaa57ae06acc08
MD5 d0de0c79c3c3540a3eb7d7c6ab3435d7
BLAKE2b-256 f992cfcc8532fcb89365291084526aea63ca3407d457f64eed1d7e6d86aee30e

See more details on using hashes here.

File details

Details for the file isoview-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: isoview-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 127.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.2

File hashes

Hashes for isoview-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a9640844458c8f773c9d5cd2db44268cdf3230133376bcd53f882cc083c0cd99
MD5 29aa621e15923d956c4d52e3b7d7e31d
BLAKE2b-256 54b4ec2f991c82080572742fde538f20360836b7957d6a66af734df0a9cdf8e1

See more details on using hashes here.

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