xenium-splitter 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xenium_splitter-0.2.1/.github/workflows/publish.yml +129 -0
- xenium_splitter-0.2.1/.gitignore +23 -0
- xenium_splitter-0.2.1/LICENSE +21 -0
- xenium_splitter-0.2.1/PKG-INFO +419 -0
- xenium_splitter-0.2.1/README.md +389 -0
- xenium_splitter-0.2.1/TODO.md +153 -0
- xenium_splitter-0.2.1/WORKFLOW_README.md +293 -0
- xenium_splitter-0.2.1/pyproject.toml +58 -0
- xenium_splitter-0.2.1/release.md +46 -0
- xenium_splitter-0.2.1/scripts/BENCHMARK_README.md +360 -0
- xenium_splitter-0.2.1/scripts/benchmark_capture.py +137 -0
- xenium_splitter-0.2.1/scripts/benchmark_config_example.csv +15 -0
- xenium_splitter-0.2.1/scripts/benchmark_example_output.txt +2988 -0
- xenium_splitter-0.2.1/scripts/benchmark_report.py +1316 -0
- xenium_splitter-0.2.1/scripts/benchmark_submit.py +553 -0
- xenium_splitter-0.2.1/setup.cfg +4 -0
- xenium_splitter-0.2.1/src/xenium_splitter/__init__.py +6 -0
- xenium_splitter-0.2.1/src/xenium_splitter/cli.py +237 -0
- xenium_splitter-0.2.1/src/xenium_splitter/estimator.py +20 -0
- xenium_splitter-0.2.1/src/xenium_splitter/image_utils.py +1104 -0
- xenium_splitter-0.2.1/src/xenium_splitter/io_utils.py +3720 -0
- xenium_splitter-0.2.1/src/xenium_splitter/lasso.py +244 -0
- xenium_splitter-0.2.1/src/xenium_splitter/metadata.py +449 -0
- xenium_splitter-0.2.1/src/xenium_splitter/models.py +87 -0
- xenium_splitter-0.2.1/src/xenium_splitter/recalculate_diffexp.py +366 -0
- xenium_splitter-0.2.1/src/xenium_splitter/show_regions.py +253 -0
- xenium_splitter-0.2.1/src/xenium_splitter/splitter.py +3032 -0
- xenium_splitter-0.2.1/src/xenium_splitter/validate_transcripts_zarr.py +43 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/PKG-INFO +419 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/SOURCES.txt +41 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/dependency_links.txt +1 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/entry_points.txt +4 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/requires.txt +24 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/scm_file_list.json +37 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/scm_version.json +8 -0
- xenium_splitter-0.2.1/src/xenium_splitter.egg-info/top_level.txt +1 -0
- xenium_splitter-0.2.1/tests/test_benchmark_capture.py +116 -0
- xenium_splitter-0.2.1/tests/test_benchmark_report.py +240 -0
- xenium_splitter-0.2.1/tests/test_benchmark_submit.py +183 -0
- xenium_splitter-0.2.1/tests/test_estimator.py +72 -0
- xenium_splitter-0.2.1/tests/test_image_utils.py +84 -0
- xenium_splitter-0.2.1/tests/test_lasso.py +37 -0
- xenium_splitter-0.2.1/tests/test_show_regions.py +94 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
name: Publish To PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: pypi-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
name: Test And Build
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 20
|
|
20
|
+
env:
|
|
21
|
+
RELEASE_TAG: ${{ github.ref_name }}
|
|
22
|
+
steps:
|
|
23
|
+
- name: Check Out Release Tag
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
|
|
29
|
+
- name: Set Up Python
|
|
30
|
+
uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.12"
|
|
33
|
+
|
|
34
|
+
- name: Validate Release Tag
|
|
35
|
+
run: |
|
|
36
|
+
python -m pip install packaging
|
|
37
|
+
python - <<'PY'
|
|
38
|
+
import os
|
|
39
|
+
from packaging.version import Version
|
|
40
|
+
|
|
41
|
+
version = Version(os.environ["RELEASE_TAG"].removeprefix("v"))
|
|
42
|
+
if version.local is not None or version.is_devrelease:
|
|
43
|
+
raise SystemExit("Release tags must not contain local or development versions")
|
|
44
|
+
print(f"Release version: {version}")
|
|
45
|
+
PY
|
|
46
|
+
|
|
47
|
+
- name: Install Build And Test Dependencies
|
|
48
|
+
run: |
|
|
49
|
+
python -m pip install --upgrade pip
|
|
50
|
+
python -m pip install -e ".[dev]" build twine
|
|
51
|
+
|
|
52
|
+
- name: Run Tests
|
|
53
|
+
run: python -m pytest
|
|
54
|
+
|
|
55
|
+
- name: Build Distributions
|
|
56
|
+
run: python -m build
|
|
57
|
+
|
|
58
|
+
- name: Check Distribution Metadata And Versions
|
|
59
|
+
run: |
|
|
60
|
+
python -m twine check dist/*
|
|
61
|
+
python - <<'PY'
|
|
62
|
+
import os
|
|
63
|
+
import tarfile
|
|
64
|
+
import zipfile
|
|
65
|
+
from email.parser import BytesParser
|
|
66
|
+
from pathlib import Path
|
|
67
|
+
from packaging.version import Version
|
|
68
|
+
|
|
69
|
+
expected = Version(os.environ["RELEASE_TAG"].removeprefix("v"))
|
|
70
|
+
wheels = list(Path("dist").glob("*.whl"))
|
|
71
|
+
sources = list(Path("dist").glob("*.tar.gz"))
|
|
72
|
+
if len(wheels) != 1 or len(sources) != 1:
|
|
73
|
+
raise SystemExit("Expected exactly one wheel and one source distribution")
|
|
74
|
+
with zipfile.ZipFile(wheels[0]) as archive:
|
|
75
|
+
metadata_path = next(
|
|
76
|
+
name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
|
|
77
|
+
)
|
|
78
|
+
wheel_metadata = BytesParser().parsebytes(archive.read(metadata_path))
|
|
79
|
+
with tarfile.open(sources[0]) as archive:
|
|
80
|
+
metadata_member = next(
|
|
81
|
+
member for member in archive.getmembers()
|
|
82
|
+
if member.name.count("/") == 1 and member.name.endswith("/PKG-INFO")
|
|
83
|
+
)
|
|
84
|
+
source_metadata = BytesParser().parsebytes(
|
|
85
|
+
archive.extractfile(metadata_member).read()
|
|
86
|
+
)
|
|
87
|
+
for metadata in (wheel_metadata, source_metadata):
|
|
88
|
+
actual = Version(metadata["Version"])
|
|
89
|
+
if actual != expected:
|
|
90
|
+
raise SystemExit(f"Tag version {expected} does not match build version {actual}")
|
|
91
|
+
print(f"Both distributions match release tag: {expected}")
|
|
92
|
+
PY
|
|
93
|
+
|
|
94
|
+
- name: Smoke Test Installed Wheel
|
|
95
|
+
working-directory: ${{ runner.temp }}
|
|
96
|
+
run: |
|
|
97
|
+
python -m venv "$RUNNER_TEMP/wheel-smoke"
|
|
98
|
+
"$RUNNER_TEMP/wheel-smoke/bin/python" -m pip install "$GITHUB_WORKSPACE"/dist/*.whl
|
|
99
|
+
"$RUNNER_TEMP/wheel-smoke/bin/python" -m pip check
|
|
100
|
+
"$RUNNER_TEMP/wheel-smoke/bin/xenium-splitter" --help
|
|
101
|
+
"$RUNNER_TEMP/wheel-smoke/bin/xenium-recalc-diffexp" --help
|
|
102
|
+
"$RUNNER_TEMP/wheel-smoke/bin/xenium-validate-transcripts-zarr" --help
|
|
103
|
+
|
|
104
|
+
- name: Upload Release Distributions
|
|
105
|
+
uses: actions/upload-artifact@v4
|
|
106
|
+
with:
|
|
107
|
+
name: distributions
|
|
108
|
+
path: dist/*
|
|
109
|
+
if-no-files-found: error
|
|
110
|
+
|
|
111
|
+
publish:
|
|
112
|
+
name: Publish To PyPI
|
|
113
|
+
needs: build
|
|
114
|
+
runs-on: ubuntu-latest
|
|
115
|
+
timeout-minutes: 10
|
|
116
|
+
environment:
|
|
117
|
+
name: pypi
|
|
118
|
+
url: https://pypi.org/project/xenium-splitter/
|
|
119
|
+
permissions:
|
|
120
|
+
id-token: write
|
|
121
|
+
steps:
|
|
122
|
+
- name: Download Release Distributions
|
|
123
|
+
uses: actions/download-artifact@v4
|
|
124
|
+
with:
|
|
125
|
+
name: distributions
|
|
126
|
+
path: dist
|
|
127
|
+
|
|
128
|
+
- name: Publish With Trusted Publishing
|
|
129
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.pyo
|
|
4
|
+
*.pyd
|
|
5
|
+
.Python
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
htmlcov/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
|
|
17
|
+
# local outputs
|
|
18
|
+
output/
|
|
19
|
+
|
|
20
|
+
# large raw data
|
|
21
|
+
data/
|
|
22
|
+
|
|
23
|
+
data_out/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xenium-splitter
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Split Xenium outputs by LASSO regions and optionally process matching H&E images.
|
|
5
|
+
Author: xenium-splitter contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy<2,>=1.26
|
|
11
|
+
Requires-Dist: opencv-python-headless<4.12,>=4.8
|
|
12
|
+
Requires-Dist: pandas>=2.2
|
|
13
|
+
Requires-Dist: pyarrow>=15.0
|
|
14
|
+
Requires-Dist: h5py>=3.10
|
|
15
|
+
Requires-Dist: zarr<=3.0.0
|
|
16
|
+
Requires-Dist: Pillow>=10.0
|
|
17
|
+
Requires-Dist: shapely>=2.0
|
|
18
|
+
Requires-Dist: tifffile>=2024.5.22
|
|
19
|
+
Requires-Dist: imagecodecs>=2024.1.1
|
|
20
|
+
Requires-Dist: typer>=0.12
|
|
21
|
+
Provides-Extra: parquet
|
|
22
|
+
Provides-Extra: svs
|
|
23
|
+
Requires-Dist: openslide-python>=1.3.1; extra == "svs"
|
|
24
|
+
Provides-Extra: hdf5
|
|
25
|
+
Provides-Extra: zarr
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# xenium-splitter
|
|
32
|
+
|
|
33
|
+
`xenium-splitter` splits Xenium output files by user-defined LASSO regions and writes one output
|
|
34
|
+
directory per region. It can also process a matching H&E image, applying polygon masks and
|
|
35
|
+
cropping to each region.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- CLI-first workflow.
|
|
40
|
+
- LASSO region parsing from GeoJSON/JSON or CSV/TSV.
|
|
41
|
+
- Tabular Xenium split (CSV/TSV/TXT/Parquet) using detected coordinate columns.
|
|
42
|
+
- Coordinate re-basing in each region output (region origin becomes `(0, 0)`).
|
|
43
|
+
- Image masking + cropping for raster images (`.png`, `.jpg`, `.jpeg`, `.tif`, `.tiff`, `.ome.tif`, `.ome.tiff`).
|
|
44
|
+
- Optional SVS handling for H&E: load, split, and optionally convert to OME-TIFF.
|
|
45
|
+
- `show-regions` preview command: render labeled LASSO regions and dotted region bounding boxes on low-res H&E/morphology images.
|
|
46
|
+
- Metadata README generation with run parameters and file-level metrics.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install -e .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Zarr, HDF5, and Parquet support are included in the default installation.
|
|
55
|
+
|
|
56
|
+
Optional extras:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install -e ".[svs,dev]"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The `svs` extra adds SVS image support and requires native OpenSlide libraries.
|
|
63
|
+
The `dev` extra adds testing and linting tools. The legacy `parquet`, `hdf5`, and
|
|
64
|
+
`zarr` extras remain accepted for compatibility but are no longer necessary.
|
|
65
|
+
|
|
66
|
+
For versioning and publishing instructions, see [release.md](release.md).
|
|
67
|
+
|
|
68
|
+
## CLI Usage
|
|
69
|
+
|
|
70
|
+
### RAM Estimate
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
xenium-splitter estimate-ram \
|
|
74
|
+
--input-dir /path/to/xenium_output \
|
|
75
|
+
--lasso-file regions.csv \
|
|
76
|
+
--output-dir /path/to/output
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Estimates peak RAM using the on-disk archive sizes directly inside `--input-dir`:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
PeakRAM(GB) = 2.5 + 0.010 * TranscriptZarrMB + 0.007 * CellZarrMB
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`TranscriptZarrMB` is the size of `transcripts.zarr.zip`; `CellZarrMB` is the size
|
|
86
|
+
of `cells.zarr.zip`. Both files are required. MB means 1,000,000 bytes, not MiB.
|
|
87
|
+
The estimator reads file sizes only; it does not load or extract the archives.
|
|
88
|
+
It accepts all the same options, defaults, and required paths as `split`; replace
|
|
89
|
+
`split` with `estimate-ram` in a split invocation. This includes repeated
|
|
90
|
+
`--include-glob` options, image/analysis switches, and `-v`/`--verbose`.
|
|
91
|
+
`--images-only` and `--skip-images` remain mutually exclusive.
|
|
92
|
+
Only the two archive sizes affect this equation: processing options, LASSO
|
|
93
|
+
geometry, H&E, and the output path are not modeled. No output files or directories
|
|
94
|
+
are created.
|
|
95
|
+
|
|
96
|
+
This is an approximate peak estimate, not a guaranteed memory limit or a scheduler
|
|
97
|
+
allocation recommendation. Image layout, region geometry, and processing options
|
|
98
|
+
are not modeled; allow additional headroom when requesting RAM.
|
|
99
|
+
|
|
100
|
+
### Options
|
|
101
|
+
|
|
102
|
+
**Required:**
|
|
103
|
+
|
|
104
|
+
| Option | Description |
|
|
105
|
+
| -------------- | -------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `--input-dir` | Xenium output directory (the `outs/` folder or equivalent). |
|
|
107
|
+
| `--lasso-file` | LASSO region file — GeoJSON/JSON or CSV/TSV (see [LASSO Input Expectations](#lasso-input-expectations)). |
|
|
108
|
+
| `--output-dir` | Destination directory; one `region_<id>/` subfolder is created per region. |
|
|
109
|
+
|
|
110
|
+
**Optional — H&E image:**
|
|
111
|
+
|
|
112
|
+
| Option | Default | Description |
|
|
113
|
+
| ---------------------- | ------- | -------------------------------------------------------------------------------------- |
|
|
114
|
+
| `--he-image` | None | External H&E image to split alongside Xenium outputs (TIFF, OME-TIFF, SVS, PNG, JPEG). |
|
|
115
|
+
| `--convert-svs-to-ome` | off | When `--he-image` is an SVS file, also write a full-slide OME-TIFF per region. |
|
|
116
|
+
|
|
117
|
+
> **Note:** xenium-splitter does **not** perform H&E registration. The H&E image is assumed to already be spatially aligned with the Xenium coordinate space. No registration file, affine transform, or warping is applied. If your H&E requires registration to the morphology image, that step must be completed before running this tool. The tool also assumes the H&E image uses the **same pixel size** as the Xenium morphology data — it reads `pixel_size_um` from `experiment.xenium` and uses that value to convert polygon coordinates to pixel coordinates for both. If your H&E has a different pixel resolution, the crop will be incorrectly positioned.
|
|
118
|
+
|
|
119
|
+
**Optional — image processing:**
|
|
120
|
+
|
|
121
|
+
| Option | Default | Description |
|
|
122
|
+
| ---------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `--squash-layers` / `--no-squash-layers` | `--squash-layers` | Flatten multi-layer TIFF stacks to 2D/RGB before cropping. |
|
|
124
|
+
| `--skip-images` / `--process-images` | `--process-images` | Skip all image cropping and masking (useful for fast data-only runs). |
|
|
125
|
+
| `--images-only` | off | Process **only** image files; skip all tabular data, zarr, CFM, and diffexp stages. Useful when running images separately to keep RAM available, or for a re-run after a prior data-only pass. Mutually exclusive with `--skip-images`. |
|
|
126
|
+
| `--overlays` / `--no-overlays` | `--no-overlays` | Write annotated FOV grid overlay images alongside each region `morphology_mip.ome.tif`. |
|
|
127
|
+
|
|
128
|
+
**Optional — data filtering:**
|
|
129
|
+
|
|
130
|
+
| Option | Default | Description |
|
|
131
|
+
| -------------------- | ---------------- | ---------------------------------------------------------------------------------- |
|
|
132
|
+
| `--include-glob` | None (all files) | Glob pattern to restrict which files are read from `--input-dir`; repeatable. |
|
|
133
|
+
| `--copy-transcripts` | off | Copy `transcripts.zarr.zip` verbatim to each region instead of filtering/rebasing. |
|
|
134
|
+
|
|
135
|
+
**Optional — analysis outputs:**
|
|
136
|
+
|
|
137
|
+
| Option | Default | Description |
|
|
138
|
+
| ---------------------------------------------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------- |
|
|
139
|
+
| `--recalculate-diffexp` / `--skip-diffexp-recalc` | `--recalculate-diffexp` | Recompute `analysis/diffexp` CSVs from the filtered matrix and clustering labels. |
|
|
140
|
+
| `--write-cell-feature-matrix-zarr` / `--skip-cell-feature-matrix-zarr` | `--write-cell-feature-matrix-zarr` | Write `cell_feature_matrix.zarr.zip` in each region output. |
|
|
141
|
+
|
|
142
|
+
**Optional — logging:**
|
|
143
|
+
|
|
144
|
+
| Option | Default | Description |
|
|
145
|
+
| ------------------ | ------- | ------------------------------------- |
|
|
146
|
+
| `-v` / `--verbose` | off | Enable debug-level logging to stderr. |
|
|
147
|
+
|
|
148
|
+
### Examples
|
|
149
|
+
|
|
150
|
+
**Minimal split — tabular data and morphology only:**
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
xenium-splitter split \
|
|
154
|
+
--input-dir /path/to/xenium_output \
|
|
155
|
+
--lasso-file regions.geojson \
|
|
156
|
+
--output-dir /path/to/output
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Fast data-only run — skip all image processing:**
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
xenium-splitter split \
|
|
163
|
+
--input-dir /path/to/xenium_output \
|
|
164
|
+
--lasso-file regions.csv \
|
|
165
|
+
--output-dir /path/to/output \
|
|
166
|
+
--skip-images -v
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Include a pre-aligned H&E TIFF alongside Xenium outputs:**
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
xenium-splitter split \
|
|
173
|
+
--input-dir /path/to/xenium_output \
|
|
174
|
+
--lasso-file regions.geojson \
|
|
175
|
+
--output-dir /path/to/output \
|
|
176
|
+
--he-image /path/to/aligned_he.ome.tif
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Include an SVS H&E and also produce per-region OME-TIFF exports:**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
xenium-splitter split \
|
|
183
|
+
--input-dir /path/to/xenium_output \
|
|
184
|
+
--lasso-file regions.geojson \
|
|
185
|
+
--output-dir /path/to/output \
|
|
186
|
+
--he-image /path/to/he.svs \
|
|
187
|
+
--convert-svs-to-ome
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Write FOV grid overlays for visual QC of region alignment:**
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
xenium-splitter split \
|
|
194
|
+
--input-dir /path/to/xenium_output \
|
|
195
|
+
--lasso-file regions.geojson \
|
|
196
|
+
--output-dir /path/to/output \
|
|
197
|
+
--overlays
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Process only a subset of files matching a glob pattern:**
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
xenium-splitter split \
|
|
204
|
+
--input-dir /path/to/xenium_output \
|
|
205
|
+
--lasso-file regions.geojson \
|
|
206
|
+
--output-dir /path/to/output \
|
|
207
|
+
--include-glob "cells*" \
|
|
208
|
+
--include-glob "transcripts*" \
|
|
209
|
+
--skip-images
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Copy transcripts verbatim (no filtering or coordinate rebasing):**
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
xenium-splitter split \
|
|
216
|
+
--input-dir /path/to/xenium_output \
|
|
217
|
+
--lasso-file regions.geojson \
|
|
218
|
+
--output-dir /path/to/output \
|
|
219
|
+
--copy-transcripts
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Images-only re-run after a prior data-only pass (minimise peak RAM):**
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Step 1 — data only (low RAM):
|
|
226
|
+
xenium-splitter split \
|
|
227
|
+
--input-dir /path/to/xenium_output \
|
|
228
|
+
--lasso-file regions.geojson \
|
|
229
|
+
--output-dir /path/to/output \
|
|
230
|
+
--skip-images
|
|
231
|
+
|
|
232
|
+
# Step 2 — images only (reserves all RAM for image loading):
|
|
233
|
+
xenium-splitter split \
|
|
234
|
+
--input-dir /path/to/xenium_output \
|
|
235
|
+
--lasso-file regions.geojson \
|
|
236
|
+
--output-dir /path/to/output \
|
|
237
|
+
--images-only
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Images-only with a large SVS H&E:**
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
xenium-splitter split \
|
|
244
|
+
--input-dir /path/to/xenium_output \
|
|
245
|
+
--lasso-file regions.geojson \
|
|
246
|
+
--output-dir /path/to/output \
|
|
247
|
+
--images-only \
|
|
248
|
+
--he-image /path/to/he.svs
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Temp Cleanup
|
|
252
|
+
|
|
253
|
+
Remove temporary working directories created under the OS temp folder
|
|
254
|
+
(`.../xenium_splitter`):
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
xenium-splitter clean-temp
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Region Preview (show-regions)
|
|
261
|
+
|
|
262
|
+
Create a quick visual QC image (max `2000 x 2000`) that overlays LASSO regions and
|
|
263
|
+
region bounding boxes on a source tissue image.
|
|
264
|
+
|
|
265
|
+
Behavior:
|
|
266
|
+
|
|
267
|
+
- Source image preference: H&E first, morphology fallback.
|
|
268
|
+
- Region outline color: purple.
|
|
269
|
+
- Dotted region bounding box color:
|
|
270
|
+
- H&E source: black.
|
|
271
|
+
- Morphology source: green.
|
|
272
|
+
- Region labels: each `region_id` label is drawn once per bounding box in the top-inside corner with a small margin.
|
|
273
|
+
- Label font size scales with generated image size for readability.
|
|
274
|
+
|
|
275
|
+
CLI options:
|
|
276
|
+
|
|
277
|
+
| Option | Description |
|
|
278
|
+
| -------------------- | ------------------------------------------------------------------------------ |
|
|
279
|
+
| `--input-dir` | Xenium input directory used to discover source images and `experiment.xenium`. |
|
|
280
|
+
| `--lasso-file` | LASSO region file (GeoJSON/JSON or CSV/TSV). |
|
|
281
|
+
| `--output-image` | Output preview image path (for example `regions_preview.png`). |
|
|
282
|
+
| `--he-image` | Optional explicit H&E image path; when provided, this is used directly. |
|
|
283
|
+
| `--max-dimension-px` | Longest output edge in pixels (max allowed value is `2000`, default `2000`). |
|
|
284
|
+
|
|
285
|
+
Examples:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
xenium-splitter show-regions \
|
|
289
|
+
--input-dir /path/to/xenium_output \
|
|
290
|
+
--lasso-file regions.geojson \
|
|
291
|
+
--output-image /path/to/output/regions_preview.png
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
xenium-splitter show-regions \
|
|
296
|
+
--input-dir /path/to/xenium_output \
|
|
297
|
+
--lasso-file regions.csv \
|
|
298
|
+
--he-image /path/to/aligned_he.svs \
|
|
299
|
+
--output-image /path/to/output/regions_preview.png
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## LASSO Input Expectations
|
|
303
|
+
|
|
304
|
+
### GeoJSON / JSON
|
|
305
|
+
|
|
306
|
+
- `FeatureCollection` with polygon-like geometries (`Polygon` or `MultiPolygon`).
|
|
307
|
+
- Region id is read from `id`, `region_id`, `name`, or generated automatically.
|
|
308
|
+
|
|
309
|
+
### CSV / TSV
|
|
310
|
+
|
|
311
|
+
Two supported layouts:
|
|
312
|
+
|
|
313
|
+
1. Point list with columns: `region_id`, `x`, `y`.
|
|
314
|
+
2. WKT with columns: `region_id`, `polygon_wkt`.
|
|
315
|
+
|
|
316
|
+
## Output Layout
|
|
317
|
+
|
|
318
|
+
For each region, a directory is created:
|
|
319
|
+
|
|
320
|
+
```text
|
|
321
|
+
output/
|
|
322
|
+
region_<id>/
|
|
323
|
+
...split files mirroring input names...
|
|
324
|
+
run_metadata_README.md
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## RAM Requirements
|
|
328
|
+
|
|
329
|
+
Memory usage is driven by three independent factors: transcript table size, image size, and zarr processing. They do not all peak simultaneously, but on large datasets they can overlap significantly. Note, official benchmarking has not been done on this yet. Once benchmarks are available, they will be added to this section.
|
|
330
|
+
|
|
331
|
+
### Without image processing (`--skip-images`)
|
|
332
|
+
|
|
333
|
+
The dominant cost is the full transcript table, which is loaded entirely into RAM as a pandas DataFrame before being filtered per region.
|
|
334
|
+
|
|
335
|
+
| Dataset scale | Approx. transcripts | Recommended RAM |
|
|
336
|
+
| ---------------- | ------------------- | --------------- |
|
|
337
|
+
| Small (< 1 mm²) | ~500 K | 4 GB |
|
|
338
|
+
| Medium (~10 mm²) | ~5 M | 8 GB |
|
|
339
|
+
| Large (~50 mm²) | ~50 M | 16–32 GB |
|
|
340
|
+
|
|
341
|
+
The cell/barcode tables and CFM zarr add a modest additional cost (typically < 1 GB).
|
|
342
|
+
|
|
343
|
+
### With image processing (default)
|
|
344
|
+
|
|
345
|
+
Image memory cost depends on format and adds on top of the transcript cost above.
|
|
346
|
+
|
|
347
|
+
**Morphology TIFF / OME-TIFF — always full-image load:**
|
|
348
|
+
The entire morphology image is read into RAM before cropping; windowed reads are not used for TIFF. A temporary copy is created during Z-stack squashing (max-projection), so peak is approximately 2× the raw array size.
|
|
349
|
+
|
|
350
|
+
| Image dimensions | Channels / planes | RAM (peak ~2×) |
|
|
351
|
+
| ---------------- | ------------------ | -------------- |
|
|
352
|
+
| 5 K × 5 K | 1 plane, uint16 | ~100 MB |
|
|
353
|
+
| 10 K × 10 K | 1 plane, uint16 | ~400 MB |
|
|
354
|
+
| 20 K × 20 K | 7 Z-planes, uint16 | ~11 GB |
|
|
355
|
+
| 30 K × 30 K | 7 Z-planes, uint16 | ~25 GB |
|
|
356
|
+
|
|
357
|
+
**H&E as TIFF / OME-TIFF — full-image load:**
|
|
358
|
+
Same behaviour as morphology: the complete image is loaded before any cropping.
|
|
359
|
+
|
|
360
|
+
| Image dimensions | Channels | RAM (peak ~2×) |
|
|
361
|
+
| ---------------- | --------- | -------------- |
|
|
362
|
+
| 10 K × 20 K | RGB uint8 | ~1.2 GB |
|
|
363
|
+
| 25 K × 45 K | RGB uint8 | ~6.8 GB |
|
|
364
|
+
|
|
365
|
+
**H&E as SVS — windowed, memory-efficient:**
|
|
366
|
+
OpenSlide reads only the polygon bounding box per region; the full slide is never loaded. A typical crop (5 K × 5 K px) uses ~100 MB regardless of source file size. **SVS is the recommended format for large H&E images.**
|
|
367
|
+
|
|
368
|
+
### Practical guidance
|
|
369
|
+
|
|
370
|
+
| Scenario | Recommended minimum RAM |
|
|
371
|
+
| --------------------------------------------- | ----------------------- |
|
|
372
|
+
| Data only, small dataset | 8 GB |
|
|
373
|
+
| Data only, large dataset (≥ 50 M transcripts) | 32–64 GB |
|
|
374
|
+
| Data + morphology TIFF, medium dataset | 16 GB |
|
|
375
|
+
| Data + H&E TIFF (25 K × 45 K), medium dataset | 32 GB |
|
|
376
|
+
| Data + H&E TIFF (25 K × 45 K), large dataset | 64 GB |
|
|
377
|
+
| Data + H&E SVS (any size), medium dataset | 16 GB |
|
|
378
|
+
| Data + H&E SVS (any size), large dataset | 32–48 GB |
|
|
379
|
+
|
|
380
|
+
> **Tip:** Run `--skip-images` first for a fast data-only pass. Re-run with images only if needed. For large H&E files, convert to SVS or use a tiled OME-TIFF with a viewer that supports windowed reads before passing to this tool.
|
|
381
|
+
|
|
382
|
+
## Current Scope and Notes
|
|
383
|
+
|
|
384
|
+
- Files without recognized coordinate columns (for tabular) are skipped and documented in metadata.
|
|
385
|
+
- Unknown binary formats are currently skipped.
|
|
386
|
+
- SVS requires `openslide-python` and system OpenSlide libraries.
|
|
387
|
+
|
|
388
|
+
## FOV Recalculation Rules
|
|
389
|
+
|
|
390
|
+
When rebuilding transcript FOV assignments, `xenium-splitter` reads
|
|
391
|
+
`instrument_sw_version` from `experiment.xenium` and uses these FOV dimensions
|
|
392
|
+
(in pixels):
|
|
393
|
+
|
|
394
|
+
- `< 1.2`: `4240` rows x `2960` cols
|
|
395
|
+
- `>= 1.2`: `3520` rows x `2960` cols
|
|
396
|
+
|
|
397
|
+
The FOV overlap is fixed at `128` pixels.
|
|
398
|
+
|
|
399
|
+
- X stride: `2960 - 128 = 2832`
|
|
400
|
+
- Y stride (`< 1.2`): `4240 - 128 = 4112`
|
|
401
|
+
- Y stride (`>= 1.2`): `3520 - 128 = 3392`
|
|
402
|
+
|
|
403
|
+
Potential FOV counts in each direction are computed from rebased transcript
|
|
404
|
+
pixel coordinates (`x_px`, `y_px`) as:
|
|
405
|
+
|
|
406
|
+
- `potential_fov_x = floor(max(x_px) / x_stride) + 1`
|
|
407
|
+
- `potential_fov_y = floor(max(y_px) / y_stride) + 1`
|
|
408
|
+
|
|
409
|
+
where `x_px = floor(x_um / pixel_size_um)` and `y_px = floor(y_um / pixel_size_um)`.
|
|
410
|
+
|
|
411
|
+
For example, with `instrument_sw_version: 1.1.2.4`, the splitter uses the
|
|
412
|
+
`< 1.2` rule: `4240 x 2960` FOVs, overlap `128`, strides `4112` (Y) and `2832` (X).
|
|
413
|
+
|
|
414
|
+
## Development
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
pytest
|
|
418
|
+
ruff check .
|
|
419
|
+
```
|