Skip to content

convert: Visium HD bins (spatial extra) - #81

Merged
Claptar merged 3 commits into
mainfrom
feat/visium-hd
Oct 26, 2026
Merged

Claptar merged 3 commits into
mainfrom
feat/visium-hd

Conversation

@Claptar

@Claptar Claptar commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Reads Visium HD, the follow-up STATUS listed first after 0.17.0. Space Ranger 3 and 4 write an HD run's counts at 2, 8 and 16 µm, each in a folder under binned_outputs/ laid out like a standard Visium outs/, but with the positions as Parquet only. pyarrow reads them, through a new spatial extra, which the Docker image also gets.

pip install 'pyadata-cli[spatial]'
adata convert outs -o bins.h5ad                    # 8 µm, as 10x recommends
adata convert outs -o bins_2um.h5ad --bin-size 2
adata convert outs/binned_outputs/square_016um -o bins_16um.h5ad

The output is laid out as for standard Visium:

  • obs: in_tissue, array_row, array_col;
  • obsm["spatial"]: [x, y] in full-resolution pixels, float64;
  • uns["spatial"][library]: the bin folder's images and scale factors (bin_size_um, microns_per_pixel).

Memory

Measured on 10x's SR 4.0.1 tiny HD sample, at the 2 µm bins: 11.2 million positions, 330,035 bins in the matrix.

  • formats/parquet.py reads a buffered stream, one set of columns per pass: first the barcodes, which the join matches as they come, then the numbers, for the write. Buffering halves the numeric pass.
  • BarcodeJoin holds barcodes as bytes, a quarter of the size of numpy's unicode.
  • Images are written in strips of rows as they are read, rather than being loaded at the start of the write. HD's hires image is 6000 × 5394 pixels, about 100 MB.
  • Result:
    • 2 µm: 3.5 s, with a peak of 470 MB without images and 610 MB with them. pandas alone takes 1.1 GB just to read the positions.
    • 8 µm: 1.0 s, with a peak of 210 MB without images and 380 MB with them.

Checked

  • Real data: the output at 2, 8 and 16 µm equals pandas' reading of the same Parquet, reindexed to the matrix. A squidpy plot puts 99.9% of the bins on the image's tissue; the tiny sample is a crop of a larger slide.
  • Unit tests:
    • an HD fixture shaped like the real output: every bin in grid order in the positions, a shuffled subset in the matrix;
    • the --bin-size default and choice, and a bin folder given on its own;
    • --unfiltered;
    • refusals: a missing bin size, which lists those available, and --bin-size on standard Visium;
    • the missing extra, which names pyadata-cli[spatial].
  • Performance guard: an HD case is added to the spatial guards.
  • Integration: a comparison against scanpy plus pandas, since squidpy has no HD reader.
  • pycisTopic fixtures: with pyarrow in the dev environment, pandas 3 made the fixtures pickle ArrowStringArray, which real pycisTopic pickles never hold (pycisTopic pins pandas 1.5). The importer refused it, as designed. The fixtures now build object strings.

uv run pytest -m "not integration" --cov=adata --cov-fail-under=90: 1,981 passed, coverage 95.5%. The local site build passes html-proofer.

Not included: Space Ranger 4's cell segmentation (segmented_outputs/, GeoJSON). STATUS lists it as a follow-up.

🤖 Generated with Claude Code

Claptar and others added 2 commits October 26, 2026 19:46
A Space Ranger 3 or 4 HD outs/ converts at the bin size --bin-size names
(8 µm by default, as 10x recommends for analysis); a bin folder given
directly reads as it is. Its positions are Parquet only, read with
pyarrow, which comes with a new `spatial` extra (and is in the image).

- formats/parquet.py reads a buffered stream, one set of columns per
  pass: the barcodes for the join, then the numbers for the write. On
  10x's SR 4.0.1 tiny sample, 2 µm bins (11.2 million positions, 330,035
  in the matrix) convert in 3.5 s with a 470 MB peak without images, where
  pandas alone takes 1.1 GB to read the positions.
- The barcode join holds barcodes as bytes, a quarter of numpy's unicode.
- Images are written a strip of rows at a time as they are read, not
  loaded up front: HD's hires image is 6,000 pixels, some 100 MB.

Checked on the real sample at 2, 8 and 16 µm against pandas reading the
same Parquet, and in CI against scanpy and pandas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Visium page gains a Visium HD section (layout, --bin-size, the
spatial extra, how the Parquet positions stream, measured cost); the
formats tables, convert guide and help, Home, README, pipelines guide,
Troubleshooting, Developers and AGENTS.md name the extra. STATUS records
the work.

With pyarrow in the dev environment, pandas 3 builds string columns as
ArrowStringArray, so the pycisTopic fixtures pickled a class no real
pycisTopic pickle holds (pycisTopic pins pandas 1.5), and the importer's
allowlist rightly refused it. The fixtures now build object strings, as
pycisTopic's pandas does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 26, 2026 20:35
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-26T20:35:54.363944Z
We couldn’t complete this review. Comment @codex review to try again.
219e6f6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (py3.12)

1 986 tests   1 986 ✅  5m 35s ⏱️
    1 suites      0 💤
    1 files        0 ❌

Results for commit f1d6ffe.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (py3.13)

1 986 tests   1 986 ✅  4m 40s ⏱️
    1 suites      0 💤
    1 files        0 ❌

Results for commit f1d6ffe.

♻️ This comment has been updated with latest results.

- --bin-size with a matrix file given is refused, as --unfiltered is; it
  was ignored and the named matrix read.
- --bin-size 0 is a size the output lacks, not "not given".
- Nulls in the HD positions' integer columns are refused before writing
  (from the schema or footer statistics, else counted); they became made-up
  numbers in obs.
- A bin folder missing its scale factors or positions says which, without
  standard Visium's file names; the bin folder used is the one found.
- The Visium page says HD is checked against scanpy and pandas, not
  squidpy, and which peak is the --no-images one.
- STATUS: this goes out as 0.17.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Claptar
Claptar merged commit 542484e into main Oct 26, 2026
6 checks passed
@Claptar Claptar mentioned this pull request Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants