annslicer 0.1.3__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.
@@ -0,0 +1,26 @@
1
+ name: Benchmark
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ benchmark:
8
+ name: Benchmark (Python 3.10)
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ # Fetch full history so setuptools-scm can derive a version from tags.
15
+ fetch-depth: 0
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.10"
20
+ cache: pip
21
+
22
+ - name: Install package with dev dependencies
23
+ run: pip install -e ".[dev]"
24
+
25
+ - name: Run benchmarks
26
+ run: make benchmark
@@ -0,0 +1,31 @@
1
+ name: Build check
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ build:
11
+ name: Build sdist and wheel
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 0 # setuptools-scm needs full history + tags
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.10"
22
+ cache: pip
23
+
24
+ - name: Install build tools
25
+ run: pip install --upgrade pip build twine
26
+
27
+ - name: Build sdist and wheel
28
+ run: python -m build
29
+
30
+ - name: Check distributions
31
+ run: twine check dist/*
@@ -0,0 +1,30 @@
1
+ name: Lint
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ name: ruff
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install ruff and mypy
21
+ run: pip install ruff mypy==1.15.0
22
+
23
+ - name: Check (lint)
24
+ run: ruff check src/ tests/
25
+
26
+ - name: Check (format)
27
+ run: ruff format --check src/ tests/
28
+
29
+ - name: Type-check (mypy)
30
+ run: mypy src/annslicer
@@ -0,0 +1,51 @@
1
+ name: Publish to PyPI
2
+
3
+ # Runs automatically when a GitHub Release is published, AND can be
4
+ # triggered manually from the Actions tab as a fallback.
5
+ #
6
+ # Before creating a release:
7
+ # git tag v0.x.y
8
+ # git push --tags
9
+ # git push origin --tags
10
+ # Then: GitHub → Releases → Draft a new release → choose the tag → Publish.
11
+ #
12
+ # PyPI Trusted Publishing (OIDC) is used — no API token stored in secrets.
13
+ # One-time setup on PyPI: add a "Trusted Publisher" for this repo/workflow.
14
+
15
+ on:
16
+ release:
17
+ types: [published]
18
+ workflow_dispatch:
19
+
20
+ jobs:
21
+ build-and-publish:
22
+ name: Build and publish to PyPI
23
+ runs-on: ubuntu-latest
24
+ environment: pypi # must match the environment name set in PyPI trusted publisher config
25
+
26
+ # Required for PyPI Trusted Publishing (OIDC token exchange)
27
+ permissions:
28
+ id-token: write
29
+ contents: read
30
+
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ with:
34
+ fetch-depth: 0 # Full history so setuptools-scm picks up the tag
35
+
36
+ - uses: actions/setup-python@v5
37
+ with:
38
+ python-version: "3.10"
39
+
40
+ - name: Install build tools
41
+ run: pip install --upgrade pip build twine
42
+
43
+ - name: Build sdist and wheel
44
+ run: python -m build
45
+
46
+ - name: Check distributions
47
+ run: twine check dist/*
48
+
49
+ - name: Publish to PyPI
50
+ uses: pypa/gh-action-pypi-publish@release/v1
51
+ # No username/password needed — OIDC handles authentication.
@@ -0,0 +1,33 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: pytest (Python ${{ matrix.python-version }})
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ # Fetch full history so setuptools-scm can derive a version from tags.
22
+ fetch-depth: 0
23
+
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ cache: pip
28
+
29
+ - name: Install package with dev dependencies
30
+ run: pip install -e ".[dev]"
31
+
32
+ - name: Run tests
33
+ run: pytest
@@ -0,0 +1,40 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+
8
+ # Distribution / packaging
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+ *.egg
13
+ .eggs/
14
+ MANIFEST
15
+
16
+ # setuptools-scm generated version file
17
+ src/annslicer/_version.py
18
+
19
+ # Virtual environments
20
+ .venv/
21
+ venv/
22
+ env/
23
+
24
+ # Pytest
25
+ .pytest_cache/
26
+ .cache/
27
+
28
+ # Ruff
29
+ .ruff_cache/
30
+
31
+ # Jupyter
32
+ .ipynb_checkpoints/
33
+
34
+ # macOS
35
+ .DS_Store
36
+
37
+ # Large data files — never commit .h5ad files to the repo
38
+ *.h5ad
39
+ *.h5
40
+ *.hdf5
@@ -0,0 +1,132 @@
1
+ # Contributing to annslicer
2
+
3
+ ## Development setup
4
+
5
+ We recommend working inside a conda environment to keep dependencies isolated.
6
+
7
+ ```bash
8
+ conda create -n annslicer python=3.10
9
+ conda activate annslicer
10
+ ```
11
+
12
+ Clone the repo:
13
+
14
+ ```bash
15
+ git clone https://github.com/cellarium-ai/annslicer.git
16
+ cd annslicer
17
+ ```
18
+
19
+ And then install in editable mode with development dependencies. You can then either install using the Makefile command
20
+
21
+ ```bash
22
+ make install
23
+ ```
24
+
25
+ or instead, you can equivalently run
26
+
27
+ ```bash
28
+ pip install --upgrade pip
29
+ pip install -e ".[dev]"
30
+ ```
31
+
32
+ The `annslicer` command will now point to your local source, and changes take effect immediately without reinstalling.
33
+
34
+ ## Running tests
35
+
36
+ ```bash
37
+ pytest
38
+ ```
39
+
40
+ Zarr-related tests (zarr output merging, zarr input slicing, zarr shuffle) are skipped automatically if `zarr` is not installed.
41
+ To run the full test suite including zarr:
42
+
43
+ ```bash
44
+ pip install -e ".[dev,zarr]"
45
+ pytest
46
+ ```
47
+
48
+ ## Linting, formatting, and type-checking
49
+
50
+ Before committing, run two commands from the root of the repo:
51
+
52
+ ```bash
53
+ make lint
54
+ make typecheck
55
+ ```
56
+
57
+ Or if you want to type things manually:
58
+
59
+ ```bash
60
+ # Check for lint errors
61
+ ruff check src/ tests/
62
+
63
+ # Auto-fix where possible
64
+ ruff check --fix src/ tests/
65
+
66
+ # Check formatting
67
+ ruff format --check src/ tests/
68
+
69
+ # Apply formatting
70
+ ruff format src/ tests/
71
+
72
+ # Type-check
73
+ mypy src/annslicer
74
+ ```
75
+
76
+ All three checks run automatically on every push and pull request via GitHub Actions.
77
+
78
+ ## Running benchmarks
79
+
80
+ Benchmarks live in `benchmarks/` and are excluded from the normal `pytest` run so that CI stays fast. Run them locally with:
81
+
82
+ ```bash
83
+ make benchmark
84
+ ```
85
+
86
+ Or directly:
87
+
88
+ ```bash
89
+ pytest benchmarks/ --benchmark-only -v
90
+ ```
91
+
92
+ The benchmark suite (`benchmarks/bench_slice.py`) compares:
93
+
94
+ | Benchmark | What it measures |
95
+ |---|---|
96
+ | `bench_annslicer_slice` | Full out-of-core sharding pipeline (no shuffle) |
97
+ | `bench_annslicer_slice_shuffle` | Overhead of random shuffling via sort-read-reorder |
98
+ | `bench_anndata_backed_iterate` | Baseline: backed AnnData row iteration |
99
+
100
+ Adjust `N_CELLS_BENCH` and `N_GENES_BENCH` in `benchmarks/conftest.py` to scale the dataset up or down.
101
+
102
+ ## Releasing a new version
103
+
104
+ Version is derived automatically from git tags — there is no version string to update in code.
105
+
106
+ 1. Ensure all tests and lint checks pass on `main`.
107
+ 2. Tag the release commit:
108
+ ```bash
109
+ git tag v0.2.0
110
+ git push --tags
111
+ ```
112
+ 3. In the GitHub Actions tab, manually trigger the **Publish to PyPI** workflow.
113
+
114
+ That's it. `setuptools-scm` picks the version from the tag, builds the sdist and wheel, and publishes to PyPI using OIDC Trusted Publishing (no API token required).
115
+
116
+ ## Project layout
117
+
118
+ ```
119
+ src/annslicer/
120
+ __init__.py — public API surface and __version__
121
+ cli.py — unified entry point; registers slice and merge subcommands
122
+ _store.py — shared HDF5/Zarr store helpers (open_store, _is_sparse_group, _require_zarr)
123
+ slice.py — shard logic: shard_h5ad (h5ad + zarr input, shuffle), register_subcommand
124
+ merge.py — merge logic: merge_out_of_core (h5ad + zarr output), register_subcommand
125
+ tests/
126
+ conftest.py — synthetic .h5ad and .zarr fixtures
127
+ test_slice.py — unit and integration tests for slicing (including shuffle + zarr input)
128
+ test_merge.py — unit and integration tests for merging (h5ad + zarr)
129
+ benchmarks/
130
+ conftest.py — large synthetic fixture (configurable N_CELLS_BENCH × N_GENES_BENCH)
131
+ bench_slice.py — annslicer slice vs. backed AnnData iteration benchmarks
132
+ ```
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2026, Broad Institute
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ 3. Neither the name of the copyright holder nor the names of its
14
+ contributors may be used to endorse or promote products derived from
15
+ this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,23 @@
1
+ .PHONY: install lint typecheck benchmark build-check
2
+
3
+ install:
4
+ pip install --upgrade pip
5
+ pip install -e ".[dev]"
6
+
7
+ lint:
8
+ ruff check --fix src/ tests/ benchmarks/
9
+ ruff format src/ tests/ benchmarks/
10
+
11
+ typecheck:
12
+ mypy src/annslicer
13
+
14
+ benchmark:
15
+ pytest benchmarks/ --benchmark-only -v -s
16
+
17
+ build-check:
18
+ @echo "--- Building sdist and wheel ---"
19
+ python -m build --outdir /tmp/annslicer-dist-check
20
+ @echo "--- Checking distributions ---"
21
+ twine check /tmp/annslicer-dist-check/*
22
+ @rm -rf /tmp/annslicer-dist-check
23
+ @echo "--- Build check passed ---"
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: annslicer
3
+ Version: 0.1.3
4
+ Summary: Out-of-core sharding of large .h5ad AnnData files with minimal memory usage.
5
+ Author: sfleming
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sfleming/annslicer
8
+ Project-URL: Bug Tracker, https://github.com/sfleming/annslicer/issues
9
+ Keywords: anndata,h5ad,bioinformatics,single-cell,genomics
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.md
21
+ Requires-Dist: anndata>=0.9
22
+ Requires-Dist: numpy>=1.24
23
+ Requires-Dist: scipy>=1.10
24
+ Requires-Dist: h5py>=3.8
25
+ Provides-Extra: dev
26
+ Requires-Dist: annslicer[zarr]; extra == "dev"
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
29
+ Requires-Dist: ruff>=0.4; extra == "dev"
30
+ Requires-Dist: mypy==1.15.0; extra == "dev"
31
+ Requires-Dist: build; extra == "dev"
32
+ Requires-Dist: twine; extra == "dev"
33
+ Provides-Extra: zarr
34
+ Requires-Dist: zarr>=2.10; extra == "zarr"
35
+ Dynamic: license-file
36
+
37
+ # annslicer
38
+
39
+ **Out-of-core sharding and merging of large AnnData files with minimal memory usage.**
40
+
41
+ ![Diagram](diagram.png)
42
+
43
+ Large single-cell datasets stored as `.h5ad` or `.zarr` files can easily exceed available RAM. `annslicer` slices them into manageable shards — and merges them back — without loading full matrices into memory. It uses best practices from `anndata` with a few small speed improvements for random shuffling.
44
+
45
+ Consolidates best practices into a simple command-line tool.
46
+
47
+ ```bash
48
+ annslicer slice input.h5ad output_prefix
49
+ ```
50
+
51
+ ```bash
52
+ annslicer merge output.h5ad shard_0.h5ad shard_1.h5ad
53
+ ```
54
+
55
+ ## Features
56
+
57
+ - Shards and merges `X`, all `layers`, `obs`, `var`, `obsm`, and `uns`
58
+ - Handles both dense and sparse (CSR) matrices
59
+ - Constant, low memory footprint regardless of file size
60
+ - Input supports both `.h5ad` and `.zarr` formats for slicing
61
+ - Merge output supports both `.h5ad` and `.zarr` formats
62
+ - Optional **cell shuffling** (`--shuffle`) for representative shards without loading the full matrix
63
+ - Simple CLI and Python API
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install annslicer
69
+ ```
70
+
71
+ For Zarr input/output support (optional):
72
+
73
+ ```bash
74
+ pip install annslicer[zarr]
75
+ ```
76
+
77
+ ## CLI Usage
78
+
79
+ `annslicer` provides two subcommands: `slice` and `merge`.
80
+
81
+ ### Sharding a large file
82
+
83
+ ```bash
84
+ annslicer slice input.h5ad output_prefix --size 10000
85
+ ```
86
+
87
+ Both `.h5ad` and `.zarr` inputs are supported.
88
+
89
+ | Argument | Description |
90
+ |---|---|
91
+ | `input.h5ad` or `input.zarr` | Path to the source file |
92
+ | `output_prefix` | Prefix for output files (e.g. `atlas` → `atlas_shard001.h5ad`, …) |
93
+ | `--size N` | Number of cells per shard (default: `10000`) |
94
+ | `--shuffle` | Randomly assign cells to shards (each shard is a representative draw) |
95
+ | `--seed N` | Random seed for reproducible shuffling (requires `--shuffle`) |
96
+
97
+ **Example — basic sharding:**
98
+
99
+ ```bash
100
+ annslicer slice /data/large_atlas.h5ad /outputs/atlas --size 20000
101
+ ```
102
+
103
+ **Example — shuffled sharding from a large h5ad:**
104
+
105
+ ```bash
106
+ annslicer slice /data/large_atlas.h5ad /outputs/atlas --size 10000 --shuffle --seed 0
107
+ ```
108
+
109
+ Produces: `atlas_shard_0.h5ad`, `atlas_shard_1.h5ad`, …
110
+
111
+ ### Merging shards back into one file
112
+
113
+ ```bash
114
+ annslicer merge output.h5ad shard_0.h5ad shard_1.h5ad shard_2.h5ad
115
+ ```
116
+
117
+ Output format is inferred from the extension — use `.zarr` for Zarr output (requires `annslicer[zarr]`):
118
+
119
+ ```bash
120
+ annslicer merge output.zarr shard_0.h5ad shard_1.h5ad shard_2.h5ad
121
+ ```
122
+
123
+ ### Global options
124
+
125
+ | Flag | Description |
126
+ |---|---|
127
+ | `--debug` | Enable verbose debug-level logging |
128
+
129
+ ## Python API
130
+
131
+ ```python
132
+ from annslicer import shard_h5ad, merge_out_of_core
133
+
134
+ # Basic sharding (h5ad or zarr input)
135
+ shard_h5ad("large_atlas.h5ad", "atlas", shard_size=20000)
136
+ shard_h5ad("large_atlas.zarr", "atlas", shard_size=20000) # requires annslicer[zarr]
137
+
138
+ # Shuffled sharding — cells are randomly distributed across shards
139
+ shard_h5ad("large_atlas.h5ad", "atlas", shard_size=20000, shuffle=True, seed=0)
140
+
141
+ # Merge shards back into one file
142
+ merge_out_of_core(["atlas_shard_0.h5ad", "atlas_shard_1.h5ad"], "merged.h5ad")
143
+ ```
144
+
145
+ ## How it works
146
+
147
+ ### Slicing
148
+ 1. Opens the input file ("backed" AnnData for `.h5ad`; `anndata.io.sparse_dataset` for `.zarr`).
149
+ 2. If `shuffle=True`, generates a global cell permutation upfront using `numpy.random.default_rng`.
150
+ 3. For each shard, reads only the relevant rows from `X` and each layer via sorted fancy indexing — no full matrix is ever loaded into memory.
151
+ 4. When shuffling, rows are read in sorted index order (maximising sequential I/O) and then reordered in-memory to the desired shuffled order.
152
+ 5. Reassembles a valid `AnnData` object per shard and writes it to disk.
153
+
154
+ ### Merging
155
+ 1. Reads `obs`, `var`, and `uns` from the shards to build a skeleton output file.
156
+ 2. Scans shards to calculate total non-zero sizes for pre-allocation.
157
+ 3. Streams `X`, layers, and `obsm` data shard-by-shard directly into the pre-allocated output arrays.
158
+
159
+ > **Note:** CSC (column-compressed) sparse matrices are not supported for out-of-core row-slicing. Convert to CSR before sharding.
160
+
161
+ ## Benchmarks
162
+
163
+ Run on a dummy sparse anndata object with 200k cells and 10k genes.
164
+
165
+ ### For h5ad format
166
+
167
+ | Slicing method | Mean runtime (s) | Peak memory (MB) |
168
+ |---|---|---|
169
+ | `annslicer slice` | 0.584 | 211.4 |
170
+ | `anndata` backed | 0.601 | 203.7 |
171
+ | `annslicer slice --shuffle` | 1.731 | 221.8 |
172
+ | `anndata` backed with shuffle | 3.830 | 209.1 |
173
+
174
+ ### For zarr format
175
+
176
+ | Slicing method | Mean runtime (s) | Peak memory (MB) |
177
+ |---|---|---|
178
+ | `annslicer slice` | 1.050 | 62.1 |
179
+ | `anndata` backed | 0.799 | 54.4 |
180
+ | `annslicer slice --shuffle` | 5.544 | 142.9 |
181
+ | `anndata` backed with shuffle | 6.591 | 151.4 |
182
+
183
+ Based on these benchmarks, for making randomly shuffled data shards, we recommend using `annslicer slice --shuffle` on an h5ad format file.
184
+
185
+ ## License
186
+
187
+ BSD 3-clause