Skip to content

Commit 6e204fb

Browse files
committed
Add manuscript package for renal pilot
1 parent a12e61f commit 6e204fb

41 files changed

Lines changed: 1977 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pyXenium is implemented in Python and uses `anndata`, `numpy`, `pandas`, `scipy`, `scikit-learn`, `zarr`, `fsspec`, `requests`, `aiohttp` and `click` according to `pyproject.toml`. The current repository version is `0.1.0`, and the declared Python requirement is `>=3.8`. Source code is available at `https://github.com/hutaobo/pyXenium` and may also be distributed through [PyPI URL placeholder]. Documentation source files are present under `docs/`; the deployed documentation URL should be inserted here as [documentation URL placeholder]. The current license is `LicenseRef-Proprietary-NonCommercial`, which permits non-commercial source use; if a different release license is chosen before submission, this sentence should be updated accordingly. Operating-system support should be stated explicitly at submission as [validated platform statement placeholder].
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# pyXenium: robust loading and multimodal analysis of 10x Xenium outputs
2+
3+
[Author 1]^1, [Author 2]^1,* and [Author 3]^2
4+
5+
^1[Affiliation placeholder]
6+
7+
^2[Affiliation placeholder]
8+
9+
*To whom correspondence should be addressed.
10+
11+
## Abstract
12+
13+
**Summary:** 10x Genomics Xenium outputs combine sparse count matrices, cell tables, clustering results and optional boundary files, and practical analyses often begin with exports that are incomplete or stored in different matrix backends. pyXenium is a Python package for loading these outputs into `AnnData` while preserving modality separation and spatial annotations. In a validated smoke test on the public Xenium FFPE human renal cell carcinoma RNA+Protein dataset, pyXenium recovered 465,545 cells, 405 RNA features and 27 protein markers, reproduced the reported detected-cell count, and attached spatial coordinates and cluster labels under both automatic and explicit HDF5 loading. A separate partial loader supports counts-first recovery from incomplete exports and degrades to structured metadata rather than immediate failure when key artifacts are absent.
14+
15+
**Availability and implementation:** Implemented in Python. Source code: `https://github.com/hutaobo/pyXenium`. Package index: [PyPI URL placeholder if published]. Documentation: [documentation URL placeholder]. Current repository version: `0.1.0`. License: `LicenseRef-Proprietary-NonCommercial`.
16+
17+
**Contact:** [corresponding.author@institution.edu]
18+
19+
**Supplementary information:** Figure-generation code and validation outputs for this draft are stored under `manuscript/` in the repository. Additional supplementary information: [supplementary materials placeholder].
20+
21+
## 1 Introduction
22+
23+
10x Genomics Xenium experiments produce multiple output components rather than a single analysis-ready table. A typical run may include a cell-feature matrix in Zarr, HDF5 or MEX form, a per-cell table, clustering results, spatial centroid information and optional boundary files. Downstream single-cell and spatial analysis workflows, however, usually begin from a single `AnnData`-like object. In practice, loading Xenium data is therefore not only a file-parsing step but also an object-reconstruction step.
24+
25+
Two practical problems motivated pyXenium. First, Xenium RNA+Protein experiments need explicit separation of RNA counts from protein measurements while keeping both modalities aligned at the cell level. Second, users often work with incomplete exports, copied subsets of a run, or archives in which only part of the expected directory structure is available. Under those conditions, a loader that assumes one exact file layout can fail before any analysis begins.
26+
27+
pyXenium addresses these problems as an engineering and reproducibility contribution rather than as a new statistical method. The package provides a multimodal Xenium loader, a second loader for partial exports, a small command-line interface, a bundled toy dataset and optional downstream modules for protein-gene spatial correlation and RNA/protein joint analysis. This application note focuses on the loading and validation layers of the repository, because those are the components directly supported by real-data smoke testing and by the current test suite (Fig. 1).
28+
29+
## 2 Implementation
30+
31+
The public API exposed in `src/pyXenium` centers on two loader functions. `load_xenium_gene_protein` is designed for Xenium RNA+Protein outputs. It searches for a usable `cell_feature_matrix` in Zarr, HDF5 or MEX format, with `prefer="auto"` trying Zarr first, then HDF5, then MEX. The loader reads the matrix, inspects the `feature_type` annotation and splits the resulting features into RNA and protein modalities. RNA counts are stored in `adata.X` and mirrored in `adata.layers["rna"]`; protein measurements are stored as a per-cell `DataFrame` in `adata.obsm["protein"]`.
32+
33+
The same loader then enriches the object with run-level metadata. It reindexes the cell table to Xenium barcodes, reads clustering assignments from `analysis/clustering/gene_expression_graphclust/clusters.csv` when available, stores them in `adata.obs["cluster"]`, and adds centroid coordinates to `adata.obsm["spatial"]` when centroid columns are present in the cell table. If `cell_boundaries.csv.gz` or `nucleus_boundaries.csv.gz` exist, the raw boundary tables are attached in `adata.uns`. The loader also records modality metadata in `adata.uns["modality"]`, including the protein value type `"scaled_mean_intensity"`.
34+
35+
`load_anndata_from_partial` addresses incomplete exports. This entry point can combine any available MEX triplet with optional `analysis.zarr[.zip]`, `cells.zarr[.zip]` and `transcripts.zarr[.zip]` inputs. The implementation includes a ZIP-aware Zarr opener that detects nested Zarr roots inside archives, supports both local and remote paths, and assembles an `AnnData` object even when optional attachments are missing. When a MEX triplet is available, the function returns a counts-bearing object with feature metadata. When the MEX triplet is absent and `build_counts_if_missing=True`, it returns an empty `AnnData` together with parsed `analysis` and `cells` summaries in `adata.uns`, allowing callers to inspect what was available rather than receiving an immediate hard failure.
36+
37+
The repository includes additional reproducibility infrastructure around these loaders. `pyXenium.validation.renal_ffpe_protein` implements a smoke-test workflow for a public 10x Genomics Xenium FFPE human renal cell carcinoma RNA+Protein dataset. The smoke test can be called directly from `examples/smoke_test_10x_renal_ffpe_protein.py` or through the CLI command `pyxenium validate-renal-ffpe-protein` (equivalently `python -m pyXenium validate-renal-ffpe-protein`). Both routes produce machine-readable JSON and optional Markdown/CSV summaries. The package also ships a tiny bundled `toy_slide` dataset and CLI commands for demonstration and dataset copying.
38+
39+
## 3 Validation and use case
40+
41+
We validated the main multimodal loader on the public 10x Genomics dataset `Xenium In Situ Gene and Protein Expression data for FFPE Human Renal Cell Carcinoma`, using the repository smoke-test workflow. The smoke test was executed locally against a downloaded copy of the dataset with `prefer="auto"` and again with `prefer="h5"`. Both runs produced the same summary: 465,545 cells, 405 RNA features, 27 protein markers and 16,454,170 non-zero RNA matrix entries. In both runs, `adata.obsm["spatial"]` and `adata.obs["cluster"]` were present, `metrics_summary.csv` reported `num_cells_detected=465545`, and the validation payload contained no issues. This agreement matters because the default path and the explicit HDF5 path exercise different loader branches while arriving at the same cell, feature and metadata totals.
42+
43+
Direct inspection of the loaded object confirmed the structure expected by downstream workflows. On the validated renal dataset, `load_xenium_gene_protein` returned an `AnnData` object of shape 465,545 x 405, with `adata.layers["rna"]`, `adata.obsm["protein"]` and `adata.obsm["spatial"]` present. The spatial matrix had shape 465,545 x 2. `adata.obs["cluster"]` was categorical, and both `cell_boundaries` and `nucleus_boundaries` were attached under `adata.uns`. The loader therefore preserved not only the RNA matrix but also protein measurements, spatial centroids, clustering assignments and raw boundary tables in one aligned object.
44+
45+
We also evaluated the partial-loading path in two conditions. First, when `load_anndata_from_partial` was given only the real dataset `cell_feature_matrix` MEX directory and no `cells` or `analysis` attachments, it still returned an `AnnData` object of shape 465,545 x 543 with a `counts` layer and feature annotations. The recovered features comprised 405 gene-expression rows, 27 protein-expression rows and 111 control or unassigned rows, showing that a counts-first workflow can proceed even when optional spatial or clustering files are unavailable. Second, on the bundled toy dataset with no MEX triplet, the same function returned an empty `AnnData` but still populated `adata.uns["analysis"]` and `adata.uns["cells"]`, demonstrating the intended metadata-preserving fallback behavior for severely partial exports.
46+
47+
Repository-level checks support these data-level validations. In the current local repository state used for this manuscript draft, `pytest -q` collected and passed six tests. These tests cover the demo CLI, the toy dataset loader, bundled dataset copying, the public dataset catalog, the smoke-report rendering helper and the CLI wrapper for the renal FFPE validation command. Taken together, the real-data smoke test, the partial-loader checks and the passing test suite support a conservative claim: pyXenium provides a reproducible way to turn Xenium outputs into analysis-ready Python objects, with explicit support for RNA+Protein data and for incomplete-export recovery.
48+
49+
## 4 Availability and implementation
50+
51+
pyXenium is implemented in Python and uses `anndata`, `numpy`, `pandas`, `scipy`, `scikit-learn`, `zarr`, `fsspec`, `requests`, `aiohttp` and `click` according to `pyproject.toml`. The current repository version is `0.1.0`, and the declared Python requirement is `>=3.8`. Source code is available at `https://github.com/hutaobo/pyXenium` and may also be distributed through [PyPI URL placeholder]. Documentation source files are present under `docs/`; the deployed documentation URL should be inserted here as [documentation URL placeholder]. The current license is `LicenseRef-Proprietary-NonCommercial`, which permits non-commercial source use; if a different release license is chosen before submission, this sentence should be updated accordingly. Operating-system support should be stated explicitly at submission as [validated platform statement placeholder].
52+
53+
## Figure Legends
54+
55+
**Figure 1. Evidence-backed summary of pyXenium loading and validation.**
56+
**(A)** Real-data smoke-test summary for the public 10x Genomics FFPE human renal cell carcinoma Xenium RNA+Protein dataset. The automatic loader path and explicit HDF5 path produced identical summaries, recovering 465,545 cells, 405 RNA features and 27 protein markers, while preserving spatial coordinates and cluster labels and returning no validation issues.
57+
**(B)** Top five RNA features by total counts in the validated `prefer="auto"` smoke test. Bars show total counts in millions, and text annotations report the number of cells with non-zero counts for each feature.
58+
**(C)** Top five protein markers by mean signal in the validated `prefer="auto"` smoke test. Bars show mean protein signal, and text annotations report the number of positive cells for each marker.
59+
**(D)** Additional evidence from the same repository validation workflow. Left: sizes of the five largest graph-based clusters recovered from the validated renal dataset. Right: feature-type composition of the real MEX-only partial load, which returned a 465,545 x 543 counts object containing gene-expression, protein-expression and control features without requiring optional spatial or clustering attachments.
60+
61+
## Funding
62+
63+
This work was supported by [Funding information placeholder].
64+
65+
## Conflict of Interest
66+
67+
Conflict of Interest: [Authors to complete. If none, use "none declared."]
68+
69+
## References
70+
71+
1. 10x Genomics. Xenium In Situ Gene and Protein Expression data for FFPE Human Renal Cell Carcinoma. Available at: https://www.10xgenomics.com/datasets/xenium-protein-ffpe-human-renal-carcinoma
72+
2. [Xenium platform and file-format reference placeholder]
73+
3. [AnnData citation placeholder]
74+
4. [scikit-learn citation placeholder if retained]
75+
5. [Additional spatial transcriptomics context reference placeholder]

manuscript/cover_note.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Cover Note
2+
3+
## Evidence used
4+
5+
- Repository inspection:
6+
- `README.md`
7+
- `pyproject.toml`
8+
- `src/pyXenium/io/partial_xenium_loader.py`
9+
- `src/pyXenium/io/xenium_gene_protein_loader.py`
10+
- `src/pyXenium/validation/renal_ffpe_protein.py`
11+
- `src/pyXenium/__main__.py`
12+
- `src/pyXenium/datasets/catalog.py`
13+
- `tests/`
14+
- `docs/`
15+
- Real-data runs performed locally on the public 10x FFPE renal carcinoma Xenium RNA+Protein dataset:
16+
- smoke test with `prefer="auto"`
17+
- smoke test with `prefer="h5"`
18+
- direct object inspection with `load_xenium_gene_protein`
19+
- `load_anndata_from_partial` on the real `cell_feature_matrix` MEX directory
20+
- Additional code-behavior check:
21+
- `load_anndata_from_partial` on the bundled toy dataset without MEX
22+
- Reproducibility checks:
23+
- `pytest --collect-only -q` collected 6 tests
24+
- `pytest -q` passed locally with 6 tests
25+
26+
## Claims strongly supported
27+
28+
- pyXenium can load the validated renal FFPE Xenium RNA+Protein dataset into `AnnData`.
29+
- The `auto` and explicit `h5` loader paths recovered the same validated summary:
30+
- `465545` cells
31+
- `405` RNA features
32+
- `27` protein markers
33+
- `16454170` RNA non-zero entries
34+
- spatial coordinates present
35+
- cluster labels present
36+
- no smoke-test issues
37+
- The validated loaded object contains:
38+
- `adata.layers["rna"]`
39+
- `adata.obsm["protein"]`
40+
- `adata.obsm["spatial"]`
41+
- categorical `adata.obs["cluster"]`
42+
- `cell_boundaries` and `nucleus_boundaries` in `adata.uns`
43+
- `load_anndata_from_partial` can recover a counts object from the real MEX directory alone:
44+
- shape `465545 x 543`
45+
- counts layer present
46+
- feature metadata preserved
47+
- optional spatial and clustering attachments not required
48+
- The repository currently includes a bundled toy dataset, a validation module, a validation CLI command and a passing local pytest run with 6 tests.
49+
50+
## Claims intentionally avoided
51+
52+
- I did not claim algorithmic novelty beyond software robustness and data handling, because the strongest direct evidence is for loading, validation and reproducibility rather than for a new statistical method.
53+
- I did not claim runtime or memory advantages, because no benchmark suite for those quantities was generated here.
54+
- I did not claim biological findings from the renal dataset beyond the observed loaded counts, top-feature summaries and presence of metadata, because this manuscript draft is positioned as a software note.
55+
- I did not describe the current license as open source, because the repository metadata states `LicenseRef-Proprietary-NonCommercial`.
56+
57+
## Metadata to verify before submission
58+
59+
- Author list, affiliations and corresponding author email
60+
- Final GitHub, PyPI and documentation URLs
61+
- Whether to archive the release on Zenodo and insert a DOI
62+
- Final package version to cite in the manuscript
63+
- Funding statement
64+
- Conflict-of-interest statement
65+
- Preferred software/data references
66+
- Operating-system support statement for the availability paragraph
67+
68+
## Submission optics to consider
69+
70+
- `pyproject.toml` currently describes pyXenium as `"A toy Python package for analyzing 10x Xenium data."` This wording is not suitable for manuscript or release metadata and should be strengthened before submission.
71+
- The current non-commercial source-available license is compatible with the manuscript's factual description, but it may read less favorably than a standard open-source license in reviewer and editor assessment. If the license changes, update the manuscript availability paragraph accordingly.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"shape": [
3+
465545,
4+
405
5+
],
6+
"layers": [
7+
"rna"
8+
],
9+
"obsm_keys": [
10+
"protein",
11+
"spatial"
12+
],
13+
"uns_keys_subset": [
14+
"cell_boundaries",
15+
"modality",
16+
"nucleus_boundaries"
17+
],
18+
"obs_columns_sample": [
19+
"x_centroid",
20+
"y_centroid",
21+
"transcript_counts",
22+
"control_probe_counts",
23+
"genomic_control_counts",
24+
"control_codeword_counts",
25+
"unassigned_codeword_counts",
26+
"deprecated_codeword_counts",
27+
"total_counts",
28+
"cell_area",
29+
"nucleus_area",
30+
"nucleus_count"
31+
],
32+
"has_cluster": true,
33+
"cluster_dtype": "category",
34+
"protein_columns_sample": [
35+
"PD-1",
36+
"VISTA",
37+
"PD-L1",
38+
"LAG-3",
39+
"CD16",
40+
"GranzymeB",
41+
"CD163",
42+
"CD4"
43+
],
44+
"spatial_shape": [
45+
465545,
46+
2
47+
],
48+
"modality_uns": {
49+
"rna": {
50+
"feature_type": "Gene Expression"
51+
},
52+
"protein": {
53+
"feature_type": "Protein Expression",
54+
"value": "scaled_mean_intensity"
55+
}
56+
}
57+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"shape": [
3+
465545,
4+
543
5+
],
6+
"layers": [
7+
"counts"
8+
],
9+
"has_spatial": false,
10+
"has_cluster": false,
11+
"uns_keys": [
12+
"io"
13+
],
14+
"var_columns": [
15+
"feature_id",
16+
"feature_name",
17+
"feature_type",
18+
"feature_types",
19+
"total_counts"
20+
],
21+
"feature_type_counts": {
22+
"Gene Expression": 405,
23+
"Negative Control Codeword": 41,
24+
"Unassigned Codeword": 35,
25+
"Protein Expression": 27,
26+
"Negative Control Probe": 20,
27+
"Genomic Control": 15
28+
}
29+
}

manuscript/evidence/pytest_q.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
...... [100%]
2+
============================== warnings summary ===============================
3+
C:\Users\taobo.hu\AppData\Local\miniconda3\Lib\site-packages\anndata\_settings.py:16
4+
C:\Users\taobo.hu\AppData\Local\miniconda3\Lib\site-packages\anndata\_settings.py:16: DeprecationWarning: anndata will no longer support zarr v2 in the near future. Please prepare to upgrade to zarr>=3.
5+
from .compat import is_zarr_v2, old_positionals
6+
7+
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
8+
6 passed, 1 warning in 5.77s
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cluster,n_cells
2+
1,87757
3+
2,67261
4+
3,59896
5+
4,53975
6+
5,35331
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# pyXenium Smoke Test Report
2+
3+
Dataset: Xenium In Situ Gene and Protein Expression data for FFPE Human Renal Cell Carcinoma
4+
Source: https://www.10xgenomics.com/datasets/xenium-protein-ffpe-human-renal-carcinoma
5+
Local path: `Y:/long/10X_datasets/Xenium/Xenium_Renal/Xenium_V1_Human_Kidney_FFPE_Protein`
6+
Backend preference: `auto`
7+
8+
## Core Results
9+
10+
- Cells: `465545`
11+
- RNA features: `405`
12+
- Protein markers: `27`
13+
- Sparse matrix nnz: `16454170`
14+
- Spatial coordinates present: `True`
15+
- Cluster labels present: `True`
16+
- metrics_summary.csv detected cells: `465545`
17+
18+
## Validated Reference
19+
20+
- Expected cells: `465545`
21+
- Expected RNA features: `405`
22+
- Expected protein markers: `27`
23+
24+
## Largest Clusters
25+
26+
- `1`: `87757` cells
27+
- `2`: `67261` cells
28+
- `3`: `59896` cells
29+
- `4`: `53975` cells
30+
- `5`: `35331` cells
31+
32+
## Top RNA Features by Total Counts
33+
34+
- `VIM`: total counts `9627261`, detected cells `438708`
35+
- `HLA-DRA`: total counts `3621954`, detected cells `382120`
36+
- `HLA-DRB1`: total counts `980650`, detected cells `278995`
37+
- `CXCL6`: total counts `963037`, detected cells `128336`
38+
- `FCGR3A`: total counts `943799`, detected cells `213534`
39+
40+
## Top Protein Markers by Mean Signal
41+
42+
- `Vimentin`: mean signal `234.7700`, positive cells `455851`
43+
- `CD45`: mean signal `206.9365`, positive cells `446921`
44+
- `PTEN`: mean signal `149.3354`, positive cells `464946`
45+
- `CD3E`: mean signal `142.4783`, positive cells `285619`
46+
- `CD68`: mean signal `120.7367`, positive cells `244801`
47+
48+
## Issues
49+
50+
- No issues detected.

0 commit comments

Comments
 (0)