Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
35946aa
Add PyxaKeys constants for Pyxa reader
ckmah Sep 23, 2026
ab8c165
Add subsampled Pyxa test fixture
ckmah Sep 23, 2026
667649f
Add _validate_columns helper for Pyxa reader
ckmah Sep 23, 2026
88cc5fa
Add _get_points loader for Pyxa reader
ckmah Sep 23, 2026
3cd1ce2
Add _get_table loader for Pyxa reader
ckmah Sep 23, 2026
0d32578
Add _get_shapes loader for Pyxa reader
ckmah Sep 23, 2026
48ee184
Add pyxa() reader function
ckmah Sep 23, 2026
5b5e4ad
Add pyxa CLI command and register as experimental reader
ckmah Sep 23, 2026
01983c2
Document Pyxa in README experimental readers section
ckmah Sep 23, 2026
571fa19
Apply pre-commit fixes (isort, pyupgrade, line endings)
ckmah Sep 23, 2026
ca3245f
Untrack uv.lock
ckmah Sep 23, 2026
9390a2a
Add optional mosaic image support to pyxa reader
ckmah Sep 24, 2026
18c2d98
Adapt pyxa reader to upstream CLI, mypy and ruff conventions
ckmah Sep 24, 2026
47ed13e
Convert Pyxa shapes to micrometers and test against public xsmall data
ckmah Sep 24, 2026
2e01fe7
Split Pyxa shapes into footprints and z-planes, load all mosaic scales
ckmah Sep 24, 2026
d7bc44f
pyxa: optional inputs (studio clusters, transcripts, shapes, images) …
ckmah Sep 25, 2026
30bf5c4
pyxa: sparse counts; keys for the mosaic file and cell labels
ckmah Sep 28, 2026
86d5d6b
pyxa: find the mosaic in the directory, zipped or not; image= replace…
ckmah Sep 28, 2026
06353c1
pyxa: mosaic grid shared by image and labels; integer label ids from …
ckmah Sep 28, 2026
9aa9032
pyxa tests: sort imports, drop unused import
ckmah Sep 28, 2026
b90a534
pyxa: decode segmentation polygons into rings on the mosaic grid
ckmah Sep 28, 2026
9d98693
pyxa: empty rings for an empty segmentation file
ckmah Sep 28, 2026
f090fcc
pyxa: plan label tiles and draw them lazily, one dask task per tile
ckmah Sep 28, 2026
197d1af
pyxa: multiscale 3D cell labels on the mosaic grid
ckmah Sep 28, 2026
cc6ed0e
pyxa: coarse label levels are strided views of level 0
ckmah Sep 28, 2026
8c8b45b
pyxa tests: cover the short-level guard
ckmah Sep 28, 2026
d6373af
pyxa: labels= rasterizes 3D cell labels annotated by the table; shape…
ckmah Sep 28, 2026
1452ac2
pyxa: decode polygon row groups in worker processes
ckmah Sep 29, 2026
1c41b17
pyxa: decode polygons in batches to bound memory
ckmah Sep 29, 2026
bf13de5
pyxa: assign tiles from padded bounds so seams match
ckmah Sep 29, 2026
ba0fa39
pyxa tests: type-clean labels tests for mypy
ckmah Sep 29, 2026
cb1ec64
pyxa: configurable worker pool, shut down after decode
ckmah Sep 29, 2026
dbbd4e0
pyxa: sample coarse labels at voxel centres
ckmah Sep 29, 2026
8517749
pyxa: docs, changelog, logging, CLI conflict, zip root
ckmah Sep 29, 2026
3184841
pyxa: keep the reader in one module
ckmah Sep 29, 2026
32bc0e5
pyxa: coarse label steps from the mosaic's OME scales
ckmah Sep 29, 2026
9a85304
pyxa tests: fetch the xsmall fixture when the CI artifact lacks it; m…
ckmah Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/prepare_test_data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ jobs:
# OMAP10 for format v0.x.x
curl -o OMAP10_small.zip "https://zenodo.org/api/records/18196366/files-archive"

# -------
# Stellaromics Pyxa, 100 um cube cropped from the public demo dataset
# https://huggingface.co/datasets/Stellaromics/demo
mkdir -p pyxa_xsmall
for file in cell_assigned_gene_v1.csv cell_by_gene_v1.csv cell_metadata_v1.csv segmentation_geometries_v1.parquet mosaic_3d.ome.zarr.zip; do
curl -L -o "pyxa_xsmall/$file" "https://huggingface.co/datasets/Stellaromics/demo/resolve/main/xsmall/$file"
done
# the zipped OME-Zarr mosaic is extracted in place (it contains a single `mosaic_3d.ome.zarr/` directory)
unzip -q pyxa_xsmall/mosaic_3d.ome.zarr.zip -d pyxa_xsmall
rm pyxa_xsmall/mosaic_3d.ome.zarr.zip

- name: Unzip files
run: |
cd ./data
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Release notes for `v0.7.1` and earlier are available on the [Releases][] page.
### Added

- `spatialdata_io` ships a `py.typed` marker, so downstream type checkers use its annotations.
- Experimental `pyxa` reader (Stellaromics Pyxa): table (sparse counts, Pyxa Studio clusters/UMAP), transcripts, segmentation shapes, mosaic image (directory or zip), and with `labels=True` 3D cell labels on the mosaic grid.

### Changed

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ Contributions for addressing the below limitations are very welcomed.

- Only Stereo-seq 7.x is supported, 8.x is not currently supported. https://github.com/scverse/spatialdata-io/issues/161

## Experimental readers

Readers without (yet) a public specification for their raw data format live
in `spatialdata_io.experimental` rather than the main technology list above.
No stability guarantees are made for these.

- Pyxa (Stellaromics): no public format specification yet; validated against
the public [demo dataset](https://huggingface.co/datasets/Stellaromics/demo).
`labels=True` adds 3D cell labels on the mosaic grid.

## Getting started

Please refer to the [documentation][link-docs]. In particular, the
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies = [
"joblib",
"numpy",
"ome-types",
"pillow",
"pyarrow",
"readfcs",
"scanpy",
Expand Down Expand Up @@ -157,6 +158,7 @@ exclude = "^tests/data/"
module = [
"dask_image.*",
"h5py.*",
"joblib.*",
"multiscale_spatial_image.*",
"pyarrow.*",
"rasterio.*",
Expand Down
55 changes: 55 additions & 0 deletions src/spatialdata_io/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,61 @@ def macsima_wrapper(
sdata.write(output)


@cli.command(name="pyxa")
@_input_output_click_options
@click.option("--dataset-id", type=str, default="pyxa", help="Dataset ID. [default: pyxa]")
@click.option(
"--image",
type=click.Path(exists=True, file_okay=True, dir_okay=True),
default=None,
help="Mosaic OME-Zarr directory or zip, if not in the input directory. [default: found in the input]",
)
@click.option("--no-image", is_flag=True, default=False, help="Leave out the mosaic even if present.")
@click.option(
"--pyxa-studio",
type=click.Path(exists=True, file_okay=True, dir_okay=False),
default=None,
help="Path to a Pyxa Studio export (cluster labels, UMAP) outside the input directory. [default: None]",
)
@click.option(
"--skip",
type=click.Choice(["cell_assigned_gene", "segmentation_geometries", "pyxa_studio"]),
multiple=True,
help="Optional input file to leave out even if present; repeatable. [default: none]",
)
@click.option("--labels", is_flag=True, default=False, help="Rasterize 3D cell labels onto the mosaic's grid.")
@click.option(
"--shapes/--no-shapes",
default=None,
help="Return the polygons as shapes. [default: when read and --labels is not set]",
)
def pyxa_wrapper(
input: str,
output: str,
dataset_id: str = "pyxa",
image: str | None = None,
no_image: bool = False,
pyxa_studio: str | None = None,
skip: tuple[str, ...] = (),
labels: bool = False,
shapes: bool | None = None,
) -> None:
"""Pyxa (Stellaromics) conversion to SpatialData."""
from spatialdata_io.experimental import pyxa

if no_image and image is not None:
raise click.UsageError("--image and --no-image are mutually exclusive")
inputs: dict[str, str | bool] = dict.fromkeys(skip, False)
if pyxa_studio is not None and "pyxa_studio" not in skip:
inputs["pyxa_studio"] = pyxa_studio
if no_image:
inputs["image"] = False
elif image is not None:
inputs["image"] = image
sdata = pyxa(input, dataset_id=dataset_id, labels=labels, shapes=shapes, **inputs) # type: ignore[arg-type]
sdata.write(output)


@cli.command(name="generic")
@click.option(
"--input",
Expand Down
56 changes: 56 additions & 0 deletions src/spatialdata_io/_constants/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,59 @@ class VisiumHDKeys(ModeEnum):
# Cell Segmentation keys
CELL_SEG_KEY_HD = "cell_segmentations"
NUCLEUS_SEG_KEY_HD = "nucleus_segmentations"


class PyxaKeys(ModeEnum):
"""Keys for *Pyxa* (Stellaromics) output.

No public specification exists yet; keys are validated against the public
demo dataset at https://huggingface.co/datasets/Stellaromics/demo.
"""

# files
CELL_ASSIGNED_GENE_FILE = "cell_assigned_gene_v1.csv"
CELL_BY_GENE_FILE = "cell_by_gene_v1.csv"
CELL_METADATA_FILE = "cell_metadata_v1.csv"
SEGMENTATION_GEOMETRIES_FILE = "segmentation_geometries_v1.parquet"
# Pyxa Studio export: cells that passed Pyxa's filters, with cluster labels and a 3D UMAP
PYXA_STUDIO_FILE = "pyxa_studio_v1.csv"

# shared columns
CELL_ID = "cell_id"
GENE = "Gene"
X_UM = "X_um"
Y_UM = "Y_um"
Z_UM = "Z_um"
X_PIXELS = "X_pixels"
Y_PIXELS = "Y_pixels"
Z_PIXELS = "Z_pixels"
VOLUME_UM3 = "Volume_um3"
ROI = "ROI"
Z_INDEX = "ZIndex"
BORDER = "Border"
FOV = "FOV"
CLUSTER = "Cluster"
X_UMAP = "X_UMAP"
Y_UMAP = "Y_UMAP"
Z_UMAP = "Z_UMAP"

# unassigned transcripts have cell_id ending in this suffix, e.g. "Region_-1"
UNASSIGNED_SUFFIX = "_-1"

# constructed metadata
REGION_KEY = "region"
# per-cell footprint (union of the cell's z-plane polygons), annotated by the table
REGION = "cell_boundaries"
# per-cell, per-z-plane polygons, as stored on disk
CELL_BOUNDARIES_Z = "cell_boundaries_z"
INSTANCE_KEY = "cell_id"
ASSIGNED = "assigned"
MOSAIC_IMAGE = "mosaic_image"
UMAP_KEY = "X_umap"

# mosaic image, looked up in the Pyxa directory (unzipped or zipped, as on the Hub)
MOSAIC_FILE = "mosaic_3d.ome.zarr"
MOSAIC_ZIP_FILE = "mosaic_3d.ome.zarr.zip"
# 3D cell labels rasterized from the segmentation polygons onto the mosaic's grid
CELL_LABELS = "cell_labels"
LABEL_ID = "label_id"
2 changes: 2 additions & 0 deletions src/spatialdata_io/experimental/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
to_legacy_anndata,
)
from spatialdata_io.readers.iss import iss
from spatialdata_io.readers.pyxa import pyxa

_readers_technologies = [
"iss",
"pyxa",
]
_readers_file_types: list[str] = [
# add experimental readers for new file types here
Expand Down
Loading
Loading