Spaces:
Running
Running
Upload 633 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +18 -0
- scanpy/mcp_output/README_MCP.md +73 -0
- scanpy/mcp_output/analysis.json +1102 -0
- scanpy/mcp_output/env_info.json +15 -0
- scanpy/mcp_output/mcp_logs/llm_statistics.json +11 -0
- scanpy/mcp_output/mcp_logs/run_log.json +74 -0
- scanpy/mcp_output/mcp_plugin/__init__.py +0 -0
- scanpy/mcp_output/mcp_plugin/__pycache__/adapter.cpython-310.pyc +0 -0
- scanpy/mcp_output/mcp_plugin/__pycache__/mcp_service.cpython-310.pyc +0 -0
- scanpy/mcp_output/mcp_plugin/adapter.py +142 -0
- scanpy/mcp_output/mcp_plugin/main.py +13 -0
- scanpy/mcp_output/mcp_plugin/mcp_service.py +606 -0
- scanpy/mcp_output/requirements.txt +17 -0
- scanpy/mcp_output/start_mcp.py +34 -0
- scanpy/mcp_output/tests_mcp/test_mcp_basic.py +49 -0
- scanpy/mcp_output/tests_smoke/test_smoke.py +29 -0
- scanpy/source/.codecov.yml +16 -0
- scanpy/source/.editorconfig +13 -0
- scanpy/source/.github/ISSUE_TEMPLATE/bug-report.yml +80 -0
- scanpy/source/.github/ISSUE_TEMPLATE/config.yml +5 -0
- scanpy/source/.github/ISSUE_TEMPLATE/enhancement-request.yml +26 -0
- scanpy/source/.github/dependabot.yml +10 -0
- scanpy/source/.github/pull_request_template.md +13 -0
- scanpy/source/.github/workflows/benchmark.yml +57 -0
- scanpy/source/.github/workflows/check-pr.yml +67 -0
- scanpy/source/.github/workflows/ci.yml +125 -0
- scanpy/source/.github/workflows/publish.yml +25 -0
- scanpy/source/.gitignore +48 -0
- scanpy/source/.gitmodules +3 -0
- scanpy/source/.pre-commit-config.yaml +44 -0
- scanpy/source/.readthedocs.yml +25 -0
- scanpy/source/.taplo.toml +5 -0
- scanpy/source/.vscode/launch.json +26 -0
- scanpy/source/.vscode/settings.json +22 -0
- scanpy/source/CONTRIBUTING.md +20 -0
- scanpy/source/LICENSE +30 -0
- scanpy/source/README.md +62 -0
- scanpy/source/__init__.py +4 -0
- scanpy/source/benchmarks/README.md +21 -0
- scanpy/source/benchmarks/asv.conf.json +169 -0
- scanpy/source/benchmarks/benchmarks/__init__.py +1 -0
- scanpy/source/benchmarks/benchmarks/__pycache__/__init__.cpython-310.pyc +0 -0
- scanpy/source/benchmarks/benchmarks/_utils.py +211 -0
- scanpy/source/benchmarks/benchmarks/preprocessing_counts.py +111 -0
- scanpy/source/benchmarks/benchmarks/preprocessing_log.py +74 -0
- scanpy/source/benchmarks/benchmarks/tools.py +46 -0
- scanpy/source/biome.jsonc +18 -0
- scanpy/source/ci/scripts/low-vers.py +180 -0
- scanpy/source/ci/scripts/towncrier_automation.py +122 -0
- scanpy/source/docs/Makefile +25 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,21 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
scanpy/source/docs/_static/img/ci_plot-view_attachment-tab.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
scanpy/source/docs/_static/img/ci_plot-view_select-test.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
scanpy/source/docs/_static/img/ci_plot-view_tests-tab.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
scanpy/source/docs/_static/img/spatial-basic-analysis.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
scanpy/source/docs/_static/img/tutorials/170430_krumsiek11/timeseries.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
scanpy/source/docs/_static/img/tutorials/170505_seurat/cell_types.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
scanpy/source/docs/_static/img/tutorials/170505_seurat/louvain.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
scanpy/source/docs/_static/img/tutorials/170522_visualizing_one_million_cells/tsne_1.3M.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
scanpy/source/docs/_static/img/tutorials/paga_paul15.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
scanpy/source/docs/_static/img/tutorials/paga_planaria.png filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
scanpy/source/src/scanpy/datasets/10x_pbmc68k_reduced.h5ad filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
scanpy/source/tests/_data/10x-10k-subset.zarr/X/0.0 filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
scanpy/source/tests/_data/10x-10k-subset.zarr/X/1.0 filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
scanpy/source/tests/_data/10x-10k-subset.zarr/X/2.0 filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
scanpy/source/tests/_data/10x-10k-subset.zarr/X/3.0 filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
scanpy/source/tests/_data/10x-10k-subset.zarr/X/4.0 filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
scanpy/source/tests/_data/visium_data/1.0.0/spatial/tissue_lowres_image.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
scanpy/source/tests/_images/embedding_outline_vmin_vmax/expected.png filter=lfs diff=lfs merge=lfs -text
|
scanpy/mcp_output/README_MCP.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCP Plugin README
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The MCP Plugin is a versatile tool designed to enhance the functionality of the Scanpy library, a popular toolkit for single-cell gene expression analysis. This plugin provides additional capabilities and optimizations for handling large datasets, performing complex analyses, and generating insightful visualizations. The MCP Plugin integrates seamlessly with Scanpy, offering users an extended range of tools and utilities to facilitate their research.
|
| 6 |
+
|
| 7 |
+
## Installation
|
| 8 |
+
|
| 9 |
+
To install the MCP Plugin, ensure that you have Python and pip installed on your system. The plugin can be installed via pip with the following command:
|
| 10 |
+
|
| 11 |
+
```
|
| 12 |
+
pip install mcp-plugin
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
Ensure that you have the required dependencies installed. The MCP Plugin requires the following packages:
|
| 16 |
+
|
| 17 |
+
- numpy
|
| 18 |
+
- scipy
|
| 19 |
+
- pandas
|
| 20 |
+
- matplotlib
|
| 21 |
+
- h5py
|
| 22 |
+
|
| 23 |
+
Optional dependencies for enhanced functionality include:
|
| 24 |
+
|
| 25 |
+
- umap-learn
|
| 26 |
+
- leidenalg
|
| 27 |
+
|
| 28 |
+
## Usage
|
| 29 |
+
|
| 30 |
+
Once installed, the MCP Plugin can be used in conjunction with Scanpy to perform a variety of tasks. Below are some basic usage examples:
|
| 31 |
+
|
| 32 |
+
### Basic Usage
|
| 33 |
+
|
| 34 |
+
To use the MCP Plugin, first import it alongside Scanpy in your Python script:
|
| 35 |
+
|
| 36 |
+
```python
|
| 37 |
+
import scanpy as sc
|
| 38 |
+
import mcp_plugin as mcp
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### Command-Line Interface
|
| 42 |
+
|
| 43 |
+
The MCP Plugin provides a command-line interface (CLI) for executing tasks directly from the terminal. The CLI can be accessed using:
|
| 44 |
+
|
| 45 |
+
```
|
| 46 |
+
scanpy-cli
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
This command provides access to various tools and functionalities offered by the plugin.
|
| 50 |
+
|
| 51 |
+
## Available Tool Endpoints
|
| 52 |
+
|
| 53 |
+
The MCP Plugin extends Scanpy with several tool endpoints, including but not limited to:
|
| 54 |
+
|
| 55 |
+
- **Preprocessing Tools**: Enhanced data normalization and scaling methods.
|
| 56 |
+
- **Visualization Tools**: Advanced plotting capabilities for better data representation.
|
| 57 |
+
- **Analysis Tools**: Improved clustering and dimensionality reduction techniques.
|
| 58 |
+
|
| 59 |
+
For a complete list of available tools and their usage, refer to the plugin's documentation or use the help command in the CLI:
|
| 60 |
+
|
| 61 |
+
```
|
| 62 |
+
scanpy-cli --help
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
## Notes and Troubleshooting
|
| 66 |
+
|
| 67 |
+
- **Compatibility**: Ensure that your version of Scanpy is compatible with the MCP Plugin. Check the plugin's documentation for version compatibility.
|
| 68 |
+
- **Performance**: For large datasets, consider using the optional dependencies to improve performance and speed.
|
| 69 |
+
- **Troubleshooting**: If you encounter issues, verify that all dependencies are correctly installed and up to date. Consult the plugin's GitHub repository for additional support and issue tracking.
|
| 70 |
+
|
| 71 |
+
## Additional Resources
|
| 72 |
+
|
| 73 |
+
For more information, visit the [Scanpy GitHub repository](https://github.com/scverse/scanpy) and explore the extensive documentation and community resources available. The MCP Plugin aims to complement Scanpy's capabilities, providing researchers with powerful tools for single-cell analysis.
|
scanpy/mcp_output/analysis.json
ADDED
|
@@ -0,0 +1,1102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"summary": {
|
| 3 |
+
"repository_url": "https://github.com/scverse/scanpy",
|
| 4 |
+
"summary": "Imported via zip fallback, file count: 324",
|
| 5 |
+
"file_tree": {
|
| 6 |
+
".codecov.yml": {
|
| 7 |
+
"size": 260
|
| 8 |
+
},
|
| 9 |
+
".github/ISSUE_TEMPLATE/bug-report.yml": {
|
| 10 |
+
"size": 2451
|
| 11 |
+
},
|
| 12 |
+
".github/ISSUE_TEMPLATE/config.yml": {
|
| 13 |
+
"size": 192
|
| 14 |
+
},
|
| 15 |
+
".github/ISSUE_TEMPLATE/enhancement-request.yml": {
|
| 16 |
+
"size": 744
|
| 17 |
+
},
|
| 18 |
+
".github/dependabot.yml": {
|
| 19 |
+
"size": 193
|
| 20 |
+
},
|
| 21 |
+
".github/pull_request_template.md": {
|
| 22 |
+
"size": 662
|
| 23 |
+
},
|
| 24 |
+
".github/workflows/benchmark.yml": {
|
| 25 |
+
"size": 1322
|
| 26 |
+
},
|
| 27 |
+
".github/workflows/check-pr.yml": {
|
| 28 |
+
"size": 2604
|
| 29 |
+
},
|
| 30 |
+
".github/workflows/ci.yml": {
|
| 31 |
+
"size": 3659
|
| 32 |
+
},
|
| 33 |
+
".github/workflows/publish.yml": {
|
| 34 |
+
"size": 622
|
| 35 |
+
},
|
| 36 |
+
".pre-commit-config.yaml": {
|
| 37 |
+
"size": 1202
|
| 38 |
+
},
|
| 39 |
+
".readthedocs.yml": {
|
| 40 |
+
"size": 666
|
| 41 |
+
},
|
| 42 |
+
".taplo.toml": {
|
| 43 |
+
"size": 106
|
| 44 |
+
},
|
| 45 |
+
".vscode/launch.json": {
|
| 46 |
+
"size": 778
|
| 47 |
+
},
|
| 48 |
+
".vscode/settings.json": {
|
| 49 |
+
"size": 692
|
| 50 |
+
},
|
| 51 |
+
"CONTRIBUTING.md": {
|
| 52 |
+
"size": 948
|
| 53 |
+
},
|
| 54 |
+
"README.md": {
|
| 55 |
+
"size": 3747
|
| 56 |
+
},
|
| 57 |
+
"benchmarks/README.md": {
|
| 58 |
+
"size": 895
|
| 59 |
+
},
|
| 60 |
+
"benchmarks/asv.conf.json": {
|
| 61 |
+
"size": 6994
|
| 62 |
+
},
|
| 63 |
+
"benchmarks/benchmarks/__init__.py": {
|
| 64 |
+
"size": 38
|
| 65 |
+
},
|
| 66 |
+
"benchmarks/benchmarks/_utils.py": {
|
| 67 |
+
"size": 6345
|
| 68 |
+
},
|
| 69 |
+
"benchmarks/benchmarks/preprocessing_counts.py": {
|
| 70 |
+
"size": 3889
|
| 71 |
+
},
|
| 72 |
+
"benchmarks/benchmarks/preprocessing_log.py": {
|
| 73 |
+
"size": 2248
|
| 74 |
+
},
|
| 75 |
+
"benchmarks/benchmarks/tools.py": {
|
| 76 |
+
"size": 1201
|
| 77 |
+
},
|
| 78 |
+
"ci/scripts/low-vers.py": {
|
| 79 |
+
"size": 5783
|
| 80 |
+
},
|
| 81 |
+
"ci/scripts/towncrier_automation.py": {
|
| 82 |
+
"size": 3636
|
| 83 |
+
},
|
| 84 |
+
"docs/api/classes.md": {
|
| 85 |
+
"size": 281
|
| 86 |
+
},
|
| 87 |
+
"docs/api/datasets.md": {
|
| 88 |
+
"size": 427
|
| 89 |
+
},
|
| 90 |
+
"docs/api/deprecated.md": {
|
| 91 |
+
"size": 292
|
| 92 |
+
},
|
| 93 |
+
"docs/api/experimental.md": {
|
| 94 |
+
"size": 530
|
| 95 |
+
},
|
| 96 |
+
"docs/api/get.md": {
|
| 97 |
+
"size": 374
|
| 98 |
+
},
|
| 99 |
+
"docs/api/index.md": {
|
| 100 |
+
"size": 357
|
| 101 |
+
},
|
| 102 |
+
"docs/api/io.md": {
|
| 103 |
+
"size": 1079
|
| 104 |
+
},
|
| 105 |
+
"docs/api/metrics.md": {
|
| 106 |
+
"size": 313
|
| 107 |
+
},
|
| 108 |
+
"docs/api/plotting.md": {
|
| 109 |
+
"size": 2824
|
| 110 |
+
},
|
| 111 |
+
"docs/api/preprocessing.md": {
|
| 112 |
+
"size": 1580
|
| 113 |
+
},
|
| 114 |
+
"docs/api/queries.md": {
|
| 115 |
+
"size": 361
|
| 116 |
+
},
|
| 117 |
+
"docs/api/settings.md": {
|
| 118 |
+
"size": 1310
|
| 119 |
+
},
|
| 120 |
+
"docs/api/tools.md": {
|
| 121 |
+
"size": 1420
|
| 122 |
+
},
|
| 123 |
+
"docs/basic_usage.md": {
|
| 124 |
+
"size": 110
|
| 125 |
+
},
|
| 126 |
+
"docs/community.md": {
|
| 127 |
+
"size": 736
|
| 128 |
+
},
|
| 129 |
+
"docs/conf.py": {
|
| 130 |
+
"size": 8867
|
| 131 |
+
},
|
| 132 |
+
"docs/contributors.md": {
|
| 133 |
+
"size": 1344
|
| 134 |
+
},
|
| 135 |
+
"docs/dev/ci.md": {
|
| 136 |
+
"size": 959
|
| 137 |
+
},
|
| 138 |
+
"docs/dev/code.md": {
|
| 139 |
+
"size": 1098
|
| 140 |
+
},
|
| 141 |
+
"docs/dev/documentation.md": {
|
| 142 |
+
"size": 6711
|
| 143 |
+
},
|
| 144 |
+
"docs/dev/getting-set-up.md": {
|
| 145 |
+
"size": 3793
|
| 146 |
+
},
|
| 147 |
+
"docs/dev/index.md": {
|
| 148 |
+
"size": 535
|
| 149 |
+
},
|
| 150 |
+
"docs/dev/release.md": {
|
| 151 |
+
"size": 3378
|
| 152 |
+
},
|
| 153 |
+
"docs/dev/testing.md": {
|
| 154 |
+
"size": 4801
|
| 155 |
+
},
|
| 156 |
+
"docs/dev/versioning.md": {
|
| 157 |
+
"size": 2031
|
| 158 |
+
},
|
| 159 |
+
"docs/ecosystem.md": {
|
| 160 |
+
"size": 5064
|
| 161 |
+
},
|
| 162 |
+
"docs/extensions/autosummary_skip_deprecated.py": {
|
| 163 |
+
"size": 799
|
| 164 |
+
},
|
| 165 |
+
"docs/extensions/autosummary_skip_inherited.py": {
|
| 166 |
+
"size": 1935
|
| 167 |
+
},
|
| 168 |
+
"docs/extensions/canonical_tutorial.py": {
|
| 169 |
+
"size": 669
|
| 170 |
+
},
|
| 171 |
+
"docs/extensions/debug_docstrings.py": {
|
| 172 |
+
"size": 778
|
| 173 |
+
},
|
| 174 |
+
"docs/extensions/function_images.py": {
|
| 175 |
+
"size": 862
|
| 176 |
+
},
|
| 177 |
+
"docs/extensions/git_ref.py": {
|
| 178 |
+
"size": 1499
|
| 179 |
+
},
|
| 180 |
+
"docs/extensions/has_attr_test.py": {
|
| 181 |
+
"size": 696
|
| 182 |
+
},
|
| 183 |
+
"docs/extensions/param_police.py": {
|
| 184 |
+
"size": 1784
|
| 185 |
+
},
|
| 186 |
+
"docs/extensions/patch_myst_nb.py": {
|
| 187 |
+
"size": 797
|
| 188 |
+
},
|
| 189 |
+
"docs/external/exporting.md": {
|
| 190 |
+
"size": 222
|
| 191 |
+
},
|
| 192 |
+
"docs/external/index.md": {
|
| 193 |
+
"size": 619
|
| 194 |
+
},
|
| 195 |
+
"docs/external/plotting.md": {
|
| 196 |
+
"size": 234
|
| 197 |
+
},
|
| 198 |
+
"docs/external/preprocessing.md": {
|
| 199 |
+
"size": 637
|
| 200 |
+
},
|
| 201 |
+
"docs/external/tools.md": {
|
| 202 |
+
"size": 541
|
| 203 |
+
},
|
| 204 |
+
"docs/how-to/index.md": {
|
| 205 |
+
"size": 162
|
| 206 |
+
},
|
| 207 |
+
"docs/index.md": {
|
| 208 |
+
"size": 1929
|
| 209 |
+
},
|
| 210 |
+
"docs/installation.md": {
|
| 211 |
+
"size": 2808
|
| 212 |
+
},
|
| 213 |
+
"docs/news.md": {
|
| 214 |
+
"size": 4533
|
| 215 |
+
},
|
| 216 |
+
"docs/release-notes/0.1.0.md": {
|
| 217 |
+
"size": 339
|
| 218 |
+
},
|
| 219 |
+
"docs/release-notes/0.2.1.md": {
|
| 220 |
+
"size": 416
|
| 221 |
+
},
|
| 222 |
+
"docs/release-notes/0.2.9.md": {
|
| 223 |
+
"size": 458
|
| 224 |
+
},
|
| 225 |
+
"docs/release-notes/0.3.0.md": {
|
| 226 |
+
"size": 370
|
| 227 |
+
},
|
| 228 |
+
"docs/release-notes/0.3.2.md": {
|
| 229 |
+
"size": 161
|
| 230 |
+
},
|
| 231 |
+
"docs/release-notes/0.4.0.md": {
|
| 232 |
+
"size": 308
|
| 233 |
+
},
|
| 234 |
+
"docs/release-notes/0.4.2.md": {
|
| 235 |
+
"size": 143
|
| 236 |
+
},
|
| 237 |
+
"docs/release-notes/0.4.3.md": {
|
| 238 |
+
"size": 322
|
| 239 |
+
},
|
| 240 |
+
"docs/release-notes/0.4.4.md": {
|
| 241 |
+
"size": 359
|
| 242 |
+
},
|
| 243 |
+
"docs/release-notes/1.0.0.md": {
|
| 244 |
+
"size": 3841
|
| 245 |
+
},
|
| 246 |
+
"docs/release-notes/1.1.0.md": {
|
| 247 |
+
"size": 1178
|
| 248 |
+
},
|
| 249 |
+
"docs/release-notes/1.10.0.md": {
|
| 250 |
+
"size": 6772
|
| 251 |
+
},
|
| 252 |
+
"docs/release-notes/1.10.1.md": {
|
| 253 |
+
"size": 558
|
| 254 |
+
},
|
| 255 |
+
"docs/release-notes/1.10.2.md": {
|
| 256 |
+
"size": 1565
|
| 257 |
+
},
|
| 258 |
+
"docs/release-notes/1.10.3.md": {
|
| 259 |
+
"size": 1138
|
| 260 |
+
},
|
| 261 |
+
"docs/release-notes/1.10.4.md": {
|
| 262 |
+
"size": 1218
|
| 263 |
+
},
|
| 264 |
+
"docs/release-notes/1.11.0.md": {
|
| 265 |
+
"size": 3629
|
| 266 |
+
},
|
| 267 |
+
"docs/release-notes/1.11.1.md": {
|
| 268 |
+
"size": 472
|
| 269 |
+
},
|
| 270 |
+
"docs/release-notes/1.11.2.md": {
|
| 271 |
+
"size": 1289
|
| 272 |
+
},
|
| 273 |
+
"docs/release-notes/1.11.3.md": {
|
| 274 |
+
"size": 502
|
| 275 |
+
},
|
| 276 |
+
"docs/release-notes/1.11.4.md": {
|
| 277 |
+
"size": 668
|
| 278 |
+
},
|
| 279 |
+
"docs/release-notes/1.11.5.md": {
|
| 280 |
+
"size": 404
|
| 281 |
+
},
|
| 282 |
+
"docs/release-notes/1.12.0rc1.md": {
|
| 283 |
+
"size": 2892
|
| 284 |
+
},
|
| 285 |
+
"docs/release-notes/1.2.0.md": {
|
| 286 |
+
"size": 211
|
| 287 |
+
},
|
| 288 |
+
"docs/release-notes/1.2.1.md": {
|
| 289 |
+
"size": 306
|
| 290 |
+
},
|
| 291 |
+
"docs/release-notes/1.3.1.md": {
|
| 292 |
+
"size": 1341
|
| 293 |
+
},
|
| 294 |
+
"docs/release-notes/1.3.3.md": {
|
| 295 |
+
"size": 614
|
| 296 |
+
},
|
| 297 |
+
"docs/release-notes/1.3.4.md": {
|
| 298 |
+
"size": 458
|
| 299 |
+
},
|
| 300 |
+
"docs/release-notes/1.3.5.md": {
|
| 301 |
+
"size": 106
|
| 302 |
+
},
|
| 303 |
+
"docs/release-notes/1.3.6.md": {
|
| 304 |
+
"size": 868
|
| 305 |
+
},
|
| 306 |
+
"docs/release-notes/1.3.7.md": {
|
| 307 |
+
"size": 249
|
| 308 |
+
},
|
| 309 |
+
"docs/release-notes/1.3.8.md": {
|
| 310 |
+
"size": 238
|
| 311 |
+
},
|
| 312 |
+
"docs/release-notes/1.4.1.md": {
|
| 313 |
+
"size": 1455
|
| 314 |
+
},
|
| 315 |
+
"docs/release-notes/1.4.2.md": {
|
| 316 |
+
"size": 1063
|
| 317 |
+
},
|
| 318 |
+
"docs/release-notes/1.4.3.md": {
|
| 319 |
+
"size": 388
|
| 320 |
+
},
|
| 321 |
+
"docs/release-notes/1.4.4.md": {
|
| 322 |
+
"size": 447
|
| 323 |
+
},
|
| 324 |
+
"docs/release-notes/1.4.5.md": {
|
| 325 |
+
"size": 1506
|
| 326 |
+
},
|
| 327 |
+
"docs/release-notes/1.4.6.md": {
|
| 328 |
+
"size": 849
|
| 329 |
+
},
|
| 330 |
+
"docs/release-notes/1.5.0.md": {
|
| 331 |
+
"size": 3331
|
| 332 |
+
},
|
| 333 |
+
"docs/release-notes/1.5.1.md": {
|
| 334 |
+
"size": 301
|
| 335 |
+
},
|
| 336 |
+
"docs/release-notes/1.6.0.md": {
|
| 337 |
+
"size": 4125
|
| 338 |
+
},
|
| 339 |
+
"docs/release-notes/1.7.0.md": {
|
| 340 |
+
"size": 5167
|
| 341 |
+
},
|
| 342 |
+
"docs/release-notes/1.7.1.md": {
|
| 343 |
+
"size": 707
|
| 344 |
+
},
|
| 345 |
+
"docs/release-notes/1.7.2.md": {
|
| 346 |
+
"size": 918
|
| 347 |
+
},
|
| 348 |
+
"docs/release-notes/1.8.0.md": {
|
| 349 |
+
"size": 4247
|
| 350 |
+
},
|
| 351 |
+
"docs/release-notes/1.8.1.md": {
|
| 352 |
+
"size": 489
|
| 353 |
+
},
|
| 354 |
+
"docs/release-notes/1.8.2.md": {
|
| 355 |
+
"size": 691
|
| 356 |
+
},
|
| 357 |
+
"docs/release-notes/1.9.0.md": {
|
| 358 |
+
"size": 4122
|
| 359 |
+
},
|
| 360 |
+
"docs/release-notes/1.9.1.md": {
|
| 361 |
+
"size": 262
|
| 362 |
+
},
|
| 363 |
+
"docs/release-notes/1.9.2.md": {
|
| 364 |
+
"size": 567
|
| 365 |
+
},
|
| 366 |
+
"docs/release-notes/1.9.3.md": {
|
| 367 |
+
"size": 132
|
| 368 |
+
},
|
| 369 |
+
"docs/release-notes/1.9.4.md": {
|
| 370 |
+
"size": 679
|
| 371 |
+
},
|
| 372 |
+
"docs/release-notes/1.9.5.md": {
|
| 373 |
+
"size": 160
|
| 374 |
+
},
|
| 375 |
+
"docs/release-notes/1.9.6.md": {
|
| 376 |
+
"size": 733
|
| 377 |
+
},
|
| 378 |
+
"docs/release-notes/1.9.7.md": {
|
| 379 |
+
"size": 839
|
| 380 |
+
},
|
| 381 |
+
"docs/release-notes/1.9.8.md": {
|
| 382 |
+
"size": 151
|
| 383 |
+
},
|
| 384 |
+
"docs/release-notes/3675.misc.md": {
|
| 385 |
+
"size": 69
|
| 386 |
+
},
|
| 387 |
+
"docs/release-notes/index.md": {
|
| 388 |
+
"size": 60
|
| 389 |
+
},
|
| 390 |
+
"docs/tutorials/basics/index.md": {
|
| 391 |
+
"size": 98
|
| 392 |
+
},
|
| 393 |
+
"docs/tutorials/experimental/index.md": {
|
| 394 |
+
"size": 71
|
| 395 |
+
},
|
| 396 |
+
"docs/tutorials/index.md": {
|
| 397 |
+
"size": 1261
|
| 398 |
+
},
|
| 399 |
+
"docs/tutorials/plotting/index.md": {
|
| 400 |
+
"size": 57
|
| 401 |
+
},
|
| 402 |
+
"docs/tutorials/trajectories/index.md": {
|
| 403 |
+
"size": 60
|
| 404 |
+
},
|
| 405 |
+
"docs/usage-principles.md": {
|
| 406 |
+
"size": 2694
|
| 407 |
+
},
|
| 408 |
+
"hatch.toml": {
|
| 409 |
+
"size": 1590
|
| 410 |
+
},
|
| 411 |
+
"pyproject.toml": {
|
| 412 |
+
"size": 12438
|
| 413 |
+
},
|
| 414 |
+
"src/scanpy/__init__.py": {
|
| 415 |
+
"size": 2199
|
| 416 |
+
},
|
| 417 |
+
"src/scanpy/__main__.py": {
|
| 418 |
+
"size": 113
|
| 419 |
+
},
|
| 420 |
+
"src/scanpy/_compat.py": {
|
| 421 |
+
"size": 6421
|
| 422 |
+
},
|
| 423 |
+
"src/scanpy/_settings/__init__.py": {
|
| 424 |
+
"size": 16100
|
| 425 |
+
},
|
| 426 |
+
"src/scanpy/_settings/verbosity.py": {
|
| 427 |
+
"size": 2527
|
| 428 |
+
},
|
| 429 |
+
"src/scanpy/_singleton.py": {
|
| 430 |
+
"size": 2302
|
| 431 |
+
},
|
| 432 |
+
"src/scanpy/_types.py": {
|
| 433 |
+
"size": 171
|
| 434 |
+
},
|
| 435 |
+
"src/scanpy/_utils/__init__.py": {
|
| 436 |
+
"size": 32013
|
| 437 |
+
},
|
| 438 |
+
"src/scanpy/_utils/_doctests.py": {
|
| 439 |
+
"size": 778
|
| 440 |
+
},
|
| 441 |
+
"src/scanpy/_utils/random.py": {
|
| 442 |
+
"size": 6014
|
| 443 |
+
},
|
| 444 |
+
"src/scanpy/cli.py": {
|
| 445 |
+
"size": 4717
|
| 446 |
+
},
|
| 447 |
+
"src/scanpy/datasets/__init__.py": {
|
| 448 |
+
"size": 531
|
| 449 |
+
},
|
| 450 |
+
"src/scanpy/datasets/_datasets.py": {
|
| 451 |
+
"size": 19669
|
| 452 |
+
},
|
| 453 |
+
"src/scanpy/datasets/_ebi_expression_atlas.py": {
|
| 454 |
+
"size": 5738
|
| 455 |
+
},
|
| 456 |
+
"src/scanpy/datasets/_utils.py": {
|
| 457 |
+
"size": 441
|
| 458 |
+
},
|
| 459 |
+
"src/scanpy/datasets/krumsiek11.txt": {
|
| 460 |
+
"size": 59808
|
| 461 |
+
},
|
| 462 |
+
"src/scanpy/datasets/toggleswitch.txt": {
|
| 463 |
+
"size": 4200
|
| 464 |
+
},
|
| 465 |
+
"src/scanpy/experimental/__init__.py": {
|
| 466 |
+
"size": 114
|
| 467 |
+
},
|
| 468 |
+
"src/scanpy/experimental/_docs.py": {
|
| 469 |
+
"size": 2786
|
| 470 |
+
},
|
| 471 |
+
"src/scanpy/experimental/pp/__init__.py": {
|
| 472 |
+
"size": 504
|
| 473 |
+
},
|
| 474 |
+
"src/scanpy/experimental/pp/_highly_variable_genes.py": {
|
| 475 |
+
"size": 13922
|
| 476 |
+
},
|
| 477 |
+
"src/scanpy/experimental/pp/_normalization.py": {
|
| 478 |
+
"size": 8229
|
| 479 |
+
},
|
| 480 |
+
"src/scanpy/experimental/pp/_recipes.py": {
|
| 481 |
+
"size": 5084
|
| 482 |
+
},
|
| 483 |
+
"src/scanpy/external/__init__.py": {
|
| 484 |
+
"size": 294
|
| 485 |
+
},
|
| 486 |
+
"src/scanpy/external/exporting.py": {
|
| 487 |
+
"size": 21700
|
| 488 |
+
},
|
| 489 |
+
"src/scanpy/external/pl.py": {
|
| 490 |
+
"size": 9292
|
| 491 |
+
},
|
| 492 |
+
"src/scanpy/external/pp/__init__.py": {
|
| 493 |
+
"size": 726
|
| 494 |
+
},
|
| 495 |
+
"src/scanpy/external/pp/_bbknn.py": {
|
| 496 |
+
"size": 6633
|
| 497 |
+
},
|
| 498 |
+
"src/scanpy/external/pp/_dca.py": {
|
| 499 |
+
"size": 7236
|
| 500 |
+
},
|
| 501 |
+
"src/scanpy/external/pp/_harmony_integrate.py": {
|
| 502 |
+
"size": 3225
|
| 503 |
+
},
|
| 504 |
+
"src/scanpy/external/pp/_hashsolo.py": {
|
| 505 |
+
"size": 16680
|
| 506 |
+
},
|
| 507 |
+
"src/scanpy/external/pp/_magic.py": {
|
| 508 |
+
"size": 7388
|
| 509 |
+
},
|
| 510 |
+
"src/scanpy/external/pp/_mnn_correct.py": {
|
| 511 |
+
"size": 6017
|
| 512 |
+
},
|
| 513 |
+
"src/scanpy/external/pp/_scanorama_integrate.py": {
|
| 514 |
+
"size": 4664
|
| 515 |
+
},
|
| 516 |
+
"src/scanpy/external/tl/__init__.py": {
|
| 517 |
+
"size": 536
|
| 518 |
+
},
|
| 519 |
+
"src/scanpy/external/tl/_harmony_timeseries.py": {
|
| 520 |
+
"size": 6198
|
| 521 |
+
},
|
| 522 |
+
"src/scanpy/external/tl/_palantir.py": {
|
| 523 |
+
"size": 10273
|
| 524 |
+
},
|
| 525 |
+
"src/scanpy/external/tl/_phate.py": {
|
| 526 |
+
"size": 5805
|
| 527 |
+
},
|
| 528 |
+
"src/scanpy/external/tl/_phenograph.py": {
|
| 529 |
+
"size": 9358
|
| 530 |
+
},
|
| 531 |
+
"src/scanpy/external/tl/_pypairs.py": {
|
| 532 |
+
"size": 4994
|
| 533 |
+
},
|
| 534 |
+
"src/scanpy/external/tl/_sam.py": {
|
| 535 |
+
"size": 8371
|
| 536 |
+
},
|
| 537 |
+
"src/scanpy/external/tl/_trimap.py": {
|
| 538 |
+
"size": 4539
|
| 539 |
+
},
|
| 540 |
+
"src/scanpy/external/tl/_wishbone.py": {
|
| 541 |
+
"size": 5131
|
| 542 |
+
},
|
| 543 |
+
"src/scanpy/get/__init__.py": {
|
| 544 |
+
"size": 400
|
| 545 |
+
},
|
| 546 |
+
"src/scanpy/get/_aggregated.py": {
|
| 547 |
+
"size": 19035
|
| 548 |
+
},
|
| 549 |
+
"src/scanpy/get/get.py": {
|
| 550 |
+
"size": 16771
|
| 551 |
+
},
|
| 552 |
+
"src/scanpy/logging.py": {
|
| 553 |
+
"size": 7285
|
| 554 |
+
},
|
| 555 |
+
"src/scanpy/metrics/__init__.py": {
|
| 556 |
+
"size": 211
|
| 557 |
+
},
|
| 558 |
+
"src/scanpy/metrics/_common.py": {
|
| 559 |
+
"size": 4334
|
| 560 |
+
},
|
| 561 |
+
"src/scanpy/metrics/_gearys_c.py": {
|
| 562 |
+
"size": 7372
|
| 563 |
+
},
|
| 564 |
+
"src/scanpy/metrics/_metrics.py": {
|
| 565 |
+
"size": 2469
|
| 566 |
+
},
|
| 567 |
+
"src/scanpy/metrics/_morans_i.py": {
|
| 568 |
+
"size": 5200
|
| 569 |
+
},
|
| 570 |
+
"src/scanpy/neighbors/__init__.py": {
|
| 571 |
+
"size": 35389
|
| 572 |
+
},
|
| 573 |
+
"src/scanpy/neighbors/_backends/__init__.py": {
|
| 574 |
+
"size": 0
|
| 575 |
+
},
|
| 576 |
+
"src/scanpy/neighbors/_backends/_common.py": {
|
| 577 |
+
"size": 355
|
| 578 |
+
},
|
| 579 |
+
"src/scanpy/neighbors/_backends/rapids.py": {
|
| 580 |
+
"size": 2947
|
| 581 |
+
},
|
| 582 |
+
"src/scanpy/neighbors/_common.py": {
|
| 583 |
+
"size": 5329
|
| 584 |
+
},
|
| 585 |
+
"src/scanpy/neighbors/_connectivity.py": {
|
| 586 |
+
"size": 6210
|
| 587 |
+
},
|
| 588 |
+
"src/scanpy/neighbors/_doc.py": {
|
| 589 |
+
"size": 500
|
| 590 |
+
},
|
| 591 |
+
"src/scanpy/neighbors/_types.py": {
|
| 592 |
+
"size": 1564
|
| 593 |
+
},
|
| 594 |
+
"src/scanpy/plotting/__init__.py": {
|
| 595 |
+
"size": 2611
|
| 596 |
+
},
|
| 597 |
+
"src/scanpy/plotting/_anndata.py": {
|
| 598 |
+
"size": 91551
|
| 599 |
+
},
|
| 600 |
+
"src/scanpy/plotting/_baseplot_class.py": {
|
| 601 |
+
"size": 32931
|
| 602 |
+
},
|
| 603 |
+
"src/scanpy/plotting/_docs.py": {
|
| 604 |
+
"size": 13677
|
| 605 |
+
},
|
| 606 |
+
"src/scanpy/plotting/_dotplot.py": {
|
| 607 |
+
"size": 37695
|
| 608 |
+
},
|
| 609 |
+
"src/scanpy/plotting/_matrixplot.py": {
|
| 610 |
+
"size": 14321
|
| 611 |
+
},
|
| 612 |
+
"src/scanpy/plotting/_preprocessing.py": {
|
| 613 |
+
"size": 4593
|
| 614 |
+
},
|
| 615 |
+
"src/scanpy/plotting/_qc.py": {
|
| 616 |
+
"size": 3718
|
| 617 |
+
},
|
| 618 |
+
"src/scanpy/plotting/_rcmod.py": {
|
| 619 |
+
"size": 1993
|
| 620 |
+
},
|
| 621 |
+
"src/scanpy/plotting/_scrublet.py": {
|
| 622 |
+
"size": 5037
|
| 623 |
+
},
|
| 624 |
+
"src/scanpy/plotting/_stacked_violin.py": {
|
| 625 |
+
"size": 30701
|
| 626 |
+
},
|
| 627 |
+
"src/scanpy/plotting/_tools/__init__.py": {
|
| 628 |
+
"size": 52030
|
| 629 |
+
},
|
| 630 |
+
"src/scanpy/plotting/_tools/paga.py": {
|
| 631 |
+
"size": 50484
|
| 632 |
+
},
|
| 633 |
+
"src/scanpy/plotting/_tools/scatterplots.py": {
|
| 634 |
+
"size": 45640
|
| 635 |
+
},
|
| 636 |
+
"src/scanpy/plotting/_utils.py": {
|
| 637 |
+
"size": 35036
|
| 638 |
+
},
|
| 639 |
+
"src/scanpy/plotting/palettes.py": {
|
| 640 |
+
"size": 4795
|
| 641 |
+
},
|
| 642 |
+
"src/scanpy/preprocessing/__init__.py": {
|
| 643 |
+
"size": 1185
|
| 644 |
+
},
|
| 645 |
+
"src/scanpy/preprocessing/_combat.py": {
|
| 646 |
+
"size": 11894
|
| 647 |
+
},
|
| 648 |
+
"src/scanpy/preprocessing/_deprecated/__init__.py": {
|
| 649 |
+
"size": 2103
|
| 650 |
+
},
|
| 651 |
+
"src/scanpy/preprocessing/_deprecated/highly_variable_genes.py": {
|
| 652 |
+
"size": 10572
|
| 653 |
+
},
|
| 654 |
+
"src/scanpy/preprocessing/_deprecated/sampling.py": {
|
| 655 |
+
"size": 1683
|
| 656 |
+
},
|
| 657 |
+
"src/scanpy/preprocessing/_distributed.py": {
|
| 658 |
+
"size": 1107
|
| 659 |
+
},
|
| 660 |
+
"src/scanpy/preprocessing/_docs.py": {
|
| 661 |
+
"size": 2820
|
| 662 |
+
},
|
| 663 |
+
"src/scanpy/preprocessing/_highly_variable_genes.py": {
|
| 664 |
+
"size": 30225
|
| 665 |
+
},
|
| 666 |
+
"src/scanpy/preprocessing/_normalization.py": {
|
| 667 |
+
"size": 10737
|
| 668 |
+
},
|
| 669 |
+
"src/scanpy/preprocessing/_pca/__init__.py": {
|
| 670 |
+
"size": 21536
|
| 671 |
+
},
|
| 672 |
+
"src/scanpy/preprocessing/_pca/_compat.py": {
|
| 673 |
+
"size": 2234
|
| 674 |
+
},
|
| 675 |
+
"src/scanpy/preprocessing/_pca/_dask.py": {
|
| 676 |
+
"size": 7271
|
| 677 |
+
},
|
| 678 |
+
"src/scanpy/preprocessing/_qc.py": {
|
| 679 |
+
"size": 14296
|
| 680 |
+
},
|
| 681 |
+
"src/scanpy/preprocessing/_recipes.py": {
|
| 682 |
+
"size": 5920
|
| 683 |
+
},
|
| 684 |
+
"src/scanpy/preprocessing/_scale.py": {
|
| 685 |
+
"size": 9532
|
| 686 |
+
},
|
| 687 |
+
"src/scanpy/preprocessing/_scrublet/__init__.py": {
|
| 688 |
+
"size": 21888
|
| 689 |
+
},
|
| 690 |
+
"src/scanpy/preprocessing/_scrublet/core.py": {
|
| 691 |
+
"size": 17288
|
| 692 |
+
},
|
| 693 |
+
"src/scanpy/preprocessing/_scrublet/pipeline.py": {
|
| 694 |
+
"size": 2760
|
| 695 |
+
},
|
| 696 |
+
"src/scanpy/preprocessing/_scrublet/sparse_utils.py": {
|
| 697 |
+
"size": 1882
|
| 698 |
+
},
|
| 699 |
+
"src/scanpy/preprocessing/_simple.py": {
|
| 700 |
+
"size": 40205
|
| 701 |
+
},
|
| 702 |
+
"src/scanpy/preprocessing/_utils.py": {
|
| 703 |
+
"size": 774
|
| 704 |
+
},
|
| 705 |
+
"src/scanpy/queries/__init__.py": {
|
| 706 |
+
"size": 298
|
| 707 |
+
},
|
| 708 |
+
"src/scanpy/queries/_queries.py": {
|
| 709 |
+
"size": 9265
|
| 710 |
+
},
|
| 711 |
+
"src/scanpy/readwrite.py": {
|
| 712 |
+
"size": 40285
|
| 713 |
+
},
|
| 714 |
+
"src/scanpy/sim_models/__init__.py": {
|
| 715 |
+
"size": 67
|
| 716 |
+
},
|
| 717 |
+
"src/scanpy/sim_models/krumsiek11.txt": {
|
| 718 |
+
"size": 1886
|
| 719 |
+
},
|
| 720 |
+
"src/scanpy/sim_models/krumsiek11_params.txt": {
|
| 721 |
+
"size": 141
|
| 722 |
+
},
|
| 723 |
+
"src/scanpy/sim_models/toggleswitch.txt": {
|
| 724 |
+
"size": 438
|
| 725 |
+
},
|
| 726 |
+
"src/scanpy/sim_models/toggleswitch_params.txt": {
|
| 727 |
+
"size": 146
|
| 728 |
+
},
|
| 729 |
+
"src/scanpy/tools/__init__.py": {
|
| 730 |
+
"size": 1320
|
| 731 |
+
},
|
| 732 |
+
"src/scanpy/tools/_dendrogram.py": {
|
| 733 |
+
"size": 6697
|
| 734 |
+
},
|
| 735 |
+
"src/scanpy/tools/_diffmap.py": {
|
| 736 |
+
"size": 3290
|
| 737 |
+
},
|
| 738 |
+
"src/scanpy/tools/_dpt.py": {
|
| 739 |
+
"size": 50030
|
| 740 |
+
},
|
| 741 |
+
"src/scanpy/tools/_draw_graph.py": {
|
| 742 |
+
"size": 7854
|
| 743 |
+
},
|
| 744 |
+
"src/scanpy/tools/_embedding_density.py": {
|
| 745 |
+
"size": 6272
|
| 746 |
+
},
|
| 747 |
+
"src/scanpy/tools/_ingest.py": {
|
| 748 |
+
"size": 17487
|
| 749 |
+
},
|
| 750 |
+
"src/scanpy/tools/_leiden.py": {
|
| 751 |
+
"size": 8838
|
| 752 |
+
},
|
| 753 |
+
"src/scanpy/tools/_louvain.py": {
|
| 754 |
+
"size": 9717
|
| 755 |
+
},
|
| 756 |
+
"src/scanpy/tools/_marker_gene_overlap.py": {
|
| 757 |
+
"size": 10844
|
| 758 |
+
},
|
| 759 |
+
"src/scanpy/tools/_paga.py": {
|
| 760 |
+
"size": 24963
|
| 761 |
+
},
|
| 762 |
+
"src/scanpy/tools/_rank_genes_groups.py": {
|
| 763 |
+
"size": 33815
|
| 764 |
+
},
|
| 765 |
+
"src/scanpy/tools/_score_genes.py": {
|
| 766 |
+
"size": 11057
|
| 767 |
+
},
|
| 768 |
+
"src/scanpy/tools/_sim.py": {
|
| 769 |
+
"size": 46590
|
| 770 |
+
},
|
| 771 |
+
"src/scanpy/tools/_tsne.py": {
|
| 772 |
+
"size": 7292
|
| 773 |
+
},
|
| 774 |
+
"src/scanpy/tools/_umap.py": {
|
| 775 |
+
"size": 10337
|
| 776 |
+
},
|
| 777 |
+
"src/scanpy/tools/_utils.py": {
|
| 778 |
+
"size": 3760
|
| 779 |
+
},
|
| 780 |
+
"src/scanpy/tools/_utils_clustering.py": {
|
| 781 |
+
"size": 1573
|
| 782 |
+
},
|
| 783 |
+
"src/testing/scanpy/__init__.py": {
|
| 784 |
+
"size": 86
|
| 785 |
+
},
|
| 786 |
+
"src/testing/scanpy/_helpers/__init__.py": {
|
| 787 |
+
"size": 6859
|
| 788 |
+
},
|
| 789 |
+
"src/testing/scanpy/_helpers/data.py": {
|
| 790 |
+
"size": 1490
|
| 791 |
+
},
|
| 792 |
+
"src/testing/scanpy/_pytest/__init__.py": {
|
| 793 |
+
"size": 4032
|
| 794 |
+
},
|
| 795 |
+
"src/testing/scanpy/_pytest/fixtures/__init__.py": {
|
| 796 |
+
"size": 1857
|
| 797 |
+
},
|
| 798 |
+
"src/testing/scanpy/_pytest/fixtures/data.py": {
|
| 799 |
+
"size": 3416
|
| 800 |
+
},
|
| 801 |
+
"src/testing/scanpy/_pytest/marks.py": {
|
| 802 |
+
"size": 2205
|
| 803 |
+
},
|
| 804 |
+
"src/testing/scanpy/_pytest/params.py": {
|
| 805 |
+
"size": 4825
|
| 806 |
+
},
|
| 807 |
+
"tests/_data/visium_data/1.0.0/spatial/scalefactors_json.json": {
|
| 808 |
+
"size": 179
|
| 809 |
+
},
|
| 810 |
+
"tests/conftest.py": {
|
| 811 |
+
"size": 3996
|
| 812 |
+
},
|
| 813 |
+
"tests/external/test_harmony_integrate.py": {
|
| 814 |
+
"size": 755
|
| 815 |
+
},
|
| 816 |
+
"tests/external/test_harmony_timeseries.py": {
|
| 817 |
+
"size": 1167
|
| 818 |
+
},
|
| 819 |
+
"tests/external/test_hashsolo.py": {
|
| 820 |
+
"size": 1481
|
| 821 |
+
},
|
| 822 |
+
"tests/external/test_magic.py": {
|
| 823 |
+
"size": 1476
|
| 824 |
+
},
|
| 825 |
+
"tests/external/test_palantir.py": {
|
| 826 |
+
"size": 398
|
| 827 |
+
},
|
| 828 |
+
"tests/external/test_phenograph.py": {
|
| 829 |
+
"size": 618
|
| 830 |
+
},
|
| 831 |
+
"tests/external/test_sam.py": {
|
| 832 |
+
"size": 652
|
| 833 |
+
},
|
| 834 |
+
"tests/external/test_scanorama_integrate.py": {
|
| 835 |
+
"size": 761
|
| 836 |
+
},
|
| 837 |
+
"tests/external/test_wishbone.py": {
|
| 838 |
+
"size": 723
|
| 839 |
+
},
|
| 840 |
+
"tests/notebooks/test_pbmc3k.py": {
|
| 841 |
+
"size": 7151
|
| 842 |
+
},
|
| 843 |
+
"tests/test_aggregated.py": {
|
| 844 |
+
"size": 18279
|
| 845 |
+
},
|
| 846 |
+
"tests/test_backed.py": {
|
| 847 |
+
"size": 3071
|
| 848 |
+
},
|
| 849 |
+
"tests/test_binary.py": {
|
| 850 |
+
"size": 2169
|
| 851 |
+
},
|
| 852 |
+
"tests/test_clustering.py": {
|
| 853 |
+
"size": 6647
|
| 854 |
+
},
|
| 855 |
+
"tests/test_combat.py": {
|
| 856 |
+
"size": 2766
|
| 857 |
+
},
|
| 858 |
+
"tests/test_datasets.py": {
|
| 859 |
+
"size": 6926
|
| 860 |
+
},
|
| 861 |
+
"tests/test_dendrogram.py": {
|
| 862 |
+
"size": 3257
|
| 863 |
+
},
|
| 864 |
+
"tests/test_deprecations.py": {
|
| 865 |
+
"size": 802
|
| 866 |
+
},
|
| 867 |
+
"tests/test_embedding.py": {
|
| 868 |
+
"size": 3197
|
| 869 |
+
},
|
| 870 |
+
"tests/test_embedding_density.py": {
|
| 871 |
+
"size": 964
|
| 872 |
+
},
|
| 873 |
+
"tests/test_filter_rank_genes_groups.py": {
|
| 874 |
+
"size": 3004
|
| 875 |
+
},
|
| 876 |
+
"tests/test_get.py": {
|
| 877 |
+
"size": 17574
|
| 878 |
+
},
|
| 879 |
+
"tests/test_helpers.py": {
|
| 880 |
+
"size": 282
|
| 881 |
+
},
|
| 882 |
+
"tests/test_highly_variable_genes.py": {
|
| 883 |
+
"size": 25369
|
| 884 |
+
},
|
| 885 |
+
"tests/test_ingest.py": {
|
| 886 |
+
"size": 4441
|
| 887 |
+
},
|
| 888 |
+
"tests/test_logging.py": {
|
| 889 |
+
"size": 4788
|
| 890 |
+
},
|
| 891 |
+
"tests/test_marker_gene_overlap.py": {
|
| 892 |
+
"size": 1952
|
| 893 |
+
},
|
| 894 |
+
"tests/test_metrics.py": {
|
| 895 |
+
"size": 6814
|
| 896 |
+
},
|
| 897 |
+
"tests/test_neighbors.py": {
|
| 898 |
+
"size": 9357
|
| 899 |
+
},
|
| 900 |
+
"tests/test_neighbors_common.py": {
|
| 901 |
+
"size": 3238
|
| 902 |
+
},
|
| 903 |
+
"tests/test_neighbors_key_added.py": {
|
| 904 |
+
"size": 3236
|
| 905 |
+
},
|
| 906 |
+
"tests/test_normalization.py": {
|
| 907 |
+
"size": 12291
|
| 908 |
+
},
|
| 909 |
+
"tests/test_package_structure.py": {
|
| 910 |
+
"size": 5348
|
| 911 |
+
},
|
| 912 |
+
"tests/test_paga.py": {
|
| 913 |
+
"size": 3770
|
| 914 |
+
},
|
| 915 |
+
"tests/test_pca.py": {
|
| 916 |
+
"size": 22776
|
| 917 |
+
},
|
| 918 |
+
"tests/test_performance.py": {
|
| 919 |
+
"size": 1831
|
| 920 |
+
},
|
| 921 |
+
"tests/test_plotting.py": {
|
| 922 |
+
"size": 56098
|
| 923 |
+
},
|
| 924 |
+
"tests/test_plotting_embedded/conftest.py": {
|
| 925 |
+
"size": 1902
|
| 926 |
+
},
|
| 927 |
+
"tests/test_plotting_embedded/test_embeddings.py": {
|
| 928 |
+
"size": 7749
|
| 929 |
+
},
|
| 930 |
+
"tests/test_plotting_embedded/test_spatial.py": {
|
| 931 |
+
"size": 8188
|
| 932 |
+
},
|
| 933 |
+
"tests/test_plotting_utils.py": {
|
| 934 |
+
"size": 2019
|
| 935 |
+
},
|
| 936 |
+
"tests/test_preprocessing.py": {
|
| 937 |
+
"size": 24785
|
| 938 |
+
},
|
| 939 |
+
"tests/test_preprocessing_distributed.py": {
|
| 940 |
+
"size": 4796
|
| 941 |
+
},
|
| 942 |
+
"tests/test_qc_metrics.py": {
|
| 943 |
+
"size": 11546
|
| 944 |
+
},
|
| 945 |
+
"tests/test_queries.py": {
|
| 946 |
+
"size": 1367
|
| 947 |
+
},
|
| 948 |
+
"tests/test_rank_genes_groups.py": {
|
| 949 |
+
"size": 10691
|
| 950 |
+
},
|
| 951 |
+
"tests/test_rank_genes_groups_logreg.py": {
|
| 952 |
+
"size": 2564
|
| 953 |
+
},
|
| 954 |
+
"tests/test_read_10x.py": {
|
| 955 |
+
"size": 6962
|
| 956 |
+
},
|
| 957 |
+
"tests/test_readwrite.py": {
|
| 958 |
+
"size": 3996
|
| 959 |
+
},
|
| 960 |
+
"tests/test_scaling.py": {
|
| 961 |
+
"size": 4856
|
| 962 |
+
},
|
| 963 |
+
"tests/test_score_genes.py": {
|
| 964 |
+
"size": 9779
|
| 965 |
+
},
|
| 966 |
+
"tests/test_scrublet.py": {
|
| 967 |
+
"size": 7021
|
| 968 |
+
},
|
| 969 |
+
"tests/test_settings.py": {
|
| 970 |
+
"size": 486
|
| 971 |
+
},
|
| 972 |
+
"tests/test_sim.py": {
|
| 973 |
+
"size": 446
|
| 974 |
+
},
|
| 975 |
+
"tests/test_utils.py": {
|
| 976 |
+
"size": 8498
|
| 977 |
+
}
|
| 978 |
+
},
|
| 979 |
+
"processed_by": "zip_fallback",
|
| 980 |
+
"success": true
|
| 981 |
+
},
|
| 982 |
+
"structure": {
|
| 983 |
+
"packages": [
|
| 984 |
+
"source.benchmarks.benchmarks",
|
| 985 |
+
"source.src.scanpy"
|
| 986 |
+
]
|
| 987 |
+
},
|
| 988 |
+
"dependencies": {
|
| 989 |
+
"has_environment_yml": false,
|
| 990 |
+
"has_requirements_txt": false,
|
| 991 |
+
"pyproject": true,
|
| 992 |
+
"setup_cfg": false,
|
| 993 |
+
"setup_py": false
|
| 994 |
+
},
|
| 995 |
+
"entry_points": {
|
| 996 |
+
"imports": [],
|
| 997 |
+
"cli": [],
|
| 998 |
+
"modules": []
|
| 999 |
+
},
|
| 1000 |
+
"llm_analysis": {
|
| 1001 |
+
"core_modules": [
|
| 1002 |
+
{
|
| 1003 |
+
"package": "source.src.scanpy",
|
| 1004 |
+
"module": "scanpy",
|
| 1005 |
+
"functions": [
|
| 1006 |
+
"read",
|
| 1007 |
+
"write",
|
| 1008 |
+
"pp",
|
| 1009 |
+
"tl",
|
| 1010 |
+
"pl",
|
| 1011 |
+
"neighbors",
|
| 1012 |
+
"logging"
|
| 1013 |
+
],
|
| 1014 |
+
"classes": [
|
| 1015 |
+
"AnnData",
|
| 1016 |
+
"Settings"
|
| 1017 |
+
],
|
| 1018 |
+
"description": "Scanpy is a scalable toolkit for analyzing single-cell gene expression data."
|
| 1019 |
+
},
|
| 1020 |
+
{
|
| 1021 |
+
"package": "source.src.scanpy.preprocessing",
|
| 1022 |
+
"module": "preprocessing",
|
| 1023 |
+
"functions": [
|
| 1024 |
+
"normalize_total",
|
| 1025 |
+
"log1p",
|
| 1026 |
+
"highly_variable_genes"
|
| 1027 |
+
],
|
| 1028 |
+
"classes": [],
|
| 1029 |
+
"description": "Preprocessing functions for single-cell data."
|
| 1030 |
+
},
|
| 1031 |
+
{
|
| 1032 |
+
"package": "source.src.scanpy.tools",
|
| 1033 |
+
"module": "tools",
|
| 1034 |
+
"functions": [
|
| 1035 |
+
"rank_genes_groups",
|
| 1036 |
+
"paga",
|
| 1037 |
+
"umap"
|
| 1038 |
+
],
|
| 1039 |
+
"classes": [],
|
| 1040 |
+
"description": "Tools for single-cell data analysis."
|
| 1041 |
+
},
|
| 1042 |
+
{
|
| 1043 |
+
"package": "source.src.scanpy.plotting",
|
| 1044 |
+
"module": "plotting",
|
| 1045 |
+
"functions": [
|
| 1046 |
+
"scatter",
|
| 1047 |
+
"violin",
|
| 1048 |
+
"dotplot"
|
| 1049 |
+
],
|
| 1050 |
+
"classes": [],
|
| 1051 |
+
"description": "Plotting functions for visualizing single-cell data."
|
| 1052 |
+
}
|
| 1053 |
+
],
|
| 1054 |
+
"cli_commands": [
|
| 1055 |
+
{
|
| 1056 |
+
"name": "scanpy-cli",
|
| 1057 |
+
"module": "source.src.scanpy.cli",
|
| 1058 |
+
"description": "Command-line interface for Scanpy."
|
| 1059 |
+
}
|
| 1060 |
+
],
|
| 1061 |
+
"import_strategy": {
|
| 1062 |
+
"primary": "import",
|
| 1063 |
+
"fallback": "cli",
|
| 1064 |
+
"confidence": 0.9
|
| 1065 |
+
},
|
| 1066 |
+
"dependencies": {
|
| 1067 |
+
"required": [
|
| 1068 |
+
"numpy",
|
| 1069 |
+
"scipy",
|
| 1070 |
+
"pandas",
|
| 1071 |
+
"matplotlib",
|
| 1072 |
+
"h5py"
|
| 1073 |
+
],
|
| 1074 |
+
"optional": [
|
| 1075 |
+
"umap-learn",
|
| 1076 |
+
"leidenalg"
|
| 1077 |
+
]
|
| 1078 |
+
},
|
| 1079 |
+
"risk_assessment": {
|
| 1080 |
+
"import_feasibility": 0.9,
|
| 1081 |
+
"intrusiveness_risk": "low",
|
| 1082 |
+
"complexity": "medium"
|
| 1083 |
+
}
|
| 1084 |
+
},
|
| 1085 |
+
"deepwiki_analysis": {
|
| 1086 |
+
"repo_url": "https://github.com/scverse/scanpy",
|
| 1087 |
+
"repo_name": "scanpy",
|
| 1088 |
+
"content": null,
|
| 1089 |
+
"model": "gpt-4o",
|
| 1090 |
+
"source": "selenium",
|
| 1091 |
+
"success": true
|
| 1092 |
+
},
|
| 1093 |
+
"deepwiki_options": {
|
| 1094 |
+
"enabled": true,
|
| 1095 |
+
"model": "gpt-4o"
|
| 1096 |
+
},
|
| 1097 |
+
"risk": {
|
| 1098 |
+
"import_feasibility": 0.9,
|
| 1099 |
+
"intrusiveness_risk": "low",
|
| 1100 |
+
"complexity": "medium"
|
| 1101 |
+
}
|
| 1102 |
+
}
|
scanpy/mcp_output/env_info.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"environment": {
|
| 3 |
+
"type": "conda",
|
| 4 |
+
"name": "scanpy_862512_env",
|
| 5 |
+
"files": {},
|
| 6 |
+
"python": "3.10",
|
| 7 |
+
"exec_prefix": []
|
| 8 |
+
},
|
| 9 |
+
"original_tests": {
|
| 10 |
+
"passed": true,
|
| 11 |
+
"report_path": null
|
| 12 |
+
},
|
| 13 |
+
"timestamp": 1762862649.6675427,
|
| 14 |
+
"conda_available": true
|
| 15 |
+
}
|
scanpy/mcp_output/mcp_logs/llm_statistics.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"total_calls": 6,
|
| 3 |
+
"failed_calls": 0,
|
| 4 |
+
"retry_count": 0,
|
| 5 |
+
"total_prompt_tokens": 39538,
|
| 6 |
+
"total_completion_tokens": 6086,
|
| 7 |
+
"total_tokens": 45624,
|
| 8 |
+
"average_prompt_tokens": 6589.666666666667,
|
| 9 |
+
"average_completion_tokens": 1014.3333333333334,
|
| 10 |
+
"average_tokens": 7604.0
|
| 11 |
+
}
|
scanpy/mcp_output/mcp_logs/run_log.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"timestamp": 1762863047.3048873,
|
| 3 |
+
"node": "RunNode",
|
| 4 |
+
"test_result": {
|
| 5 |
+
"passed": false,
|
| 6 |
+
"report_path": null,
|
| 7 |
+
"stdout": "",
|
| 8 |
+
"stderr": "ERROR conda.cli.main_run:execute(41): `conda run python mcp_output/start_mcp.py` failed. (See above for error)\nTraceback (most recent call last):\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/start_mcp.py\", line 17, in <module>\n from mcp_service import create_app\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin/mcp_service.py\", line 8, in <module>\n from src.scanpy.cli import main as scanpy_cli_main\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/source/src/scanpy/__init__.py\", line 9, in <module>\n from packaging.version import Version\nModuleNotFoundError: No module named 'packaging'\n\n"
|
| 9 |
+
},
|
| 10 |
+
"run_result": {
|
| 11 |
+
"success": false,
|
| 12 |
+
"test_passed": false,
|
| 13 |
+
"exit_code": 1,
|
| 14 |
+
"stdout": "",
|
| 15 |
+
"stderr": "ERROR conda.cli.main_run:execute(41): `conda run python mcp_output/start_mcp.py` failed. (See above for error)\nTraceback (most recent call last):\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/start_mcp.py\", line 17, in <module>\n from mcp_service import create_app\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin/mcp_service.py\", line 8, in <module>\n from src.scanpy.cli import main as scanpy_cli_main\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/source/src/scanpy/__init__.py\", line 9, in <module>\n from packaging.version import Version\nModuleNotFoundError: No module named 'packaging'\n\n",
|
| 16 |
+
"timestamp": 1762863047.3048391,
|
| 17 |
+
"error_type": "ImportError",
|
| 18 |
+
"error": "Module import failed: ERROR conda.cli.main_run:execute(41): `conda run python mcp_output/start_mcp.py` failed. (See above for error)\nTraceback (most recent call last):\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/start_mcp.py\", line 17, in <module>\n from mcp_service import create_app\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin/mcp_service.py\", line 8, in <module>\n from src.scanpy.cli import main as scanpy_cli_main\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/source/src/scanpy/__init__.py\", line 9, in <module>\n from packaging.version import Version\nModuleNotFoundError: No module named 'packaging'\n\n",
|
| 19 |
+
"details": {
|
| 20 |
+
"command": "/home/wshiah/code/miniconda3/bin/conda run -n scanpy_862512_env --cwd /export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy python mcp_output/start_mcp.py",
|
| 21 |
+
"working_directory": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy",
|
| 22 |
+
"environment_type": "conda"
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
"environment": {
|
| 26 |
+
"type": "conda",
|
| 27 |
+
"name": "scanpy_862512_env",
|
| 28 |
+
"files": {},
|
| 29 |
+
"python": "3.10",
|
| 30 |
+
"exec_prefix": []
|
| 31 |
+
},
|
| 32 |
+
"plugin_info": {
|
| 33 |
+
"files": {
|
| 34 |
+
"mcp_output/start_mcp.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/start_mcp.py",
|
| 35 |
+
"mcp_output/mcp_plugin/__init__.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin/__init__.py",
|
| 36 |
+
"mcp_output/mcp_plugin/mcp_service.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin/mcp_service.py",
|
| 37 |
+
"mcp_output/mcp_plugin/adapter.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin/adapter.py",
|
| 38 |
+
"mcp_output/mcp_plugin/main.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin/main.py",
|
| 39 |
+
"mcp_output/requirements.txt": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/requirements.txt",
|
| 40 |
+
"mcp_output/README_MCP.md": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/README_MCP.md",
|
| 41 |
+
"mcp_output/tests_mcp/test_mcp_basic.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/tests_mcp/test_mcp_basic.py"
|
| 42 |
+
},
|
| 43 |
+
"adapter_mode": "import",
|
| 44 |
+
"endpoints": [
|
| 45 |
+
"read",
|
| 46 |
+
"write",
|
| 47 |
+
"pp",
|
| 48 |
+
"tl",
|
| 49 |
+
"pl",
|
| 50 |
+
"neighbors",
|
| 51 |
+
"logging",
|
| 52 |
+
"anndata",
|
| 53 |
+
"settings",
|
| 54 |
+
"normalize_total",
|
| 55 |
+
"log1p",
|
| 56 |
+
"highly_variable_genes",
|
| 57 |
+
"rank_genes_groups",
|
| 58 |
+
"paga",
|
| 59 |
+
"umap",
|
| 60 |
+
"scatter",
|
| 61 |
+
"violin",
|
| 62 |
+
"dotplot"
|
| 63 |
+
],
|
| 64 |
+
"mcp_dir": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/mcp_plugin",
|
| 65 |
+
"tests_dir": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/tests_mcp",
|
| 66 |
+
"main_entry": "start_mcp.py",
|
| 67 |
+
"readme_path": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/scanpy/mcp_output/README_MCP.md",
|
| 68 |
+
"requirements": [
|
| 69 |
+
"fastmcp>=0.1.0",
|
| 70 |
+
"pydantic>=2.0.0"
|
| 71 |
+
]
|
| 72 |
+
},
|
| 73 |
+
"fastmcp_installed": false
|
| 74 |
+
}
|
scanpy/mcp_output/mcp_plugin/__init__.py
ADDED
|
File without changes
|
scanpy/mcp_output/mcp_plugin/__pycache__/adapter.cpython-310.pyc
ADDED
|
Binary file (4.67 kB). View file
|
|
|
scanpy/mcp_output/mcp_plugin/__pycache__/mcp_service.cpython-310.pyc
ADDED
|
Binary file (7.98 kB). View file
|
|
|
scanpy/mcp_output/mcp_plugin/adapter.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
# Path settings
|
| 5 |
+
source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
|
| 6 |
+
sys.path.insert(0, source_path)
|
| 7 |
+
|
| 8 |
+
# Import statements
|
| 9 |
+
try:
|
| 10 |
+
from src.scanpy.cli import scanpy_cli
|
| 11 |
+
from src.scanpy import __init__ as scanpy_init
|
| 12 |
+
from src.scanpy import _compat as scanpy_compat
|
| 13 |
+
from src.scanpy import _settings as scanpy_settings
|
| 14 |
+
from src.scanpy import _singleton as scanpy_singleton
|
| 15 |
+
from src.scanpy import _types as scanpy_types
|
| 16 |
+
from src.scanpy import _utils as scanpy_utils
|
| 17 |
+
from src.scanpy import datasets as scanpy_datasets
|
| 18 |
+
from src.scanpy import experimental as scanpy_experimental
|
| 19 |
+
from src.scanpy import external as scanpy_external
|
| 20 |
+
from src.scanpy import get as scanpy_get
|
| 21 |
+
from src.scanpy import logging as scanpy_logging
|
| 22 |
+
from src.scanpy import metrics as scanpy_metrics
|
| 23 |
+
from src.scanpy import neighbors as scanpy_neighbors
|
| 24 |
+
from src.scanpy import plotting as scanpy_plotting
|
| 25 |
+
from src.scanpy import preprocessing as scanpy_preprocessing
|
| 26 |
+
from src.scanpy import queries as scanpy_queries
|
| 27 |
+
from src.scanpy import readwrite as scanpy_readwrite
|
| 28 |
+
from src.scanpy import sim_models as scanpy_sim_models
|
| 29 |
+
from src.scanpy import tools as scanpy_tools
|
| 30 |
+
except ImportError as e:
|
| 31 |
+
print(f"Import failed: {e}. Please ensure all dependencies are installed and the source path is correct.")
|
| 32 |
+
|
| 33 |
+
# Adapter class definition
|
| 34 |
+
class Adapter:
|
| 35 |
+
"""
|
| 36 |
+
Adapter class for the MCP plugin, utilizing the Scanpy library.
|
| 37 |
+
Provides methods to interact with various functionalities of Scanpy.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
def __init__(self):
|
| 41 |
+
self.mode = "import"
|
| 42 |
+
|
| 43 |
+
# ---------------------- CLI Methods ----------------------
|
| 44 |
+
|
| 45 |
+
def run_scanpy_cli(self, args):
|
| 46 |
+
"""
|
| 47 |
+
Execute the Scanpy command-line interface.
|
| 48 |
+
|
| 49 |
+
Parameters:
|
| 50 |
+
args (list): List of command-line arguments.
|
| 51 |
+
|
| 52 |
+
Returns:
|
| 53 |
+
dict: Status of the execution.
|
| 54 |
+
"""
|
| 55 |
+
try:
|
| 56 |
+
scanpy_cli(args)
|
| 57 |
+
return {"status": "success"}
|
| 58 |
+
except Exception as e:
|
| 59 |
+
return {"status": "error", "message": str(e)}
|
| 60 |
+
|
| 61 |
+
# ---------------------- Initialization Methods ----------------------
|
| 62 |
+
|
| 63 |
+
def initialize_scanpy(self):
|
| 64 |
+
"""
|
| 65 |
+
Initialize the Scanpy module.
|
| 66 |
+
|
| 67 |
+
Returns:
|
| 68 |
+
dict: Status of the initialization.
|
| 69 |
+
"""
|
| 70 |
+
try:
|
| 71 |
+
scanpy_init()
|
| 72 |
+
return {"status": "success"}
|
| 73 |
+
except Exception as e:
|
| 74 |
+
return {"status": "error", "message": str(e)}
|
| 75 |
+
|
| 76 |
+
# ---------------------- Utility Methods ----------------------
|
| 77 |
+
|
| 78 |
+
def use_scanpy_utils(self, function_name, *args, **kwargs):
|
| 79 |
+
"""
|
| 80 |
+
Call a utility function from Scanpy.
|
| 81 |
+
|
| 82 |
+
Parameters:
|
| 83 |
+
function_name (str): Name of the utility function.
|
| 84 |
+
args: Positional arguments for the function.
|
| 85 |
+
kwargs: Keyword arguments for the function.
|
| 86 |
+
|
| 87 |
+
Returns:
|
| 88 |
+
dict: Result of the function call.
|
| 89 |
+
"""
|
| 90 |
+
try:
|
| 91 |
+
func = getattr(scanpy_utils, function_name)
|
| 92 |
+
result = func(*args, **kwargs)
|
| 93 |
+
return {"status": "success", "result": result}
|
| 94 |
+
except AttributeError:
|
| 95 |
+
return {"status": "error", "message": f"Function {function_name} not found in scanpy_utils."}
|
| 96 |
+
except Exception as e:
|
| 97 |
+
return {"status": "error", "message": str(e)}
|
| 98 |
+
|
| 99 |
+
# ---------------------- Dataset Methods ----------------------
|
| 100 |
+
|
| 101 |
+
def load_dataset(self, dataset_name):
|
| 102 |
+
"""
|
| 103 |
+
Load a dataset using Scanpy.
|
| 104 |
+
|
| 105 |
+
Parameters:
|
| 106 |
+
dataset_name (str): Name of the dataset to load.
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
dict: Loaded dataset and status.
|
| 110 |
+
"""
|
| 111 |
+
try:
|
| 112 |
+
dataset = getattr(scanpy_datasets, dataset_name)()
|
| 113 |
+
return {"status": "success", "dataset": dataset}
|
| 114 |
+
except AttributeError:
|
| 115 |
+
return {"status": "error", "message": f"Dataset {dataset_name} not found in scanpy_datasets."}
|
| 116 |
+
except Exception as e:
|
| 117 |
+
return {"status": "error", "message": str(e)}
|
| 118 |
+
|
| 119 |
+
# ---------------------- Error Handling ----------------------
|
| 120 |
+
|
| 121 |
+
def handle_import_failure(self):
|
| 122 |
+
"""
|
| 123 |
+
Handle import failures gracefully.
|
| 124 |
+
|
| 125 |
+
Returns:
|
| 126 |
+
dict: Status of the import handling.
|
| 127 |
+
"""
|
| 128 |
+
try:
|
| 129 |
+
# Attempt to re-import or provide guidance
|
| 130 |
+
# This is a placeholder for actual handling logic
|
| 131 |
+
return {"status": "success", "message": "Import handling executed."}
|
| 132 |
+
except Exception as e:
|
| 133 |
+
return {"status": "error", "message": str(e)}
|
| 134 |
+
|
| 135 |
+
# Example usage
|
| 136 |
+
if __name__ == "__main__":
|
| 137 |
+
adapter = Adapter()
|
| 138 |
+
print(adapter.run_scanpy_cli(["--help"]))
|
| 139 |
+
print(adapter.initialize_scanpy())
|
| 140 |
+
print(adapter.use_scanpy_utils("some_function", arg1="value"))
|
| 141 |
+
print(adapter.load_dataset("example_dataset"))
|
| 142 |
+
print(adapter.handle_import_failure())
|
scanpy/mcp_output/mcp_plugin/main.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MCP Service Auto-Wrapper - Auto-generated
|
| 3 |
+
"""
|
| 4 |
+
from mcp_service import create_app
|
| 5 |
+
|
| 6 |
+
def main():
|
| 7 |
+
"""Main entry point"""
|
| 8 |
+
app = create_app()
|
| 9 |
+
return app
|
| 10 |
+
|
| 11 |
+
if __name__ == "__main__":
|
| 12 |
+
app = main()
|
| 13 |
+
app.run()
|
scanpy/mcp_output/mcp_plugin/mcp_service.py
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import json
|
| 4 |
+
from typing import Any, Optional, Dict, List, Union
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
|
| 7 |
+
source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
|
| 8 |
+
sys.path.insert(0, source_path)
|
| 9 |
+
|
| 10 |
+
from fastmcp import FastMCP
|
| 11 |
+
import anndata as ad
|
| 12 |
+
import numpy as np
|
| 13 |
+
import pandas as pd
|
| 14 |
+
|
| 15 |
+
# Main Scanpy modules
|
| 16 |
+
import scanpy as sc
|
| 17 |
+
from scanpy import (
|
| 18 |
+
datasets,
|
| 19 |
+
read_h5ad,
|
| 20 |
+
read_csv,
|
| 21 |
+
read_loom,
|
| 22 |
+
read_mtx,
|
| 23 |
+
read_10x_h5,
|
| 24 |
+
read_10x_mtx,
|
| 25 |
+
read_visium,
|
| 26 |
+
write,
|
| 27 |
+
AnnData,
|
| 28 |
+
concat,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
# Preprocessing functions
|
| 32 |
+
from scanpy import preprocessing as pp
|
| 33 |
+
from scanpy.preprocessing import (
|
| 34 |
+
calculate_qc_metrics,
|
| 35 |
+
log1p,
|
| 36 |
+
normalize_total,
|
| 37 |
+
normalize_per_cell,
|
| 38 |
+
scale,
|
| 39 |
+
pca,
|
| 40 |
+
highly_variable_genes,
|
| 41 |
+
combat,
|
| 42 |
+
filter_cells,
|
| 43 |
+
filter_genes,
|
| 44 |
+
regress_out,
|
| 45 |
+
sample,
|
| 46 |
+
sqrt,
|
| 47 |
+
downsample_counts,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
# Tool functions
|
| 51 |
+
from scanpy import tools as tl
|
| 52 |
+
from scanpy.tools import (
|
| 53 |
+
leiden,
|
| 54 |
+
louvain,
|
| 55 |
+
tsne,
|
| 56 |
+
umap,
|
| 57 |
+
pca as tl_pca,
|
| 58 |
+
diffmap,
|
| 59 |
+
dpt,
|
| 60 |
+
draw_graph,
|
| 61 |
+
embedding_density,
|
| 62 |
+
ingest,
|
| 63 |
+
marker_gene_overlap,
|
| 64 |
+
rank_genes_groups,
|
| 65 |
+
score_genes,
|
| 66 |
+
score_genes_cell_cycle,
|
| 67 |
+
dendrogram,
|
| 68 |
+
paga,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Plotting functions
|
| 72 |
+
from scanpy import plotting as pl
|
| 73 |
+
from scanpy.plotting import (
|
| 74 |
+
scatter,
|
| 75 |
+
embedding,
|
| 76 |
+
umap as pl_umap,
|
| 77 |
+
tsne as pl_tsne,
|
| 78 |
+
pca as pl_pca,
|
| 79 |
+
violin,
|
| 80 |
+
dotplot,
|
| 81 |
+
heatmap,
|
| 82 |
+
stacked_violin,
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
# Metrics
|
| 86 |
+
from scanpy import metrics
|
| 87 |
+
from scanpy.metrics import confusion_matrix
|
| 88 |
+
|
| 89 |
+
# Settings and utilities
|
| 90 |
+
from scanpy import settings
|
| 91 |
+
|
| 92 |
+
mcp = FastMCP("scanpy_service")
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# ============ 数据加载和I/O工具 ============
|
| 96 |
+
|
| 97 |
+
@mcp.tool(name="load_pbmc3k", description="Load PBMC 3K dataset")
|
| 98 |
+
def load_pbmc3k() -> dict:
|
| 99 |
+
"""加载PBMC 3000细胞单细胞RNA-seq数据集"""
|
| 100 |
+
try:
|
| 101 |
+
adata = datasets.pbmc3k()
|
| 102 |
+
return {
|
| 103 |
+
"success": True,
|
| 104 |
+
"result": {
|
| 105 |
+
"shape": str(adata.shape),
|
| 106 |
+
"obs_names": list(adata.obs.columns),
|
| 107 |
+
"var_names": list(adata.var.columns),
|
| 108 |
+
"layers": list(adata.layers.keys()) if hasattr(adata, 'layers') else []
|
| 109 |
+
},
|
| 110 |
+
"error": None
|
| 111 |
+
}
|
| 112 |
+
except Exception as e:
|
| 113 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 114 |
+
|
| 115 |
+
@mcp.tool(name="load_pbmc3k_processed", description="Load processed PBMC 3K dataset")
|
| 116 |
+
def load_pbmc3k_processed() -> dict:
|
| 117 |
+
"""加载预处理的PBMC 3000细胞数据集"""
|
| 118 |
+
try:
|
| 119 |
+
adata = datasets.pbmc3k_processed()
|
| 120 |
+
return {
|
| 121 |
+
"success": True,
|
| 122 |
+
"result": {
|
| 123 |
+
"shape": str(adata.shape),
|
| 124 |
+
"obs": dict(adata.obs.iloc[0]) if len(adata.obs) > 0 else {}
|
| 125 |
+
},
|
| 126 |
+
"error": None
|
| 127 |
+
}
|
| 128 |
+
except Exception as e:
|
| 129 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 130 |
+
|
| 131 |
+
@mcp.tool(name="load_10x_data", description="Load 10x Genomics HDF5 data")
|
| 132 |
+
def load_10x_data(file_path: str) -> dict:
|
| 133 |
+
"""加载10x Genomics HDF5格式数据"""
|
| 134 |
+
try:
|
| 135 |
+
adata = read_10x_h5(file_path)
|
| 136 |
+
return {
|
| 137 |
+
"success": True,
|
| 138 |
+
"result": {
|
| 139 |
+
"shape": str(adata.shape),
|
| 140 |
+
"obs_count": len(adata.obs),
|
| 141 |
+
"var_count": len(adata.var)
|
| 142 |
+
},
|
| 143 |
+
"error": None
|
| 144 |
+
}
|
| 145 |
+
except Exception as e:
|
| 146 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 147 |
+
|
| 148 |
+
@mcp.tool(name="read_h5ad_file", description="Read H5AD format file")
|
| 149 |
+
def read_h5ad_file(file_path: str) -> dict:
|
| 150 |
+
"""读取H5AD格式文件"""
|
| 151 |
+
try:
|
| 152 |
+
adata = read_h5ad(file_path)
|
| 153 |
+
return {
|
| 154 |
+
"success": True,
|
| 155 |
+
"result": {
|
| 156 |
+
"shape": str(adata.shape),
|
| 157 |
+
"obs": list(adata.obs.columns),
|
| 158 |
+
"var": list(adata.var.columns)
|
| 159 |
+
},
|
| 160 |
+
"error": None
|
| 161 |
+
}
|
| 162 |
+
except Exception as e:
|
| 163 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 164 |
+
|
| 165 |
+
# ============ 预处理工具 ============
|
| 166 |
+
|
| 167 |
+
@mcp.tool(name="calculate_qc", description="Calculate quality control metrics")
|
| 168 |
+
def calculate_qc(n_genes: int = 2500, n_counts: int = 5000) -> dict:
|
| 169 |
+
"""计算质量控制指标"""
|
| 170 |
+
try:
|
| 171 |
+
adata = datasets.pbmc3k()
|
| 172 |
+
calculate_qc_metrics(adata, qc_vars=["mt"])
|
| 173 |
+
return {
|
| 174 |
+
"success": True,
|
| 175 |
+
"result": {
|
| 176 |
+
"qc_completed": True,
|
| 177 |
+
"adata_shape": str(adata.shape)
|
| 178 |
+
},
|
| 179 |
+
"error": None
|
| 180 |
+
}
|
| 181 |
+
except Exception as e:
|
| 182 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 183 |
+
|
| 184 |
+
@mcp.tool(name="normalize_data", description="Normalize expression data")
|
| 185 |
+
def normalize_data(target_sum: float = 1e4) -> dict:
|
| 186 |
+
"""归一化表达式数据"""
|
| 187 |
+
try:
|
| 188 |
+
adata = datasets.pbmc3k()
|
| 189 |
+
pp.normalize_total(adata, target_sum=target_sum)
|
| 190 |
+
pp.log1p(adata)
|
| 191 |
+
return {
|
| 192 |
+
"success": True,
|
| 193 |
+
"result": {
|
| 194 |
+
"normalization": "completed",
|
| 195 |
+
"shape": str(adata.shape)
|
| 196 |
+
},
|
| 197 |
+
"error": None
|
| 198 |
+
}
|
| 199 |
+
except Exception as e:
|
| 200 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 201 |
+
|
| 202 |
+
@mcp.tool(name="find_hvg", description="Find highly variable genes")
|
| 203 |
+
def find_hvg(n_top_genes: int = 2000) -> dict:
|
| 204 |
+
"""寻找高变基因"""
|
| 205 |
+
try:
|
| 206 |
+
adata = datasets.pbmc3k()
|
| 207 |
+
pp.normalize_total(adata)
|
| 208 |
+
pp.log1p(adata)
|
| 209 |
+
pp.highly_variable_genes(adata, n_top_genes=n_top_genes)
|
| 210 |
+
hvg_count = np.sum(adata.var['highly_variable'])
|
| 211 |
+
return {
|
| 212 |
+
"success": True,
|
| 213 |
+
"result": {
|
| 214 |
+
"hvg_count": int(hvg_count),
|
| 215 |
+
"n_top_genes": n_top_genes
|
| 216 |
+
},
|
| 217 |
+
"error": None
|
| 218 |
+
}
|
| 219 |
+
except Exception as e:
|
| 220 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 221 |
+
|
| 222 |
+
@mcp.tool(name="scale_data", description="Scale expression data")
|
| 223 |
+
def scale_data(max_value: float = 10) -> dict:
|
| 224 |
+
"""缩放表达式数据"""
|
| 225 |
+
try:
|
| 226 |
+
adata = datasets.pbmc3k()
|
| 227 |
+
pp.normalize_total(adata)
|
| 228 |
+
pp.log1p(adata)
|
| 229 |
+
pp.scale(adata, max_value=max_value)
|
| 230 |
+
return {
|
| 231 |
+
"success": True,
|
| 232 |
+
"result": {
|
| 233 |
+
"scaling": "completed",
|
| 234 |
+
"max_value": max_value
|
| 235 |
+
},
|
| 236 |
+
"error": None
|
| 237 |
+
}
|
| 238 |
+
except Exception as e:
|
| 239 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 240 |
+
|
| 241 |
+
@mcp.tool(name="pca_reduction", description="Perform PCA dimensionality reduction")
|
| 242 |
+
def pca_reduction(n_comps: int = 50) -> dict:
|
| 243 |
+
"""进行PCA降维"""
|
| 244 |
+
try:
|
| 245 |
+
adata = datasets.pbmc3k()
|
| 246 |
+
pp.normalize_total(adata)
|
| 247 |
+
pp.log1p(adata)
|
| 248 |
+
pp.scale(adata)
|
| 249 |
+
pp.pca(adata, n_comps=n_comps)
|
| 250 |
+
return {
|
| 251 |
+
"success": True,
|
| 252 |
+
"result": {
|
| 253 |
+
"n_comps": n_comps,
|
| 254 |
+
"X_pca_shape": str(adata.obsm['X_pca'].shape) if 'X_pca' in adata.obsm else "Not computed"
|
| 255 |
+
},
|
| 256 |
+
"error": None
|
| 257 |
+
}
|
| 258 |
+
except Exception as e:
|
| 259 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 260 |
+
|
| 261 |
+
# ============ 聚类和分析工具 ============
|
| 262 |
+
|
| 263 |
+
@mcp.tool(name="neighbors_graph", description="Compute k-nearest neighbors graph")
|
| 264 |
+
def neighbors_graph(n_neighbors: int = 15, use_rep: str = "X_pca") -> dict:
|
| 265 |
+
"""计算k近邻图"""
|
| 266 |
+
try:
|
| 267 |
+
adata = datasets.pbmc3k()
|
| 268 |
+
pp.normalize_total(adata)
|
| 269 |
+
pp.log1p(adata)
|
| 270 |
+
pp.scale(adata)
|
| 271 |
+
pp.pca(adata)
|
| 272 |
+
sc.pp.neighbors(adata, n_neighbors=n_neighbors, use_rep=use_rep)
|
| 273 |
+
return {
|
| 274 |
+
"success": True,
|
| 275 |
+
"result": {
|
| 276 |
+
"neighbors_computed": True,
|
| 277 |
+
"n_neighbors": n_neighbors
|
| 278 |
+
},
|
| 279 |
+
"error": None
|
| 280 |
+
}
|
| 281 |
+
except Exception as e:
|
| 282 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 283 |
+
|
| 284 |
+
@mcp.tool(name="leiden_clustering", description="Perform Leiden clustering")
|
| 285 |
+
def leiden_clustering(resolution: float = 1.0) -> dict:
|
| 286 |
+
"""执行Leiden聚类"""
|
| 287 |
+
try:
|
| 288 |
+
adata = datasets.pbmc3k()
|
| 289 |
+
pp.normalize_total(adata)
|
| 290 |
+
pp.log1p(adata)
|
| 291 |
+
pp.scale(adata)
|
| 292 |
+
pp.pca(adata)
|
| 293 |
+
sc.pp.neighbors(adata)
|
| 294 |
+
tl.leiden(adata, resolution=resolution, key_added='leiden')
|
| 295 |
+
n_clusters = len(adata.obs['leiden'].unique())
|
| 296 |
+
return {
|
| 297 |
+
"success": True,
|
| 298 |
+
"result": {
|
| 299 |
+
"n_clusters": n_clusters,
|
| 300 |
+
"resolution": resolution,
|
| 301 |
+
"clusters": list(adata.obs['leiden'].unique())
|
| 302 |
+
},
|
| 303 |
+
"error": None
|
| 304 |
+
}
|
| 305 |
+
except Exception as e:
|
| 306 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 307 |
+
|
| 308 |
+
@mcp.tool(name="louvain_clustering", description="Perform Louvain clustering")
|
| 309 |
+
def louvain_clustering(resolution: float = 1.0) -> dict:
|
| 310 |
+
"""执行Louvain聚类"""
|
| 311 |
+
try:
|
| 312 |
+
adata = datasets.pbmc3k()
|
| 313 |
+
pp.normalize_total(adata)
|
| 314 |
+
pp.log1p(adata)
|
| 315 |
+
pp.scale(adata)
|
| 316 |
+
pp.pca(adata)
|
| 317 |
+
sc.pp.neighbors(adata)
|
| 318 |
+
tl.louvain(adata, resolution=resolution, key_added='louvain')
|
| 319 |
+
n_clusters = len(adata.obs['louvain'].unique())
|
| 320 |
+
return {
|
| 321 |
+
"success": True,
|
| 322 |
+
"result": {
|
| 323 |
+
"n_clusters": n_clusters,
|
| 324 |
+
"resolution": resolution
|
| 325 |
+
},
|
| 326 |
+
"error": None
|
| 327 |
+
}
|
| 328 |
+
except Exception as e:
|
| 329 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 330 |
+
|
| 331 |
+
# ============ 降维可视化工具 ============
|
| 332 |
+
|
| 333 |
+
@mcp.tool(name="umap_embedding", description="Compute UMAP embedding")
|
| 334 |
+
def umap_embedding(min_dist: float = 0.1, spread: float = 1.0) -> dict:
|
| 335 |
+
"""计算UMAP嵌入"""
|
| 336 |
+
try:
|
| 337 |
+
adata = datasets.pbmc3k()
|
| 338 |
+
pp.normalize_total(adata)
|
| 339 |
+
pp.log1p(adata)
|
| 340 |
+
pp.scale(adata)
|
| 341 |
+
pp.pca(adata)
|
| 342 |
+
sc.pp.neighbors(adata)
|
| 343 |
+
tl.umap(adata, min_dist=min_dist, spread=spread)
|
| 344 |
+
return {
|
| 345 |
+
"success": True,
|
| 346 |
+
"result": {
|
| 347 |
+
"umap_computed": True,
|
| 348 |
+
"X_umap_shape": str(adata.obsm['X_umap'].shape) if 'X_umap' in adata.obsm else "Not computed"
|
| 349 |
+
},
|
| 350 |
+
"error": None
|
| 351 |
+
}
|
| 352 |
+
except Exception as e:
|
| 353 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 354 |
+
|
| 355 |
+
@mcp.tool(name="tsne_embedding", description="Compute t-SNE embedding")
|
| 356 |
+
def tsne_embedding(perplexity: float = 30.0) -> dict:
|
| 357 |
+
"""计算t-SNE嵌入"""
|
| 358 |
+
try:
|
| 359 |
+
adata = datasets.pbmc3k()
|
| 360 |
+
pp.normalize_total(adata)
|
| 361 |
+
pp.log1p(adata)
|
| 362 |
+
pp.scale(adata)
|
| 363 |
+
pp.pca(adata)
|
| 364 |
+
tl.tsne(adata, perplexity=perplexity)
|
| 365 |
+
return {
|
| 366 |
+
"success": True,
|
| 367 |
+
"result": {
|
| 368 |
+
"tsne_computed": True,
|
| 369 |
+
"perplexity": perplexity
|
| 370 |
+
},
|
| 371 |
+
"error": None
|
| 372 |
+
}
|
| 373 |
+
except Exception as e:
|
| 374 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 375 |
+
|
| 376 |
+
@mcp.tool(name="diffmap_embedding", description="Compute diffusion map embedding")
|
| 377 |
+
def diffmap_embedding(n_comps: int = 15) -> dict:
|
| 378 |
+
"""计算扩散图嵌入"""
|
| 379 |
+
try:
|
| 380 |
+
adata = datasets.pbmc3k()
|
| 381 |
+
pp.normalize_total(adata)
|
| 382 |
+
pp.log1p(adata)
|
| 383 |
+
pp.scale(adata)
|
| 384 |
+
pp.pca(adata)
|
| 385 |
+
sc.pp.neighbors(adata)
|
| 386 |
+
tl.diffmap(adata, n_comps=n_comps)
|
| 387 |
+
return {
|
| 388 |
+
"success": True,
|
| 389 |
+
"result": {
|
| 390 |
+
"diffmap_computed": True,
|
| 391 |
+
"n_comps": n_comps
|
| 392 |
+
},
|
| 393 |
+
"error": None
|
| 394 |
+
}
|
| 395 |
+
except Exception as e:
|
| 396 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 397 |
+
|
| 398 |
+
# ============ 差异表达分析工具 ============
|
| 399 |
+
|
| 400 |
+
@mcp.tool(name="rank_genes", description="Rank genes for differential expression")
|
| 401 |
+
def rank_genes(groupby: str = "leiden", method: str = "wilcoxon") -> dict:
|
| 402 |
+
"""对基因进行排序以进行差异表达分析"""
|
| 403 |
+
try:
|
| 404 |
+
adata = datasets.pbmc3k()
|
| 405 |
+
pp.normalize_total(adata)
|
| 406 |
+
pp.log1p(adata)
|
| 407 |
+
pp.scale(adata)
|
| 408 |
+
pp.pca(adata)
|
| 409 |
+
sc.pp.neighbors(adata)
|
| 410 |
+
tl.leiden(adata, key_added='leiden')
|
| 411 |
+
tl.rank_genes_groups(adata, groupby=groupby, method=method)
|
| 412 |
+
return {
|
| 413 |
+
"success": True,
|
| 414 |
+
"result": {
|
| 415 |
+
"ranking_completed": True,
|
| 416 |
+
"groupby": groupby,
|
| 417 |
+
"method": method
|
| 418 |
+
},
|
| 419 |
+
"error": None
|
| 420 |
+
}
|
| 421 |
+
except Exception as e:
|
| 422 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 423 |
+
|
| 424 |
+
@mcp.tool(name="score_genes_tool", description="Score gene sets for cells")
|
| 425 |
+
def score_genes_tool(gene_list: List[str]) -> dict:
|
| 426 |
+
"""为细胞评分基因集"""
|
| 427 |
+
try:
|
| 428 |
+
adata = datasets.pbmc3k()
|
| 429 |
+
pp.normalize_total(adata)
|
| 430 |
+
pp.log1p(adata)
|
| 431 |
+
# 使用可用的基因进行评分
|
| 432 |
+
available_genes = [g for g in gene_list if g in adata.var_names][:10]
|
| 433 |
+
if available_genes:
|
| 434 |
+
tl.score_genes(adata, available_genes, score_name='gene_score')
|
| 435 |
+
return {
|
| 436 |
+
"success": True,
|
| 437 |
+
"result": {
|
| 438 |
+
"scoring_completed": True,
|
| 439 |
+
"genes_scored": len(available_genes)
|
| 440 |
+
},
|
| 441 |
+
"error": None
|
| 442 |
+
}
|
| 443 |
+
except Exception as e:
|
| 444 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 445 |
+
|
| 446 |
+
# ============ 轨迹推断工具 ============
|
| 447 |
+
|
| 448 |
+
@mcp.tool(name="dpt_analysis", description="Perform diffusion pseudotime analysis")
|
| 449 |
+
def dpt_analysis() -> dict:
|
| 450 |
+
"""执行扩散伪时间分析"""
|
| 451 |
+
try:
|
| 452 |
+
adata = datasets.pbmc3k()
|
| 453 |
+
pp.normalize_total(adata)
|
| 454 |
+
pp.log1p(adata)
|
| 455 |
+
pp.scale(adata)
|
| 456 |
+
pp.pca(adata)
|
| 457 |
+
sc.pp.neighbors(adata)
|
| 458 |
+
tl.diffmap(adata)
|
| 459 |
+
tl.dpt(adata)
|
| 460 |
+
return {
|
| 461 |
+
"success": True,
|
| 462 |
+
"result": {
|
| 463 |
+
"dpt_computed": True,
|
| 464 |
+
"dpt_groups": list(adata.obs.columns)
|
| 465 |
+
},
|
| 466 |
+
"error": None
|
| 467 |
+
}
|
| 468 |
+
except Exception as e:
|
| 469 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 470 |
+
|
| 471 |
+
# ============ 配体-受体分析工具 ============
|
| 472 |
+
|
| 473 |
+
@mcp.tool(name="paga_analysis", description="Perform PAGA analysis")
|
| 474 |
+
def paga_analysis() -> dict:
|
| 475 |
+
"""执行PAGA (Partition-based graph abstraction) 分析"""
|
| 476 |
+
try:
|
| 477 |
+
adata = datasets.pbmc3k()
|
| 478 |
+
pp.normalize_total(adata)
|
| 479 |
+
pp.log1p(adata)
|
| 480 |
+
pp.scale(adata)
|
| 481 |
+
pp.pca(adata)
|
| 482 |
+
sc.pp.neighbors(adata)
|
| 483 |
+
tl.leiden(adata, key_added='leiden')
|
| 484 |
+
tl.paga(adata, groups='leiden')
|
| 485 |
+
return {
|
| 486 |
+
"success": True,
|
| 487 |
+
"result": {
|
| 488 |
+
"paga_computed": True,
|
| 489 |
+
"paga_neighbors": "Computed"
|
| 490 |
+
},
|
| 491 |
+
"error": None
|
| 492 |
+
}
|
| 493 |
+
except Exception as e:
|
| 494 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 495 |
+
|
| 496 |
+
# ============ 批次效应校正工具 ============
|
| 497 |
+
|
| 498 |
+
@mcp.tool(name="combat_correction", description="Perform ComBat batch correction")
|
| 499 |
+
def combat_correction() -> dict:
|
| 500 |
+
"""执行ComBat批次效应校正"""
|
| 501 |
+
try:
|
| 502 |
+
adata = datasets.pbmc3k()
|
| 503 |
+
pp.normalize_total(adata)
|
| 504 |
+
pp.log1p(adata)
|
| 505 |
+
# 为演示添加虚拟批次信息
|
| 506 |
+
adata.obs['batch'] = np.random.choice(['batch1', 'batch2'], size=adata.n_obs)
|
| 507 |
+
pp.combat(adata, key='batch')
|
| 508 |
+
return {
|
| 509 |
+
"success": True,
|
| 510 |
+
"result": {
|
| 511 |
+
"combat_completed": True,
|
| 512 |
+
"shape": str(adata.shape)
|
| 513 |
+
},
|
| 514 |
+
"error": None
|
| 515 |
+
}
|
| 516 |
+
except Exception as e:
|
| 517 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 518 |
+
|
| 519 |
+
# ============ 绘图工具 ============
|
| 520 |
+
|
| 521 |
+
@mcp.tool(name="plot_umap", description="Plot UMAP embedding")
|
| 522 |
+
def plot_umap(color_by: str = "leiden") -> dict:
|
| 523 |
+
"""绘制UMAP嵌入"""
|
| 524 |
+
try:
|
| 525 |
+
adata = datasets.pbmc3k()
|
| 526 |
+
pp.normalize_total(adata)
|
| 527 |
+
pp.log1p(adata)
|
| 528 |
+
pp.scale(adata)
|
| 529 |
+
pp.pca(adata)
|
| 530 |
+
sc.pp.neighbors(adata)
|
| 531 |
+
tl.leiden(adata, key_added='leiden')
|
| 532 |
+
tl.umap(adata)
|
| 533 |
+
# 返回绘图信息而不实际渲染
|
| 534 |
+
return {
|
| 535 |
+
"success": True,
|
| 536 |
+
"result": {
|
| 537 |
+
"plot_type": "umap",
|
| 538 |
+
"color_by": color_by,
|
| 539 |
+
"cells": adata.n_obs,
|
| 540 |
+
"genes": adata.n_vars
|
| 541 |
+
},
|
| 542 |
+
"error": None
|
| 543 |
+
}
|
| 544 |
+
except Exception as e:
|
| 545 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 546 |
+
|
| 547 |
+
@mcp.tool(name="plot_pca", description="Plot PCA variance")
|
| 548 |
+
def plot_pca() -> dict:
|
| 549 |
+
"""绘制PCA方差"""
|
| 550 |
+
try:
|
| 551 |
+
adata = datasets.pbmc3k()
|
| 552 |
+
pp.normalize_total(adata)
|
| 553 |
+
pp.log1p(adata)
|
| 554 |
+
pp.scale(adata)
|
| 555 |
+
pp.pca(adata)
|
| 556 |
+
return {
|
| 557 |
+
"success": True,
|
| 558 |
+
"result": {
|
| 559 |
+
"plot_type": "pca_variance_ratio",
|
| 560 |
+
"components": adata.obsm['X_pca'].shape[1] if 'X_pca' in adata.obsm else 0
|
| 561 |
+
},
|
| 562 |
+
"error": None
|
| 563 |
+
}
|
| 564 |
+
except Exception as e:
|
| 565 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 566 |
+
|
| 567 |
+
# ============ 统计工具 ============
|
| 568 |
+
|
| 569 |
+
@mcp.tool(name="get_dataset_info", description="Get comprehensive dataset information")
|
| 570 |
+
def get_dataset_info() -> dict:
|
| 571 |
+
"""获取数据集详细信息"""
|
| 572 |
+
try:
|
| 573 |
+
adata = datasets.pbmc3k()
|
| 574 |
+
return {
|
| 575 |
+
"success": True,
|
| 576 |
+
"result": {
|
| 577 |
+
"n_obs": adata.n_obs,
|
| 578 |
+
"n_vars": adata.n_vars,
|
| 579 |
+
"obs_names": list(adata.obs.columns),
|
| 580 |
+
"var_names": list(adata.var.columns)[:10],
|
| 581 |
+
"layers": list(adata.layers.keys()) if hasattr(adata, 'layers') else []
|
| 582 |
+
},
|
| 583 |
+
"error": None
|
| 584 |
+
}
|
| 585 |
+
except Exception as e:
|
| 586 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 587 |
+
|
| 588 |
+
@mcp.tool(name="get_settings", description="Get Scanpy settings")
|
| 589 |
+
def get_settings() -> dict:
|
| 590 |
+
"""获取Scanpy设置"""
|
| 591 |
+
try:
|
| 592 |
+
return {
|
| 593 |
+
"success": True,
|
| 594 |
+
"result": {
|
| 595 |
+
"figure_format": settings.figure_format,
|
| 596 |
+
"figure_dpi": settings.figure_dpi,
|
| 597 |
+
"file_format_data": settings.file_format_data,
|
| 598 |
+
"file_format_figs": settings.file_format_figs
|
| 599 |
+
},
|
| 600 |
+
"error": None
|
| 601 |
+
}
|
| 602 |
+
except Exception as e:
|
| 603 |
+
return {"success": False, "result": None, "error": str(e)}
|
| 604 |
+
|
| 605 |
+
def create_app() -> FastMCP:
|
| 606 |
+
return mcp
|
scanpy/mcp_output/requirements.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastmcp>=0.1.0
|
| 2 |
+
pydantic>=2.0.0
|
| 3 |
+
scanpy>=1.9.0
|
| 4 |
+
anndata>=0.10.0
|
| 5 |
+
numpy
|
| 6 |
+
scipy
|
| 7 |
+
pandas
|
| 8 |
+
matplotlib
|
| 9 |
+
h5py
|
| 10 |
+
scikit-learn
|
| 11 |
+
scikit-misc
|
| 12 |
+
leidenalg>=0.8.0
|
| 13 |
+
umap-learn>=0.5.0
|
| 14 |
+
|
| 15 |
+
# Optional Dependencies for advanced features
|
| 16 |
+
scikit-image
|
| 17 |
+
networkx
|
scanpy/mcp_output/start_mcp.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
"""
|
| 3 |
+
MCP Service Startup Entry
|
| 4 |
+
"""
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
project_root = os.path.dirname(os.path.abspath(__file__))
|
| 9 |
+
mcp_plugin_dir = os.path.join(project_root, "mcp_plugin")
|
| 10 |
+
if mcp_plugin_dir not in sys.path:
|
| 11 |
+
sys.path.insert(0, mcp_plugin_dir)
|
| 12 |
+
|
| 13 |
+
# Set path to source directory
|
| 14 |
+
source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
|
| 15 |
+
sys.path.insert(0, source_path)
|
| 16 |
+
|
| 17 |
+
from mcp_service import create_app
|
| 18 |
+
|
| 19 |
+
def main():
|
| 20 |
+
"""Start FastMCP service"""
|
| 21 |
+
app = create_app()
|
| 22 |
+
# Use environment variable to configure port, default 8000
|
| 23 |
+
port = int(os.environ.get("MCP_PORT", "8000"))
|
| 24 |
+
|
| 25 |
+
# Choose transport mode based on environment variable
|
| 26 |
+
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
| 27 |
+
if transport == "http":
|
| 28 |
+
app.run(transport="http", host="0.0.0.0", port=port)
|
| 29 |
+
else:
|
| 30 |
+
# Default to STDIO mode
|
| 31 |
+
app.run()
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
main()
|
scanpy/mcp_output/tests_mcp/test_mcp_basic.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MCP Service Basic Test
|
| 3 |
+
"""
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
+
mcp_plugin_dir = os.path.join(project_root, "mcp_plugin")
|
| 9 |
+
if mcp_plugin_dir not in sys.path:
|
| 10 |
+
sys.path.insert(0, mcp_plugin_dir)
|
| 11 |
+
|
| 12 |
+
source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
|
| 13 |
+
sys.path.insert(0, source_path)
|
| 14 |
+
|
| 15 |
+
def test_import_mcp_service():
|
| 16 |
+
"""Test if MCP service can be imported normally"""
|
| 17 |
+
try:
|
| 18 |
+
from mcp_service import create_app
|
| 19 |
+
app = create_app()
|
| 20 |
+
assert app is not None
|
| 21 |
+
print("MCP service imported successfully")
|
| 22 |
+
return True
|
| 23 |
+
except Exception as e:
|
| 24 |
+
print("MCP service import failed: " + str(e))
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
def test_adapter_init():
|
| 28 |
+
"""Test if adapter can be initialized normally"""
|
| 29 |
+
try:
|
| 30 |
+
from adapter import Adapter
|
| 31 |
+
adapter = Adapter()
|
| 32 |
+
assert adapter is not None
|
| 33 |
+
print("Adapter initialized successfully")
|
| 34 |
+
return True
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print("Adapter initialization failed: " + str(e))
|
| 37 |
+
return False
|
| 38 |
+
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
print("Running MCP service basic test...")
|
| 41 |
+
test1 = test_import_mcp_service()
|
| 42 |
+
test2 = test_adapter_init()
|
| 43 |
+
|
| 44 |
+
if test1 and test2:
|
| 45 |
+
print("All basic tests passed")
|
| 46 |
+
sys.exit(0)
|
| 47 |
+
else:
|
| 48 |
+
print("Some tests failed")
|
| 49 |
+
sys.exit(1)
|
scanpy/mcp_output/tests_smoke/test_smoke.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import importlib, sys
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
# Add current directory to Python path
|
| 5 |
+
sys.path.insert(0, os.getcwd())
|
| 6 |
+
|
| 7 |
+
source_dir = os.path.join(os.getcwd(), "source")
|
| 8 |
+
if os.path.exists(source_dir):
|
| 9 |
+
sys.path.insert(0, source_dir)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
importlib.import_module("benchmarks.benchmarks")
|
| 14 |
+
print("OK - Successfully imported benchmarks.benchmarks")
|
| 15 |
+
except ImportError as e:
|
| 16 |
+
print(f"Failed to import benchmarks.benchmarks: {e}")
|
| 17 |
+
fallback_packages = []
|
| 18 |
+
|
| 19 |
+
fallback_packages = ['benchmarks', 'benchmarks.benchmarks']
|
| 20 |
+
|
| 21 |
+
for pkg in fallback_packages:
|
| 22 |
+
try:
|
| 23 |
+
importlib.import_module(pkg)
|
| 24 |
+
print(f"OK - Successfully imported {pkg}")
|
| 25 |
+
break
|
| 26 |
+
except ImportError:
|
| 27 |
+
continue
|
| 28 |
+
else:
|
| 29 |
+
print("All import attempts failed")
|
scanpy/source/.codecov.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Based on pydata/xarray
|
| 2 |
+
codecov:
|
| 3 |
+
require_ci_to_pass: false
|
| 4 |
+
|
| 5 |
+
coverage:
|
| 6 |
+
status:
|
| 7 |
+
project:
|
| 8 |
+
default:
|
| 9 |
+
# Require 75% coverage
|
| 10 |
+
target: 75
|
| 11 |
+
changes: false
|
| 12 |
+
|
| 13 |
+
comment:
|
| 14 |
+
layout: "diff, flags, files"
|
| 15 |
+
behavior: once
|
| 16 |
+
require_base: false
|
scanpy/source/.editorconfig
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
root = true
|
| 2 |
+
|
| 3 |
+
[*]
|
| 4 |
+
charset = utf-8
|
| 5 |
+
end_of_line = lf
|
| 6 |
+
insert_final_newline = true
|
| 7 |
+
trim_trailing_whitespace = true
|
| 8 |
+
max_line_length = 88
|
| 9 |
+
indent_size = 4
|
| 10 |
+
indent_style = space
|
| 11 |
+
|
| 12 |
+
[*.{yml,yaml}]
|
| 13 |
+
indent_size = 2
|
scanpy/source/.github/ISSUE_TEMPLATE/bug-report.yml
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Bug report
|
| 2 |
+
description: Scanpy doesn’t do what it should? Please help us fix it!
|
| 3 |
+
#title: ...
|
| 4 |
+
type: Bug
|
| 5 |
+
labels:
|
| 6 |
+
- Triage 🩺
|
| 7 |
+
#assignees: []
|
| 8 |
+
body:
|
| 9 |
+
- type: checkboxes
|
| 10 |
+
id: terms
|
| 11 |
+
attributes:
|
| 12 |
+
label: Please make sure these conditions are met
|
| 13 |
+
# description: ...
|
| 14 |
+
options:
|
| 15 |
+
- label: I have checked that this issue has not already been reported.
|
| 16 |
+
required: true
|
| 17 |
+
- label: I have confirmed this bug exists on the latest version of scanpy.
|
| 18 |
+
required: true
|
| 19 |
+
- label: (optional) I have confirmed this bug exists on the main branch of scanpy.
|
| 20 |
+
required: false
|
| 21 |
+
- type: markdown
|
| 22 |
+
attributes:
|
| 23 |
+
value: |
|
| 24 |
+
**Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing how to provide the necessary information for us to reproduce your bug.
|
| 25 |
+
- type: textarea
|
| 26 |
+
id: description
|
| 27 |
+
attributes:
|
| 28 |
+
label: What happened?
|
| 29 |
+
description: Describe what you tried to do, and what happened instead.
|
| 30 |
+
validations:
|
| 31 |
+
required: true
|
| 32 |
+
- type: textarea
|
| 33 |
+
id: code
|
| 34 |
+
attributes:
|
| 35 |
+
label: Minimal code sample
|
| 36 |
+
description: |
|
| 37 |
+
Reproducible code sample. Must list dependencies in [inline script metadata][]. When put in a file named `issue.py` using [uv run][] i.e., `uv run issue.py`, should show the issue.
|
| 38 |
+
|
| 39 |
+
[uv run]: https://docs.astral.sh/uv/guides/scripts/#running-a-script-with-dependencies
|
| 40 |
+
[inline script metadata]: https://packaging.python.org/en/latest/specifications/inline-script-metadata/#example
|
| 41 |
+
render: python
|
| 42 |
+
value: |
|
| 43 |
+
```python
|
| 44 |
+
# /// script
|
| 45 |
+
# requires-python = ">=3.12"
|
| 46 |
+
# dependencies = [
|
| 47 |
+
# "scanpy@git+https://github.com/scverse/scanpy.git@main",
|
| 48 |
+
# ]
|
| 49 |
+
# ///
|
| 50 |
+
#
|
| 51 |
+
# This script automatically imports the development branch of scanpy to check for issues
|
| 52 |
+
|
| 53 |
+
import scanpy as sc
|
| 54 |
+
# your reproducer code
|
| 55 |
+
```
|
| 56 |
+
validations:
|
| 57 |
+
required: true
|
| 58 |
+
- type: textarea
|
| 59 |
+
id: traceback
|
| 60 |
+
attributes:
|
| 61 |
+
label: Error output
|
| 62 |
+
description: Produced by the code above. Leave empty if the issue is unexpected behavior instead of an error.
|
| 63 |
+
render: pytb
|
| 64 |
+
validations:
|
| 65 |
+
required: false
|
| 66 |
+
- type: textarea
|
| 67 |
+
id: versions
|
| 68 |
+
attributes:
|
| 69 |
+
label: Versions
|
| 70 |
+
description: Paste the output of `import scanpy; scanpy.logging.print_versions()` between the lines of backticks (\`\`\`)
|
| 71 |
+
value: |
|
| 72 |
+
<details>
|
| 73 |
+
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
</details>
|
| 79 |
+
validations:
|
| 80 |
+
required: true
|
scanpy/source/.github/ISSUE_TEMPLATE/config.yml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
blank_issues_enabled: false
|
| 2 |
+
contact_links:
|
| 3 |
+
- name: Scanpy Community Forum
|
| 4 |
+
url: https://discourse.scverse.org/
|
| 5 |
+
about: If you have questions about “How to do X”, please ask them here.
|
scanpy/source/.github/ISSUE_TEMPLATE/enhancement-request.yml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Enhancement request
|
| 2 |
+
description: Anything you’d like to see in scanpy?
|
| 3 |
+
#title: ...
|
| 4 |
+
type: Enhancement
|
| 5 |
+
labels:
|
| 6 |
+
- Triage 🩺
|
| 7 |
+
#assignees: []
|
| 8 |
+
body:
|
| 9 |
+
- type: dropdown
|
| 10 |
+
id: kind
|
| 11 |
+
attributes:
|
| 12 |
+
label: What kind of feature would you like to request?
|
| 13 |
+
options:
|
| 14 |
+
- 'Additional function parameters / changed functionality / changed defaults?'
|
| 15 |
+
- 'New analysis tool: A simple analysis tool you have been using and are missing in `sc.tools`?'
|
| 16 |
+
- 'New plotting function: A kind of plot you would like to seein `sc.pl`?'
|
| 17 |
+
- 'Improved documentation or error message?'
|
| 18 |
+
- 'Other?'
|
| 19 |
+
validations:
|
| 20 |
+
required: true
|
| 21 |
+
- type: textarea
|
| 22 |
+
id: description
|
| 23 |
+
attributes:
|
| 24 |
+
label: Please describe your wishes
|
| 25 |
+
validations:
|
| 26 |
+
required: true
|
scanpy/source/.github/dependabot.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: 2
|
| 2 |
+
updates:
|
| 3 |
+
- package-ecosystem: github-actions
|
| 4 |
+
directory: /
|
| 5 |
+
schedule:
|
| 6 |
+
interval: weekly
|
| 7 |
+
- package-ecosystem: pip
|
| 8 |
+
directory: /
|
| 9 |
+
schedule:
|
| 10 |
+
interval: weekly
|
scanpy/source/.github/pull_request_template.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!--
|
| 2 |
+
Thanks for opening a PR to scanpy!
|
| 3 |
+
Please be sure to follow the guidelines in our contribution guide (https://scanpy.readthedocs.io/en/latest/dev/index.html) to familiarize yourself with our workflow and speed up review.
|
| 4 |
+
-->
|
| 5 |
+
|
| 6 |
+
<!-- Please check (“- [x]”) and fill in the following boxes -->
|
| 7 |
+
- [ ] Closes #
|
| 8 |
+
- [ ] [Tests][] included or not required because:
|
| 9 |
+
<!-- Only check the following box if you did not include release notes -->
|
| 10 |
+
- [ ] [Release notes][] not necessary because:
|
| 11 |
+
|
| 12 |
+
[tests]: https://scanpy.readthedocs.io/en/stable/dev/testing.html#writing-tests
|
| 13 |
+
[release notes]: https://scanpy.readthedocs.io/en/stable/dev/documentation.html#adding-to-the-docs
|
scanpy/source/.github/workflows/benchmark.yml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Benchmark
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [main]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [main]
|
| 8 |
+
|
| 9 |
+
env:
|
| 10 |
+
FORCE_COLOR: "1"
|
| 11 |
+
|
| 12 |
+
jobs:
|
| 13 |
+
benchmark:
|
| 14 |
+
runs-on: ${{ matrix.os }}
|
| 15 |
+
|
| 16 |
+
strategy:
|
| 17 |
+
fail-fast: false
|
| 18 |
+
matrix:
|
| 19 |
+
python: ["3.13"]
|
| 20 |
+
os: [ubuntu-latest]
|
| 21 |
+
|
| 22 |
+
env:
|
| 23 |
+
OS: ${{ matrix.os }}
|
| 24 |
+
PYTHON: ${{ matrix.python }}
|
| 25 |
+
ASV_DIR: "./benchmarks"
|
| 26 |
+
|
| 27 |
+
steps:
|
| 28 |
+
- uses: actions/checkout@v5
|
| 29 |
+
with: { fetch-depth: 0 } # no blob:none so ASV can compare commits
|
| 30 |
+
|
| 31 |
+
- name: Fetch main branch for `asv run`’s hash
|
| 32 |
+
run: git fetch origin main:main
|
| 33 |
+
if: ${{ github.ref_name != 'main' }}
|
| 34 |
+
|
| 35 |
+
- name: Set up Python ${{ matrix.python }}
|
| 36 |
+
uses: actions/setup-python@v6
|
| 37 |
+
with:
|
| 38 |
+
python-version: ${{ matrix.python }}
|
| 39 |
+
cache: 'pip'
|
| 40 |
+
|
| 41 |
+
- name: Cache datasets
|
| 42 |
+
uses: actions/cache@v4
|
| 43 |
+
with:
|
| 44 |
+
path: |
|
| 45 |
+
~/.cache
|
| 46 |
+
key: benchmark-state-${{ hashFiles('benchmarks/**') }}
|
| 47 |
+
|
| 48 |
+
- name: Install dependencies
|
| 49 |
+
run: pip install 'asv>=0.6.4' py-rattler
|
| 50 |
+
|
| 51 |
+
- name: Configure ASV
|
| 52 |
+
working-directory: ${{ env.ASV_DIR }}
|
| 53 |
+
run: asv machine --yes
|
| 54 |
+
|
| 55 |
+
- name: Quick benchmark run
|
| 56 |
+
working-directory: ${{ env.ASV_DIR }}
|
| 57 |
+
run: asv run --dry-run --quick --show-stderr --verbose HEAD^!
|
scanpy/source/.github/workflows/check-pr.yml
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Pull Request Validation
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
pull_request:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
types:
|
| 8 |
+
# title changes
|
| 9 |
+
- edited
|
| 10 |
+
# milestone changes
|
| 11 |
+
- milestoned
|
| 12 |
+
- demilestoned
|
| 13 |
+
# label changes for “no milestone”
|
| 14 |
+
- labeled
|
| 15 |
+
- unlabeled
|
| 16 |
+
# initial check
|
| 17 |
+
- opened
|
| 18 |
+
- edited
|
| 19 |
+
- reopened
|
| 20 |
+
# code change (e.g. this workflow)
|
| 21 |
+
- synchronize
|
| 22 |
+
|
| 23 |
+
jobs:
|
| 24 |
+
# This job verifies that the milestone is present or not necessary
|
| 25 |
+
# and determines if “check-relnotes” needs to be run.
|
| 26 |
+
check-milestone:
|
| 27 |
+
name: Check title, milestone, and labels
|
| 28 |
+
runs-on: ubuntu-latest
|
| 29 |
+
steps:
|
| 30 |
+
- name: Check if milestone or “no milestone” label is present
|
| 31 |
+
uses: flying-sheep/check@v1
|
| 32 |
+
with:
|
| 33 |
+
success: ${{ github.event.pull_request.user.login == 'pre-commit-ci[bot]' || github.event.pull_request.milestone != null || contains(github.event.pull_request.labels.*.name, 'no milestone') }}
|
| 34 |
+
- name: Check if the “Release notes” checkbox is checked and filled
|
| 35 |
+
uses: kaisugi/action-regex-match@v1.0.1
|
| 36 |
+
id: checked-relnotes
|
| 37 |
+
with:
|
| 38 |
+
text: ${{ github.event.pull_request.body }}
|
| 39 |
+
regex: '^\s*- \[x\].*Release notes.*not necessary because:(.*)$'
|
| 40 |
+
flags: m
|
| 41 |
+
- name: Check PR title
|
| 42 |
+
id: check-title
|
| 43 |
+
uses: amannn/action-semantic-pull-request@v6
|
| 44 |
+
env: # Needs repo options: “Squash and merge” with commit message set to “PR title”
|
| 45 |
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 46 |
+
outputs:
|
| 47 |
+
no-relnotes-reason: ${{ steps.checked-relnotes.outputs.group1 }}
|
| 48 |
+
type: ${{ steps.check-title.outputs.type }}
|
| 49 |
+
# This job verifies that the relevant release notes file has been modified.
|
| 50 |
+
check-relnotes:
|
| 51 |
+
name: Check for release notes
|
| 52 |
+
runs-on: ubuntu-latest
|
| 53 |
+
needs: check-milestone
|
| 54 |
+
if: github.event.pull_request.user.login != 'pre-commit-ci[bot]' && needs.check-milestone.outputs.no-relnotes-reason == '' && !contains(fromJSON('["style","refactor","test","build","ci"]'), needs.check-milestone.outputs.type)
|
| 55 |
+
steps:
|
| 56 |
+
- uses: actions/checkout@v5
|
| 57 |
+
with: { filter: 'blob:none', fetch-depth: 0 }
|
| 58 |
+
- name: Find out if a relevant release fragment is added
|
| 59 |
+
uses: dorny/paths-filter@v3
|
| 60 |
+
id: changes
|
| 61 |
+
with:
|
| 62 |
+
filters: | # this is intentionally a string
|
| 63 |
+
relnotes: 'docs/release-notes/${{ github.event.pull_request.number }}.*.md'
|
| 64 |
+
- name: Check if a relevant release fragment is added
|
| 65 |
+
uses: flying-sheep/check@v1
|
| 66 |
+
with:
|
| 67 |
+
success: ${{ steps.changes.outputs.relnotes }}
|
scanpy/source/.github/workflows/ci.yml
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: CI
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
- "[0-9]+.[0-9]+.x"
|
| 8 |
+
pull_request:
|
| 9 |
+
|
| 10 |
+
env:
|
| 11 |
+
PYTEST_ADDOPTS: "-v --color=yes -n auto --internet-tests --junitxml=test-data/test-results.xml"
|
| 12 |
+
FORCE_COLOR: "1"
|
| 13 |
+
MPLBACKEND: agg
|
| 14 |
+
# It’s impossible to ignore SyntaxWarnings for a single module,
|
| 15 |
+
# so because leidenalg 0.10.0 has them, we pre-compile things: https://github.com/vtraag/leidenalg/issues/173
|
| 16 |
+
UV_COMPILE_BYTECODE: "1"
|
| 17 |
+
|
| 18 |
+
jobs:
|
| 19 |
+
get-environments:
|
| 20 |
+
runs-on: ubuntu-latest
|
| 21 |
+
outputs:
|
| 22 |
+
envs: ${{ steps.get-envs.outputs.envs }}
|
| 23 |
+
steps:
|
| 24 |
+
- uses: actions/checkout@v5
|
| 25 |
+
with: { filter: 'blob:none', fetch-depth: 0 }
|
| 26 |
+
- uses: astral-sh/setup-uv@v7
|
| 27 |
+
with: { enable-cache: false }
|
| 28 |
+
- id: get-envs
|
| 29 |
+
run: |
|
| 30 |
+
ENVS_JSON=$(NO_COLOR=1 uvx hatch env show --json | jq -c 'to_entries
|
| 31 |
+
| map(
|
| 32 |
+
select(.key | startswith("hatch-test"))
|
| 33 |
+
| {
|
| 34 |
+
name: .key,
|
| 35 |
+
"test-type": (if (.key | test("pre|min")) then "coverage" else null end),
|
| 36 |
+
python: .value.python
|
| 37 |
+
}
|
| 38 |
+
)')
|
| 39 |
+
echo "envs=${ENVS_JSON}" | tee $GITHUB_OUTPUT
|
| 40 |
+
|
| 41 |
+
test:
|
| 42 |
+
needs: get-environments
|
| 43 |
+
runs-on: ubuntu-latest
|
| 44 |
+
strategy:
|
| 45 |
+
matrix:
|
| 46 |
+
env: ${{ fromJSON(needs.get-environments.outputs.envs) }}
|
| 47 |
+
steps:
|
| 48 |
+
- uses: actions/checkout@v5
|
| 49 |
+
with: { filter: 'blob:none', fetch-depth: 0 }
|
| 50 |
+
|
| 51 |
+
- uses: astral-sh/setup-uv@v7
|
| 52 |
+
with:
|
| 53 |
+
python-version: ${{ matrix.env.python }}
|
| 54 |
+
|
| 55 |
+
- name: Cache downloaded data
|
| 56 |
+
uses: actions/cache@v4
|
| 57 |
+
with:
|
| 58 |
+
path: .pytest_cache/d/scanpy-data
|
| 59 |
+
key: pytest
|
| 60 |
+
|
| 61 |
+
- name: Install dependencies
|
| 62 |
+
run: |
|
| 63 |
+
uv tool install --with='click!=8.3.0' hatch
|
| 64 |
+
hatch -v env create ${{ matrix.env.name }}
|
| 65 |
+
|
| 66 |
+
- name: Run tests
|
| 67 |
+
if: matrix.env.test-type == null
|
| 68 |
+
run: hatch run ${{ matrix.env.name }}:run
|
| 69 |
+
- name: Run tests (coverage)
|
| 70 |
+
if: matrix.env.test-type == 'coverage'
|
| 71 |
+
run: |
|
| 72 |
+
hatch run ${{ matrix.env.name }}:run-cov
|
| 73 |
+
hatch run ${{ matrix.env.name }}:cov-combine
|
| 74 |
+
hatch run ${{ matrix.env.name }}:coverage xml
|
| 75 |
+
|
| 76 |
+
- name: Upload coverage data
|
| 77 |
+
if: ${{ !cancelled() && matrix.env.test-type == 'coverage' }}
|
| 78 |
+
uses: codecov/codecov-action@v5
|
| 79 |
+
with:
|
| 80 |
+
token: ${{ secrets.CODECOV_TOKEN }}
|
| 81 |
+
flags: ${{ matrix.env.name }}
|
| 82 |
+
fail_ci_if_error: true
|
| 83 |
+
files: test-data/coverage.xml
|
| 84 |
+
|
| 85 |
+
- name: Upload test results
|
| 86 |
+
if: ${{ !cancelled() }}
|
| 87 |
+
uses: codecov/test-results-action@v1
|
| 88 |
+
with:
|
| 89 |
+
token: ${{ secrets.CODECOV_TOKEN }}
|
| 90 |
+
flags: ${{ matrix.env.name }}
|
| 91 |
+
fail_ci_if_error: true
|
| 92 |
+
file: test-data/test-results.xml
|
| 93 |
+
|
| 94 |
+
- name: Publish debug artifacts
|
| 95 |
+
if: ${{ !cancelled() }}
|
| 96 |
+
uses: actions/upload-artifact@v5
|
| 97 |
+
with:
|
| 98 |
+
name: debug-data-${{ matrix.env.name }}
|
| 99 |
+
path: .pytest_cache/d/debug
|
| 100 |
+
|
| 101 |
+
build:
|
| 102 |
+
runs-on: ubuntu-latest
|
| 103 |
+
steps:
|
| 104 |
+
- uses: actions/checkout@v5
|
| 105 |
+
with: { filter: 'blob:none', fetch-depth: 0 }
|
| 106 |
+
- uses: actions/setup-python@v6
|
| 107 |
+
with:
|
| 108 |
+
python-version: "3.x"
|
| 109 |
+
- uses: astral-sh/setup-uv@v7
|
| 110 |
+
with:
|
| 111 |
+
enable-cache: true
|
| 112 |
+
- run: uvx --from build pyproject-build --sdist --wheel .
|
| 113 |
+
- run: uvx twine check dist/*
|
| 114 |
+
|
| 115 |
+
check:
|
| 116 |
+
if: always()
|
| 117 |
+
needs:
|
| 118 |
+
- get-environments
|
| 119 |
+
- test
|
| 120 |
+
- build
|
| 121 |
+
runs-on: ubuntu-latest
|
| 122 |
+
steps:
|
| 123 |
+
- uses: re-actors/alls-green@release/v1
|
| 124 |
+
with:
|
| 125 |
+
jobs: ${{ toJSON(needs) }}
|
scanpy/source/.github/workflows/publish.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Publish Python Package
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
release:
|
| 5 |
+
types: [published]
|
| 6 |
+
|
| 7 |
+
env:
|
| 8 |
+
FORCE_COLOR: "1"
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
publish:
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
environment: pypi
|
| 14 |
+
permissions:
|
| 15 |
+
id-token: write # to authenticate as Trusted Publisher to pypi.org
|
| 16 |
+
steps:
|
| 17 |
+
- uses: actions/checkout@v5
|
| 18 |
+
with: { filter: 'blob:none', fetch-depth: 0 }
|
| 19 |
+
- uses: actions/setup-python@v6
|
| 20 |
+
with:
|
| 21 |
+
python-version: "3.x"
|
| 22 |
+
- uses: astral-sh/setup-uv@v7
|
| 23 |
+
- run: uvx --from build pyproject-build --sdist --wheel .
|
| 24 |
+
- run: uvx twine check dist/*
|
| 25 |
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
scanpy/source/.gitignore
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Scanpy outfiles
|
| 2 |
+
/data/
|
| 3 |
+
/write/
|
| 4 |
+
/figures/
|
| 5 |
+
|
| 6 |
+
# Docs
|
| 7 |
+
/docs/_build/
|
| 8 |
+
/docs/generated/
|
| 9 |
+
/docs/data/
|
| 10 |
+
/docs/api/generated
|
| 11 |
+
/docs/external/generated
|
| 12 |
+
/docs/jupyter_execute
|
| 13 |
+
|
| 14 |
+
# tests
|
| 15 |
+
/*cache/
|
| 16 |
+
/test-data/
|
| 17 |
+
/tests/test*.h5ad
|
| 18 |
+
/tests/**/*actual.png
|
| 19 |
+
/tests/**/*failed-diff.png
|
| 20 |
+
|
| 21 |
+
# Environment management
|
| 22 |
+
/Pipfile
|
| 23 |
+
/Pipfile.lock
|
| 24 |
+
/requirements*.lock
|
| 25 |
+
|
| 26 |
+
# always-ignore extensions
|
| 27 |
+
*~
|
| 28 |
+
|
| 29 |
+
# Python build files
|
| 30 |
+
__pycache__/
|
| 31 |
+
/ci/scanpy-low-vers.txt
|
| 32 |
+
/dist/
|
| 33 |
+
/*-env/
|
| 34 |
+
/env-*/
|
| 35 |
+
/environment.yml
|
| 36 |
+
|
| 37 |
+
# OS stuff
|
| 38 |
+
.DS_Store
|
| 39 |
+
Thumbs.db
|
| 40 |
+
.ipynb_checkpoints/
|
| 41 |
+
.directory
|
| 42 |
+
|
| 43 |
+
# IDEs and editors
|
| 44 |
+
/.idea/
|
| 45 |
+
|
| 46 |
+
# asv benchmark files
|
| 47 |
+
/benchmarks/.asv
|
| 48 |
+
/benchmarks/data/
|
scanpy/source/.gitmodules
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[submodule "notebooks"]
|
| 2 |
+
path = notebooks
|
| 3 |
+
url = https://github.com/scverse/scanpy-tutorials/
|
scanpy/source/.pre-commit-config.yaml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ci:
|
| 2 |
+
autoupdate_commit_msg: "ci: pre-commit autoupdate"
|
| 3 |
+
|
| 4 |
+
repos:
|
| 5 |
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
| 6 |
+
rev: v0.14.4
|
| 7 |
+
hooks:
|
| 8 |
+
- id: ruff-check
|
| 9 |
+
args: ["--fix"]
|
| 10 |
+
- id: ruff-format
|
| 11 |
+
# The following can be removed once PLR0917 is out of preview
|
| 12 |
+
- name: ruff preview rules
|
| 13 |
+
id: ruff-check
|
| 14 |
+
args: ["--preview", "--select=PLR0917"]
|
| 15 |
+
- repo: https://github.com/flying-sheep/bibfmt
|
| 16 |
+
rev: v4.3.0
|
| 17 |
+
hooks:
|
| 18 |
+
- id: bibfmt
|
| 19 |
+
args:
|
| 20 |
+
- --sort-by-bibkey
|
| 21 |
+
- --drop=abstract
|
| 22 |
+
- repo: https://github.com/biomejs/pre-commit
|
| 23 |
+
rev: v2.3.4
|
| 24 |
+
hooks:
|
| 25 |
+
- id: biome-format
|
| 26 |
+
- repo: https://github.com/ComPWA/taplo-pre-commit
|
| 27 |
+
rev: v0.9.3
|
| 28 |
+
hooks:
|
| 29 |
+
- id: taplo-format
|
| 30 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
| 31 |
+
rev: v6.0.0
|
| 32 |
+
hooks:
|
| 33 |
+
- id: trailing-whitespace
|
| 34 |
+
exclude: tests/_data
|
| 35 |
+
- id: end-of-file-fixer
|
| 36 |
+
exclude: tests/_data
|
| 37 |
+
- id: check-added-large-files
|
| 38 |
+
- id: check-case-conflict
|
| 39 |
+
- id: check-toml
|
| 40 |
+
- id: check-yaml
|
| 41 |
+
- id: check-merge-conflict
|
| 42 |
+
- id: detect-private-key
|
| 43 |
+
- id: no-commit-to-branch
|
| 44 |
+
args: ["--branch=main"]
|
scanpy/source/.readthedocs.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# https://docs.readthedocs.io/en/stable/config-file/v2.html
|
| 2 |
+
version: 2
|
| 3 |
+
submodules:
|
| 4 |
+
include: all
|
| 5 |
+
build:
|
| 6 |
+
os: ubuntu-24.04
|
| 7 |
+
tools:
|
| 8 |
+
python: '3.13'
|
| 9 |
+
jobs:
|
| 10 |
+
post_checkout:
|
| 11 |
+
# unshallow so version can be derived from tag
|
| 12 |
+
- git fetch --unshallow || true
|
| 13 |
+
pre_build:
|
| 14 |
+
# run towncrier to preview the next version’s release notes
|
| 15 |
+
- ( find docs/release-notes -regex '[^.]+[.][^.]+.md' | grep -q . ) && towncrier build --keep || true
|
| 16 |
+
sphinx:
|
| 17 |
+
fail_on_warning: true # do not change or you will be fired
|
| 18 |
+
configuration: docs/conf.py
|
| 19 |
+
python:
|
| 20 |
+
install:
|
| 21 |
+
- method: pip
|
| 22 |
+
path: .
|
| 23 |
+
extra_requirements:
|
| 24 |
+
- doc
|
| 25 |
+
- dev # for towncrier
|
scanpy/source/.taplo.toml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[formatting]
|
| 2 |
+
array_auto_collapse = false
|
| 3 |
+
column_width = 120
|
| 4 |
+
compact_arrays = false
|
| 5 |
+
indent_string = ' '
|
scanpy/source/.vscode/launch.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "0.2.0",
|
| 3 |
+
"configurations": [
|
| 4 |
+
{
|
| 5 |
+
"name": "Python: Build Documentation",
|
| 6 |
+
"type": "debugpy",
|
| 7 |
+
"request": "launch",
|
| 8 |
+
"module": "sphinx",
|
| 9 |
+
"args": ["-M", "html", ".", "_build"],
|
| 10 |
+
"cwd": "${workspaceFolder}/docs",
|
| 11 |
+
"console": "internalConsole",
|
| 12 |
+
"justMyCode": false,
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"name": "Python: Debug Test",
|
| 16 |
+
"type": "debugpy",
|
| 17 |
+
"request": "launch",
|
| 18 |
+
"program": "${file}",
|
| 19 |
+
"purpose": ["debug-test"],
|
| 20 |
+
"console": "internalConsole",
|
| 21 |
+
"justMyCode": false,
|
| 22 |
+
"env": { "PYTEST_ADDOPTS": "--color=yes" },
|
| 23 |
+
"presentation": { "hidden": true },
|
| 24 |
+
},
|
| 25 |
+
],
|
| 26 |
+
}
|
scanpy/source/.vscode/settings.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"[python][toml][json][jsonc]": {
|
| 3 |
+
"editor.formatOnSave": true,
|
| 4 |
+
"editor.codeActionsOnSave": {
|
| 5 |
+
"source.organizeImports": "explicit",
|
| 6 |
+
"source.fixAll": "explicit",
|
| 7 |
+
},
|
| 8 |
+
},
|
| 9 |
+
"[python]": {
|
| 10 |
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
| 11 |
+
},
|
| 12 |
+
"[toml]": {
|
| 13 |
+
"editor.defaultFormatter": "tamasfe.even-better-toml",
|
| 14 |
+
},
|
| 15 |
+
"[json][jsonc]": {
|
| 16 |
+
"editor.defaultFormatter": "biomejs.biome",
|
| 17 |
+
},
|
| 18 |
+
"python.analysis.typeCheckingMode": "basic",
|
| 19 |
+
"python.testing.pytestArgs": ["-vv", "--color=yes", "--internet-tests"],
|
| 20 |
+
"python.testing.pytestEnabled": true,
|
| 21 |
+
"python.terminal.activateEnvironment": true,
|
| 22 |
+
}
|
scanpy/source/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Contributing
|
| 2 |
+
============
|
| 3 |
+
|
| 4 |
+
Contributions to Scanpy are highly welcome!
|
| 5 |
+
|
| 6 |
+
Before filing an issue
|
| 7 |
+
----------------------
|
| 8 |
+
* Search the repository (also google) to see if someone has already reported the same issue.
|
| 9 |
+
This allows contributors to spend less time responding to issues, and more time adding new features!
|
| 10 |
+
* Please provide a minimal complete verifiable example for any bug.
|
| 11 |
+
If you're not sure what this means, check out
|
| 12 |
+
[this blog post](https://matthewrocklin.com/minimal-bug-reports)
|
| 13 |
+
by Matthew Rocklin or [this definition](https://stackoverflow.com/help/mcve) from StackOverflow.
|
| 14 |
+
* Let us know about your environment. Environment information is available via: `sc.logging.print_versions()`.
|
| 15 |
+
|
| 16 |
+
Contributing code
|
| 17 |
+
-----------------
|
| 18 |
+
|
| 19 |
+
We love code contributions!
|
| 20 |
+
If you're interested in contributing code, please take a look over the [contribution guide](https://scanpy.readthedocs.io/en/latest/dev/index.html) in the main documentation.
|
scanpy/source/LICENSE
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 3-Clause License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 scverse®
|
| 4 |
+
Copyright (c) 2017 F. Alexander Wolf, P. Angerer, Theis Lab
|
| 5 |
+
All rights reserved.
|
| 6 |
+
|
| 7 |
+
Redistribution and use in source and binary forms, with or without
|
| 8 |
+
modification, are permitted provided that the following conditions are met:
|
| 9 |
+
|
| 10 |
+
* Redistributions of source code must retain the above copyright notice, this
|
| 11 |
+
list of conditions and the following disclaimer.
|
| 12 |
+
|
| 13 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 14 |
+
this list of conditions and the following disclaimer in the documentation
|
| 15 |
+
and/or other materials provided with the distribution.
|
| 16 |
+
|
| 17 |
+
* Neither the name of the copyright holder nor the names of its
|
| 18 |
+
contributors may be used to endorse or promote products derived from
|
| 19 |
+
this software without specific prior written permission.
|
| 20 |
+
|
| 21 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 22 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 23 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 24 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 25 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 26 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 27 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 28 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 29 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 30 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
scanpy/source/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[](https://github.com/scverse/scanpy/stargazers)
|
| 2 |
+
[](https://pypi.org/project/scanpy)
|
| 3 |
+
[](https://pepy.tech/project/scanpy)
|
| 4 |
+
[
|
| 5 |
+
](https://anaconda.org/conda-forge/scanpy)
|
| 6 |
+
[](https://scanpy.readthedocs.io)
|
| 7 |
+
[](https://github.com/scverse/scanpy/actions/workflows/ci.yml)
|
| 8 |
+
[](https://discourse.scverse.org/)
|
| 9 |
+
[](https://scverse.zulipchat.com)
|
| 10 |
+
[](https://numfocus.org/)
|
| 11 |
+
|
| 12 |
+
# Scanpy – Single-Cell Analysis in Python
|
| 13 |
+
|
| 14 |
+
Scanpy is a scalable toolkit for analyzing single-cell gene expression data built jointly with [anndata][].
|
| 15 |
+
It includes preprocessing, visualization, clustering, trajectory inference and differential expression testing.
|
| 16 |
+
The Python-based implementation efficiently deals with datasets of more than one million cells.
|
| 17 |
+
For datasets too large to fit into memory, [many scanpy functions][] are now compatible with [dask][] (**warning: experimental**).
|
| 18 |
+
|
| 19 |
+
Discuss usage on the scverse [Discourse][]. Read the [documentation][].
|
| 20 |
+
If you'd like to contribute by opening an issue or creating a pull request, please take a look at our [contribution guide][].
|
| 21 |
+
|
| 22 |
+
[anndata]: https://anndata.readthedocs.io
|
| 23 |
+
[dask]: https://docs.dask.org/en/stable/
|
| 24 |
+
[discourse]: https://discourse.scverse.org/
|
| 25 |
+
[documentation]: https://scanpy.readthedocs.io
|
| 26 |
+
[many scanpy functions]: https://github.com/scverse/scanpy/issues/2578
|
| 27 |
+
|
| 28 |
+
[//]: # (numfocus-fiscal-sponsor-attribution)
|
| 29 |
+
|
| 30 |
+
scanpy is part of the scverse® project ([website](https://scverse.org), [governance](https://scverse.org/about/roles)) and is fiscally sponsored by [NumFOCUS](https://numfocus.org/).
|
| 31 |
+
If you like scverse® and want to support our mission, please consider making a tax-deductible [donation](https://numfocus.org/donate-to-scverse) to help the project pay for developer time, professional services, travel, workshops, and a variety of other needs.
|
| 32 |
+
|
| 33 |
+
<div align="center">
|
| 34 |
+
<a href="https://numfocus.org/project/scverse">
|
| 35 |
+
<img
|
| 36 |
+
src="https://raw.githubusercontent.com/numfocus/templates/master/images/numfocus-logo.png"
|
| 37 |
+
width="200"
|
| 38 |
+
>
|
| 39 |
+
</a>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
## Citation
|
| 44 |
+
|
| 45 |
+
If you use `scanpy` in your work, please cite the `scanpy` publication as follows:
|
| 46 |
+
|
| 47 |
+
> **SCANPY: large-scale single-cell gene expression data analysis**
|
| 48 |
+
>
|
| 49 |
+
> F. Alexander Wolf, Philipp Angerer, Fabian J. Theis
|
| 50 |
+
>
|
| 51 |
+
> _Genome Biology_ 2018 Feb 06. doi: [10.1186/s13059-017-1382-0](https://doi.org/10.1186/s13059-017-1382-0).
|
| 52 |
+
|
| 53 |
+
You can cite the scverse publication as follows:
|
| 54 |
+
|
| 55 |
+
> **The scverse project provides a computational ecosystem for single-cell omics data analysis**
|
| 56 |
+
>
|
| 57 |
+
> Isaac Virshup, Danila Bredikhin, Lukas Heumos, Giovanni Palla, Gregor Sturm, Adam Gayoso, Ilia Kats, Mikaela Koutrouli, Scverse Community, Bonnie Berger, Dana Pe’er, Aviv Regev, Sarah A. Teichmann, Francesca Finotello, F. Alexander Wolf, Nir Yosef, Oliver Stegle & Fabian J. Theis
|
| 58 |
+
>
|
| 59 |
+
> _Nat Biotechnol._ 2023 Apr 10. doi: [10.1038/s41587-023-01733-8](https://doi.org/10.1038/s41587-023-01733-8).
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
[contribution guide]: CONTRIBUTING.md
|
scanpy/source/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
scanpy Project Package Initialization File
|
| 4 |
+
"""
|
scanpy/source/benchmarks/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Scanpy Benchmarks
|
| 2 |
+
|
| 3 |
+
This directory contains code for benchmarking Scanpy using [asv][].
|
| 4 |
+
|
| 5 |
+
The functionality is checked using the [`benchmark.yml`][] workflow.
|
| 6 |
+
Benchmarks are run using the [benchmark bot][].
|
| 7 |
+
|
| 8 |
+
[asv]: https://asv.readthedocs.io/
|
| 9 |
+
[`benchmark.yml`]: ../.github/workflows/benchmark.yml
|
| 10 |
+
[benchmark bot]: https://github.com/apps/scverse-benchmark
|
| 11 |
+
|
| 12 |
+
## Data processing in benchmarks
|
| 13 |
+
|
| 14 |
+
Each dataset is processed so it has
|
| 15 |
+
|
| 16 |
+
- `.layers['counts']` (containing data in C/row-major format) and `.layers['counts-off-axis']` (containing data in FORTRAN/column-major format)
|
| 17 |
+
- `.X` and `.layers['off-axis']` with log-transformed data (formats like above)
|
| 18 |
+
- a `.var['mt']` boolean column indicating mitochondrial genes
|
| 19 |
+
|
| 20 |
+
The benchmarks are set up so the `layer` parameter indicates the layer that will be moved into `.X` before the benchmark.
|
| 21 |
+
That way, we don’t need to add `layer=layer` everywhere.
|
scanpy/source/benchmarks/asv.conf.json
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
// The version of the config file format. Do not change, unless
|
| 3 |
+
// you know what you are doing.
|
| 4 |
+
"version": 1,
|
| 5 |
+
|
| 6 |
+
// The name of the project being benchmarked
|
| 7 |
+
"project": "scanpy",
|
| 8 |
+
|
| 9 |
+
// The project's homepage
|
| 10 |
+
"project_url": "https://scanpy.readthedocs.io/",
|
| 11 |
+
|
| 12 |
+
// The URL or local path of the source code repository for the
|
| 13 |
+
// project being benchmarked
|
| 14 |
+
"repo": "..",
|
| 15 |
+
|
| 16 |
+
// The Python project's subdirectory in your repo. If missing or
|
| 17 |
+
// the empty string, the project is assumed to be located at the root
|
| 18 |
+
// of the repository.
|
| 19 |
+
// "repo_subdir": "",
|
| 20 |
+
|
| 21 |
+
// Customizable commands for building, installing, and
|
| 22 |
+
// uninstalling the project. See asv.conf.json documentation.
|
| 23 |
+
//
|
| 24 |
+
// "install_command": ["python -mpip install {wheel_file}"],
|
| 25 |
+
// "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"],
|
| 26 |
+
"build_command": [
|
| 27 |
+
"python -m pip install build",
|
| 28 |
+
"python -m build --wheel -o {build_cache_dir} {build_dir}",
|
| 29 |
+
],
|
| 30 |
+
|
| 31 |
+
// List of branches to benchmark. If not provided, defaults to "master"
|
| 32 |
+
// (for git) or "default" (for mercurial).
|
| 33 |
+
"branches": ["main"], // for git
|
| 34 |
+
|
| 35 |
+
// The DVCS being used. If not set, it will be automatically
|
| 36 |
+
// determined from "repo" by looking at the protocol in the URL
|
| 37 |
+
// (if remote), or by looking for special directories, such as
|
| 38 |
+
// ".git" (if local).
|
| 39 |
+
"dvcs": "git",
|
| 40 |
+
|
| 41 |
+
// The tool to use to create environments. May be "conda",
|
| 42 |
+
// "virtualenv" or other value depending on the plugins in use.
|
| 43 |
+
// If missing or the empty string, the tool will be automatically
|
| 44 |
+
// determined by looking for tools on the PATH environment
|
| 45 |
+
// variable.
|
| 46 |
+
"environment_type": "rattler",
|
| 47 |
+
|
| 48 |
+
// timeout in seconds for installing any dependencies in environment
|
| 49 |
+
// defaults to 10 min
|
| 50 |
+
//"install_timeout": 600,
|
| 51 |
+
|
| 52 |
+
// the base URL to show a commit for the project.
|
| 53 |
+
"show_commit_url": "https://github.com/scverse/scanpy/commit/",
|
| 54 |
+
|
| 55 |
+
// The Pythons you'd like to test against. If not provided, defaults
|
| 56 |
+
// to the current version of Python used to run `asv`.
|
| 57 |
+
// "pythons": ["3.11", "3.13"],
|
| 58 |
+
|
| 59 |
+
// The list of conda channel names to be searched for benchmark
|
| 60 |
+
// dependency packages in the specified order
|
| 61 |
+
"conda_channels": ["conda-forge", "defaults"],
|
| 62 |
+
|
| 63 |
+
// The matrix of dependencies to test. Each key is the name of a
|
| 64 |
+
// package (in PyPI) and the values are version numbers. An empty
|
| 65 |
+
// list or empty string indicates to just test against the default
|
| 66 |
+
// (latest) version. null indicates that the package is to not be
|
| 67 |
+
// installed. If the package to be tested is only available from
|
| 68 |
+
// PyPi, and the 'environment_type' is conda, then you can preface
|
| 69 |
+
// the package name by 'pip+', and the package will be installed via
|
| 70 |
+
// pip (with all the conda available packages installed first,
|
| 71 |
+
// followed by the pip installed packages).
|
| 72 |
+
//
|
| 73 |
+
"matrix": {
|
| 74 |
+
"numpy": [""],
|
| 75 |
+
"scipy": [""],
|
| 76 |
+
"h5py": [""],
|
| 77 |
+
"natsort": [""],
|
| 78 |
+
"pandas": [""],
|
| 79 |
+
"memory_profiler": [""],
|
| 80 |
+
"zarr": [""],
|
| 81 |
+
"pytest": [""],
|
| 82 |
+
"pip+igraph": [""], // https://github.com/airspeed-velocity/asv/issues/1554
|
| 83 |
+
// "psutil": [""]
|
| 84 |
+
"pooch": [""],
|
| 85 |
+
"scikit-image": [""], // https://github.com/conda-forge/scikit-misc-feedstock/pull/29
|
| 86 |
+
// "scikit-misc": [""],
|
| 87 |
+
},
|
| 88 |
+
|
| 89 |
+
// Combinations of libraries/python versions can be excluded/included
|
| 90 |
+
// from the set to test. Each entry is a dictionary containing additional
|
| 91 |
+
// key-value pairs to include/exclude.
|
| 92 |
+
//
|
| 93 |
+
// An exclude entry excludes entries where all values match. The
|
| 94 |
+
// values are regexps that should match the whole string.
|
| 95 |
+
//
|
| 96 |
+
// An include entry adds an environment. Only the packages listed
|
| 97 |
+
// are installed. The 'python' key is required. The exclude rules
|
| 98 |
+
// do not apply to includes.
|
| 99 |
+
//
|
| 100 |
+
// In addition to package names, the following keys are available:
|
| 101 |
+
//
|
| 102 |
+
// - python
|
| 103 |
+
// Python version, as in the *pythons* variable above.
|
| 104 |
+
// - environment_type
|
| 105 |
+
// Environment type, as above.
|
| 106 |
+
// - sys_platform
|
| 107 |
+
// Platform, as in sys.platform. Possible values for the common
|
| 108 |
+
// cases: 'linux2', 'win32', 'cygwin', 'darwin'.
|
| 109 |
+
//
|
| 110 |
+
// "exclude": [
|
| 111 |
+
// {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows
|
| 112 |
+
// {"environment_type": "conda", "six": null}, // don't run without six on conda
|
| 113 |
+
// ],
|
| 114 |
+
//
|
| 115 |
+
// "include": [
|
| 116 |
+
// // additional env for python2.7
|
| 117 |
+
// {"python": "2.7", "numpy": "1.8"},
|
| 118 |
+
// // additional env if run on windows+conda
|
| 119 |
+
// {"platform": "win32", "environment_type": "conda", "python": "2.7", "libpython": ""},
|
| 120 |
+
// ],
|
| 121 |
+
|
| 122 |
+
// The directory (relative to the current directory) that benchmarks are
|
| 123 |
+
// stored in. If not provided, defaults to "benchmarks"
|
| 124 |
+
// "benchmark_dir": "benchmarks",
|
| 125 |
+
|
| 126 |
+
// The directory (relative to the current directory) to cache the Python
|
| 127 |
+
// environments in. If not provided, defaults to "env"
|
| 128 |
+
"env_dir": ".asv/env",
|
| 129 |
+
|
| 130 |
+
// The directory (relative to the current directory) that raw benchmark
|
| 131 |
+
// results are stored in. If not provided, defaults to "results".
|
| 132 |
+
"results_dir": ".asv/results",
|
| 133 |
+
|
| 134 |
+
// The directory (relative to the current directory) that the html tree
|
| 135 |
+
// should be written to. If not provided, defaults to "html".
|
| 136 |
+
"html_dir": ".asv/html",
|
| 137 |
+
|
| 138 |
+
// The number of characters to retain in the commit hashes.
|
| 139 |
+
// "hash_length": 8,
|
| 140 |
+
|
| 141 |
+
// `asv` will cache results of the recent builds in each
|
| 142 |
+
// environment, making them faster to install next time. This is
|
| 143 |
+
// the number of builds to keep, per environment.
|
| 144 |
+
// "build_cache_size": 2,
|
| 145 |
+
|
| 146 |
+
// The commits after which the regression search in `asv publish`
|
| 147 |
+
// should start looking for regressions. Dictionary whose keys are
|
| 148 |
+
// regexps matching to benchmark names, and values corresponding to
|
| 149 |
+
// the commit (exclusive) after which to start looking for
|
| 150 |
+
// regressions. The default is to start from the first commit
|
| 151 |
+
// with results. If the commit is `null`, regression detection is
|
| 152 |
+
// skipped for the matching benchmark.
|
| 153 |
+
//
|
| 154 |
+
// "regressions_first_commits": {
|
| 155 |
+
// "some_benchmark": "352cdf", // Consider regressions only after this commit
|
| 156 |
+
// "another_benchmark": null, // Skip regression detection altogether
|
| 157 |
+
// },
|
| 158 |
+
|
| 159 |
+
// The thresholds for relative change in results, after which `asv
|
| 160 |
+
// publish` starts reporting regressions. Dictionary of the same
|
| 161 |
+
// form as in ``regressions_first_commits``, with values
|
| 162 |
+
// indicating the thresholds. If multiple entries match, the
|
| 163 |
+
// maximum is taken. If no entry matches, the default is 5%.
|
| 164 |
+
//
|
| 165 |
+
// "regressions_thresholds": {
|
| 166 |
+
// "some_benchmark": 0.01, // Threshold of 1%
|
| 167 |
+
// "another_benchmark": 0.5, // Threshold of 50%
|
| 168 |
+
// },
|
| 169 |
+
}
|
scanpy/source/benchmarks/benchmarks/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""ASV benchmark suite for scanpy."""
|
scanpy/source/benchmarks/benchmarks/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (264 Bytes). View file
|
|
|
scanpy/source/benchmarks/benchmarks/_utils.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import itertools
|
| 4 |
+
import warnings
|
| 5 |
+
from functools import cache
|
| 6 |
+
from typing import TYPE_CHECKING
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pooch
|
| 10 |
+
from anndata import concat
|
| 11 |
+
from asv_runner.benchmarks.mark import skip_for_params
|
| 12 |
+
|
| 13 |
+
import scanpy as sc
|
| 14 |
+
from scanpy._compat import CSRBase
|
| 15 |
+
|
| 16 |
+
if TYPE_CHECKING:
|
| 17 |
+
from collections.abc import Callable, Sequence
|
| 18 |
+
from collections.abc import Set as AbstractSet
|
| 19 |
+
from typing import Literal, Protocol
|
| 20 |
+
|
| 21 |
+
from anndata import AnnData
|
| 22 |
+
|
| 23 |
+
from scanpy._compat import CSCBase
|
| 24 |
+
|
| 25 |
+
class ParamSkipper(Protocol):
|
| 26 |
+
def __call__[C: Callable](self, **skipped: AbstractSet) -> Callable[[C], C]: ...
|
| 27 |
+
|
| 28 |
+
Dataset = Literal["pbmc68k_reduced", "pbmc3k", "bmmc", "lung93k"]
|
| 29 |
+
KeyX = Literal["off-axis"] | None
|
| 30 |
+
KeyCount = Literal["counts", "counts-off-axis"]
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@cache
|
| 34 |
+
def _pbmc68k_reduced() -> AnnData:
|
| 35 |
+
"""A small datasets with a dense `.X`.""" # noqa: D401
|
| 36 |
+
adata = sc.datasets.pbmc68k_reduced()
|
| 37 |
+
assert isinstance(adata.X, np.ndarray)
|
| 38 |
+
assert not np.isfortran(adata.X)
|
| 39 |
+
|
| 40 |
+
# raw has the same number of genes, so we can use it for counts
|
| 41 |
+
# it doesn’t actually contain counts for some reason, but close enough
|
| 42 |
+
assert isinstance(adata.raw.X, CSRBase)
|
| 43 |
+
adata.layers["counts"] = adata.raw.X.toarray(order="C")
|
| 44 |
+
mapper = dict(
|
| 45 |
+
percent_mito="pct_counts_mt",
|
| 46 |
+
n_counts="total_counts",
|
| 47 |
+
)
|
| 48 |
+
adata.obs.rename(columns=mapper, inplace=True)
|
| 49 |
+
return adata
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def pbmc68k_reduced() -> AnnData:
|
| 53 |
+
return _pbmc68k_reduced().copy()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
@cache
|
| 57 |
+
def _pbmc3k() -> AnnData:
|
| 58 |
+
adata = sc.datasets.pbmc3k()
|
| 59 |
+
assert isinstance(adata.X, CSRBase)
|
| 60 |
+
adata.layers["counts"] = adata.X.astype(np.int32, copy=True)
|
| 61 |
+
sc.pp.log1p(adata)
|
| 62 |
+
return adata
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def pbmc3k() -> AnnData:
|
| 66 |
+
return _pbmc3k().copy()
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
@cache
|
| 70 |
+
def _bmmc(n_obs: int = 4000) -> AnnData:
|
| 71 |
+
registry = pooch.create(
|
| 72 |
+
path=pooch.os_cache("pooch"),
|
| 73 |
+
base_url="doi:10.6084/m9.figshare.22716739.v1/",
|
| 74 |
+
)
|
| 75 |
+
registry.load_registry_from_doi()
|
| 76 |
+
samples = {smp: f"{smp}_filtered_feature_bc_matrix.h5" for smp in ("s1d1", "s1d3")}
|
| 77 |
+
adatas = {}
|
| 78 |
+
|
| 79 |
+
for sample_id, filename in samples.items():
|
| 80 |
+
path = registry.fetch(filename)
|
| 81 |
+
with warnings.catch_warnings():
|
| 82 |
+
warnings.filterwarnings("ignore", r"Variable names are not unique")
|
| 83 |
+
sample_adata = sc.read_10x_h5(path)
|
| 84 |
+
sample_adata.var_names_make_unique()
|
| 85 |
+
sc.pp.subsample(sample_adata, n_obs=n_obs // len(samples))
|
| 86 |
+
adatas[sample_id] = sample_adata
|
| 87 |
+
|
| 88 |
+
with warnings.catch_warnings():
|
| 89 |
+
warnings.filterwarnings("ignore", r"Observation names are not unique")
|
| 90 |
+
adata = concat(adatas, label="sample")
|
| 91 |
+
adata.obs_names_make_unique()
|
| 92 |
+
|
| 93 |
+
assert isinstance(adata.X, CSRBase)
|
| 94 |
+
adata.layers["counts"] = adata.X.astype(np.int32, copy=True)
|
| 95 |
+
sc.pp.log1p(adata)
|
| 96 |
+
adata.obs["n_counts"] = adata.layers["counts"].sum(axis=1).A1
|
| 97 |
+
return adata
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def bmmc(n_obs: int = 400) -> AnnData:
|
| 101 |
+
return _bmmc(n_obs).copy()
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@cache
|
| 105 |
+
def _lung93k() -> AnnData:
|
| 106 |
+
path = pooch.retrieve(
|
| 107 |
+
url="https://figshare.com/ndownloader/files/45788454",
|
| 108 |
+
known_hash="md5:4f28af5ff226052443e7e0b39f3f9212",
|
| 109 |
+
)
|
| 110 |
+
adata = sc.read_h5ad(path)
|
| 111 |
+
assert isinstance(adata.X, CSRBase)
|
| 112 |
+
adata.layers["counts"] = adata.X.astype(np.int32, copy=True)
|
| 113 |
+
sc.pp.log1p(adata)
|
| 114 |
+
return adata
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def lung93k() -> AnnData:
|
| 118 |
+
return _lung93k().copy()
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def to_off_axis(x: np.ndarray | CSRBase) -> np.ndarray | CSCBase:
|
| 122 |
+
if isinstance(x, CSRBase):
|
| 123 |
+
return x.tocsc()
|
| 124 |
+
if isinstance(x, np.ndarray):
|
| 125 |
+
assert not np.isfortran(x)
|
| 126 |
+
return x.copy(order="F")
|
| 127 |
+
msg = f"Unexpected type {type(x)}"
|
| 128 |
+
raise TypeError(msg)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _get_dataset_raw(dataset: Dataset) -> tuple[AnnData, str | None]:
|
| 132 |
+
match dataset:
|
| 133 |
+
case "pbmc68k_reduced":
|
| 134 |
+
adata, batch_key = pbmc68k_reduced(), None
|
| 135 |
+
case "pbmc3k":
|
| 136 |
+
adata, batch_key = pbmc3k(), None # can’t use this with batches
|
| 137 |
+
case "bmmc":
|
| 138 |
+
# TODO: allow specifying bigger variant
|
| 139 |
+
adata, batch_key = bmmc(400), "sample"
|
| 140 |
+
case "lung93k":
|
| 141 |
+
adata, batch_key = lung93k(), "PatientNumber"
|
| 142 |
+
case _:
|
| 143 |
+
msg = f"Unknown dataset {dataset}"
|
| 144 |
+
raise AssertionError(msg)
|
| 145 |
+
|
| 146 |
+
# add off-axis layers
|
| 147 |
+
adata.layers["off-axis"] = to_off_axis(adata.X)
|
| 148 |
+
adata.layers["counts-off-axis"] = to_off_axis(adata.layers["counts"])
|
| 149 |
+
|
| 150 |
+
# add mitochondrial gene and pre-compute qc metrics
|
| 151 |
+
adata.var["mt"] = adata.var_names.str.startswith("MT-")
|
| 152 |
+
assert adata.var["mt"].sum() > 0, "no MT genes in dataset"
|
| 153 |
+
sc.pp.calculate_qc_metrics(
|
| 154 |
+
adata, qc_vars=["mt"], percent_top=None, log1p=False, inplace=True
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
return adata, batch_key
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def get_dataset(dataset: Dataset, *, layer: KeyX = None) -> tuple[AnnData, str | None]:
|
| 161 |
+
adata, batch_key = _get_dataset_raw(dataset)
|
| 162 |
+
if layer is not None:
|
| 163 |
+
adata.X = adata.layers.pop(layer)
|
| 164 |
+
return adata, batch_key
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def get_count_dataset(
|
| 168 |
+
dataset: Dataset, *, layer: KeyCount = "counts"
|
| 169 |
+
) -> tuple[AnnData, str | None]:
|
| 170 |
+
adata, batch_key = _get_dataset_raw(dataset)
|
| 171 |
+
|
| 172 |
+
adata.X = adata.layers.pop(layer)
|
| 173 |
+
# remove indicators that X was transformed
|
| 174 |
+
adata.uns.pop("log1p", None)
|
| 175 |
+
|
| 176 |
+
return adata, batch_key
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def param_skipper(
|
| 180 |
+
param_names: Sequence[str], params: tuple[Sequence[object], ...]
|
| 181 |
+
) -> ParamSkipper:
|
| 182 |
+
"""Create a decorator that will skip all combinations that contain any of the given parameters.
|
| 183 |
+
|
| 184 |
+
Examples
|
| 185 |
+
--------
|
| 186 |
+
>>> param_names = ["letters", "numbers"]
|
| 187 |
+
>>> params = [["a", "b"], [3, 4, 5]]
|
| 188 |
+
>>> skip_when = param_skipper(param_names, params)
|
| 189 |
+
|
| 190 |
+
>>> @skip_when(letters={"a"}, numbers={3})
|
| 191 |
+
... def func(a, b):
|
| 192 |
+
... print(a, b)
|
| 193 |
+
>>> run_as_asv_benchmark(func)
|
| 194 |
+
b 4
|
| 195 |
+
b 5
|
| 196 |
+
|
| 197 |
+
"""
|
| 198 |
+
|
| 199 |
+
def skip[C: Callable](**skipped: AbstractSet) -> Callable[[C], C]:
|
| 200 |
+
skipped_combs = [
|
| 201 |
+
tuple(record.values())
|
| 202 |
+
for record in (
|
| 203 |
+
dict(zip(param_names, vals, strict=True))
|
| 204 |
+
for vals in itertools.product(*params)
|
| 205 |
+
)
|
| 206 |
+
if any(v in skipped.get(n, set()) for n, v in record.items())
|
| 207 |
+
]
|
| 208 |
+
# print(skipped_combs, file=sys.stderr)
|
| 209 |
+
return skip_for_params(skipped_combs)
|
| 210 |
+
|
| 211 |
+
return skip
|
scanpy/source/benchmarks/benchmarks/preprocessing_counts.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmark preprocessing operations in Scanpy that run on counts.
|
| 2 |
+
|
| 3 |
+
API documentation: <https://scanpy.readthedocs.io/en/stable/api/preprocessing.html>.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from itertools import product
|
| 9 |
+
from typing import TYPE_CHECKING
|
| 10 |
+
|
| 11 |
+
import anndata as ad
|
| 12 |
+
|
| 13 |
+
import scanpy as sc
|
| 14 |
+
|
| 15 |
+
from ._utils import get_count_dataset
|
| 16 |
+
|
| 17 |
+
if TYPE_CHECKING:
|
| 18 |
+
from ._utils import Dataset, KeyCount
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# ASV suite
|
| 22 |
+
class PreprocessingCountsSuite: # noqa: D101
|
| 23 |
+
params: tuple[list[Dataset], list[KeyCount]] = (
|
| 24 |
+
["pbmc68k_reduced", "pbmc3k"],
|
| 25 |
+
["counts", "counts-off-axis"],
|
| 26 |
+
)
|
| 27 |
+
param_names = ("dataset", "layer")
|
| 28 |
+
|
| 29 |
+
def setup_cache(self) -> None:
|
| 30 |
+
"""Without this caching, asv was running several processes which meant the data was repeatedly downloaded."""
|
| 31 |
+
for dataset, layer in product(*self.params):
|
| 32 |
+
adata, batch_key = get_count_dataset(dataset, layer=layer)
|
| 33 |
+
assert "lop1p" not in adata.uns
|
| 34 |
+
adata.uns["batch_key"] = batch_key
|
| 35 |
+
adata.write_h5ad(f"{dataset}_{layer}.h5ad")
|
| 36 |
+
|
| 37 |
+
def setup(self, dataset, layer) -> None:
|
| 38 |
+
self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad")
|
| 39 |
+
|
| 40 |
+
def time_filter_cells(self, *_) -> None:
|
| 41 |
+
sc.pp.filter_cells(self.adata, min_genes=100)
|
| 42 |
+
|
| 43 |
+
def peakmem_filter_cells(self, *_) -> None:
|
| 44 |
+
sc.pp.filter_cells(self.adata, min_genes=100)
|
| 45 |
+
|
| 46 |
+
def time_filter_genes(self, *_) -> None:
|
| 47 |
+
sc.pp.filter_genes(self.adata, min_cells=3)
|
| 48 |
+
|
| 49 |
+
def peakmem_filter_genes(self, *_) -> None:
|
| 50 |
+
sc.pp.filter_genes(self.adata, min_cells=3)
|
| 51 |
+
|
| 52 |
+
def time_scrublet(self, *_) -> None:
|
| 53 |
+
sc.pp.scrublet(self.adata, batch_key=self.adata.uns["batch_key"])
|
| 54 |
+
|
| 55 |
+
def peakmem_scrublet(self, *_) -> None:
|
| 56 |
+
sc.pp.scrublet(self.adata, batch_key=self.adata.uns["batch_key"])
|
| 57 |
+
|
| 58 |
+
# sciki-misc does not exit on osx-arm64
|
| 59 |
+
# https://github.com/conda-forge/scikit-misc-feedstock/pull/29
|
| 60 |
+
# def time_hvg_seurat_v3(self, *_):
|
| 61 |
+
# # seurat v3 runs on counts
|
| 62 |
+
# sc.pp.highly_variable_genes(self.adata, flavor="seurat_v3_paper")
|
| 63 |
+
|
| 64 |
+
# def peakmem_hvg_seurat_v3(self, *_):
|
| 65 |
+
# sc.pp.highly_variable_genes(self.adata, flavor="seurat_v3_paper")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class FastSuite:
|
| 69 |
+
"""Suite for fast preprocessing operations."""
|
| 70 |
+
|
| 71 |
+
params: tuple[list[Dataset], list[KeyCount]] = (
|
| 72 |
+
["pbmc3k", "pbmc68k_reduced", "bmmc", "lung93k"],
|
| 73 |
+
["counts", "counts-off-axis"],
|
| 74 |
+
)
|
| 75 |
+
param_names = ("dataset", "layer")
|
| 76 |
+
|
| 77 |
+
def setup_cache(self) -> None:
|
| 78 |
+
"""Without this caching, asv was running several processes which meant the data was repeatedly downloaded."""
|
| 79 |
+
for dataset, layer in product(*self.params):
|
| 80 |
+
adata, _ = get_count_dataset(dataset, layer=layer)
|
| 81 |
+
assert "lop1p" not in adata.uns
|
| 82 |
+
adata.write_h5ad(f"{dataset}_{layer}.h5ad")
|
| 83 |
+
|
| 84 |
+
def setup(self, dataset, layer) -> None:
|
| 85 |
+
self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad")
|
| 86 |
+
|
| 87 |
+
def time_calculate_qc_metrics(self, *_) -> None:
|
| 88 |
+
sc.pp.calculate_qc_metrics(
|
| 89 |
+
self.adata, qc_vars=["mt"], percent_top=None, log1p=False, inplace=True
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
def peakmem_calculate_qc_metrics(self, *_) -> None:
|
| 93 |
+
sc.pp.calculate_qc_metrics(
|
| 94 |
+
self.adata, qc_vars=["mt"], percent_top=None, log1p=False, inplace=True
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
def time_normalize_total(self, *_) -> None:
|
| 98 |
+
sc.pp.normalize_total(self.adata, target_sum=1e4)
|
| 99 |
+
|
| 100 |
+
def peakmem_normalize_total(self, *_) -> None:
|
| 101 |
+
sc.pp.normalize_total(self.adata, target_sum=1e4)
|
| 102 |
+
|
| 103 |
+
def time_log1p(self, *_) -> None:
|
| 104 |
+
# TODO: This would fail: assert "log1p" not in self.adata.uns, "ASV bug?"
|
| 105 |
+
# https://github.com/scverse/scanpy/issues/3052
|
| 106 |
+
self.adata.uns.pop("log1p", None)
|
| 107 |
+
sc.pp.log1p(self.adata)
|
| 108 |
+
|
| 109 |
+
def peakmem_log1p(self, *_) -> None:
|
| 110 |
+
self.adata.uns.pop("log1p", None)
|
| 111 |
+
sc.pp.log1p(self.adata)
|
scanpy/source/benchmarks/benchmarks/preprocessing_log.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmark preprocessing operations in Scanpy that run on log-transformed data.
|
| 2 |
+
|
| 3 |
+
API documentation: <https://scanpy.readthedocs.io/en/stable/api/preprocessing.html>.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from itertools import product
|
| 9 |
+
from typing import TYPE_CHECKING
|
| 10 |
+
|
| 11 |
+
import anndata as ad
|
| 12 |
+
|
| 13 |
+
import scanpy as sc
|
| 14 |
+
|
| 15 |
+
from ._utils import get_dataset, param_skipper
|
| 16 |
+
|
| 17 |
+
if TYPE_CHECKING:
|
| 18 |
+
from ._utils import Dataset, KeyX
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# ASV suite
|
| 22 |
+
|
| 23 |
+
params: tuple[list[Dataset], list[KeyX]] = (
|
| 24 |
+
["pbmc68k_reduced", "pbmc3k"],
|
| 25 |
+
[None, "off-axis"],
|
| 26 |
+
)
|
| 27 |
+
param_names = ("dataset", "layer")
|
| 28 |
+
skip_when = param_skipper(param_names, params)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class PreprocessingSuite: # noqa: D101
|
| 32 |
+
params = params
|
| 33 |
+
param_names = param_names
|
| 34 |
+
|
| 35 |
+
def setup_cache(self) -> None:
|
| 36 |
+
"""Without this caching, asv was running several processes which meant the data was repeatedly downloaded."""
|
| 37 |
+
for dataset, layer in product(*self.params):
|
| 38 |
+
adata, _ = get_dataset(dataset, layer=layer)
|
| 39 |
+
adata.write_h5ad(f"{dataset}_{layer}.h5ad")
|
| 40 |
+
|
| 41 |
+
def setup(self, dataset, layer) -> None:
|
| 42 |
+
self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad")
|
| 43 |
+
|
| 44 |
+
def time_pca(self, *_) -> None:
|
| 45 |
+
sc.pp.pca(self.adata, svd_solver="arpack")
|
| 46 |
+
|
| 47 |
+
def peakmem_pca(self, *_) -> None:
|
| 48 |
+
sc.pp.pca(self.adata, svd_solver="arpack")
|
| 49 |
+
|
| 50 |
+
def time_highly_variable_genes(self, *_) -> None:
|
| 51 |
+
# the default flavor runs on log-transformed data
|
| 52 |
+
sc.pp.highly_variable_genes(
|
| 53 |
+
self.adata, min_mean=0.0125, max_mean=3, min_disp=0.5
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
def peakmem_highly_variable_genes(self, *_) -> None:
|
| 57 |
+
sc.pp.highly_variable_genes(
|
| 58 |
+
self.adata, min_mean=0.0125, max_mean=3, min_disp=0.5
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
# regress_out is very slow for this dataset
|
| 62 |
+
@skip_when(dataset={"pbmc3k"})
|
| 63 |
+
def time_regress_out(self, *_) -> None:
|
| 64 |
+
sc.pp.regress_out(self.adata, ["total_counts", "pct_counts_mt"])
|
| 65 |
+
|
| 66 |
+
@skip_when(dataset={"pbmc3k"})
|
| 67 |
+
def peakmem_regress_out(self, *_) -> None:
|
| 68 |
+
sc.pp.regress_out(self.adata, ["total_counts", "pct_counts_mt"])
|
| 69 |
+
|
| 70 |
+
def time_scale(self, *_) -> None:
|
| 71 |
+
sc.pp.scale(self.adata, max_value=10)
|
| 72 |
+
|
| 73 |
+
def peakmem_scale(self, *_) -> None:
|
| 74 |
+
sc.pp.scale(self.adata, max_value=10)
|
scanpy/source/benchmarks/benchmarks/tools.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmark tool operations in Scanpy.
|
| 2 |
+
|
| 3 |
+
API documentation: <https://scanpy.readthedocs.io/en/stable/api/tools.html>.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import anndata as ad
|
| 9 |
+
|
| 10 |
+
import scanpy as sc
|
| 11 |
+
|
| 12 |
+
from ._utils import pbmc68k_reduced
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ToolsSuite: # noqa: D101
|
| 16 |
+
def setup_cache(self) -> None:
|
| 17 |
+
adata = pbmc68k_reduced()
|
| 18 |
+
assert "X_pca" in adata.obsm
|
| 19 |
+
adata.write_h5ad("adata.h5ad")
|
| 20 |
+
|
| 21 |
+
def setup(self) -> None:
|
| 22 |
+
self.adata = ad.read_h5ad("adata.h5ad")
|
| 23 |
+
|
| 24 |
+
def time_umap(self) -> None:
|
| 25 |
+
sc.tl.umap(self.adata)
|
| 26 |
+
|
| 27 |
+
def peakmem_umap(self) -> None:
|
| 28 |
+
sc.tl.umap(self.adata)
|
| 29 |
+
|
| 30 |
+
def time_diffmap(self) -> None:
|
| 31 |
+
sc.tl.diffmap(self.adata)
|
| 32 |
+
|
| 33 |
+
def peakmem_diffmap(self) -> None:
|
| 34 |
+
sc.tl.diffmap(self.adata)
|
| 35 |
+
|
| 36 |
+
def time_leiden(self) -> None:
|
| 37 |
+
sc.tl.leiden(self.adata, flavor="igraph")
|
| 38 |
+
|
| 39 |
+
def peakmem_leiden(self) -> None:
|
| 40 |
+
sc.tl.leiden(self.adata, flavor="igraph")
|
| 41 |
+
|
| 42 |
+
def time_rank_genes_groups(self) -> None:
|
| 43 |
+
sc.tl.rank_genes_groups(self.adata, "bulk_labels", method="wilcoxon")
|
| 44 |
+
|
| 45 |
+
def peakmem_rank_genes_groups(self) -> None:
|
| 46 |
+
sc.tl.rank_genes_groups(self.adata, "bulk_labels", method="wilcoxon")
|
scanpy/source/biome.jsonc
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://biomejs.dev/schemas/2.1.0/schema.json",
|
| 3 |
+
"formatter": { "useEditorconfig": true },
|
| 4 |
+
"overrides": [
|
| 5 |
+
{
|
| 6 |
+
"includes": ["./.vscode/*.json", "**/*.jsonc", "**/asv.conf.json"],
|
| 7 |
+
"json": {
|
| 8 |
+
"formatter": {
|
| 9 |
+
"trailingCommas": "all",
|
| 10 |
+
},
|
| 11 |
+
"parser": {
|
| 12 |
+
"allowComments": true,
|
| 13 |
+
"allowTrailingCommas": true,
|
| 14 |
+
},
|
| 15 |
+
},
|
| 16 |
+
},
|
| 17 |
+
],
|
| 18 |
+
}
|
scanpy/source/ci/scripts/low-vers.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# /// script
|
| 3 |
+
# requires-python = ">=3.11"
|
| 4 |
+
# dependencies = [ "packaging" ]
|
| 5 |
+
# ///
|
| 6 |
+
"""Parse a pyproject.toml file and output a list of minimum dependency versions."""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import sys
|
| 12 |
+
import tomllib
|
| 13 |
+
from collections import deque
|
| 14 |
+
from contextlib import ExitStack
|
| 15 |
+
from functools import cached_property
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
from typing import TYPE_CHECKING
|
| 18 |
+
|
| 19 |
+
from packaging.requirements import Requirement
|
| 20 |
+
from packaging.version import Version
|
| 21 |
+
|
| 22 |
+
if TYPE_CHECKING:
|
| 23 |
+
from collections.abc import Generator, Iterable, Sequence
|
| 24 |
+
from collections.abc import Set as AbstractSet
|
| 25 |
+
from typing import Any, Self
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def min_dep(req: Requirement) -> Requirement:
|
| 29 |
+
"""Given a requirement, return the minimum version specifier.
|
| 30 |
+
|
| 31 |
+
Example
|
| 32 |
+
-------
|
| 33 |
+
>>> min_dep(Requirement("numpy>=1.0"))
|
| 34 |
+
<Requirement('numpy==1.0')>
|
| 35 |
+
>>> min_dep(Requirement("numpy<3.0"))
|
| 36 |
+
<Requirement('numpy<3.0')>
|
| 37 |
+
"""
|
| 38 |
+
req_name = req.name
|
| 39 |
+
if req.extras:
|
| 40 |
+
req_name = f"{req_name}[{','.join(req.extras)}]"
|
| 41 |
+
|
| 42 |
+
filter_specs = [
|
| 43 |
+
spec for spec in req.specifier if spec.operator in {"==", "~=", ">=", ">"}
|
| 44 |
+
]
|
| 45 |
+
if not filter_specs:
|
| 46 |
+
# TODO: handle markers
|
| 47 |
+
return Requirement(f"{req_name}{req.specifier}")
|
| 48 |
+
min_version = Version("0.0.0.a1")
|
| 49 |
+
for spec in filter_specs:
|
| 50 |
+
if spec.operator in {">", ">=", "~="}:
|
| 51 |
+
min_version = max(min_version, Version(spec.version))
|
| 52 |
+
elif spec.operator == "==":
|
| 53 |
+
min_version = Version(spec.version)
|
| 54 |
+
|
| 55 |
+
return Requirement(f"{req_name}=={min_version}")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def extract_min_deps(
|
| 59 |
+
dependencies: Iterable[Requirement], *, pyproject
|
| 60 |
+
) -> Generator[Requirement, None, None]:
|
| 61 |
+
"""Extract minimum dependency versions from a list of requirements."""
|
| 62 |
+
dependencies = deque(dependencies) # We'll be mutating this
|
| 63 |
+
project_name = pyproject["project"]["name"]
|
| 64 |
+
|
| 65 |
+
deps = {}
|
| 66 |
+
while len(dependencies) > 0:
|
| 67 |
+
req = dependencies.pop()
|
| 68 |
+
|
| 69 |
+
# If we are referring to other optional dependency lists, resolve them
|
| 70 |
+
if req.name == project_name:
|
| 71 |
+
assert req.extras, (
|
| 72 |
+
f"Project included itself as dependency, without specifying extras: {req}"
|
| 73 |
+
)
|
| 74 |
+
for extra in req.extras:
|
| 75 |
+
extra_deps = pyproject["project"]["optional-dependencies"][extra]
|
| 76 |
+
dependencies += map(Requirement, extra_deps)
|
| 77 |
+
else:
|
| 78 |
+
if req.name in deps:
|
| 79 |
+
req.specifier &= deps[req.name].specifier
|
| 80 |
+
req.extras |= deps[req.name].extras
|
| 81 |
+
deps[req.name] = min_dep(req)
|
| 82 |
+
yield from deps.values()
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class Args(argparse.Namespace):
|
| 86 |
+
"""Parse a pyproject.toml file and output a list of minimum dependencies.
|
| 87 |
+
|
| 88 |
+
Output is optimized for `[uv] pip install` (see `-o`/`--output` for details).
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
_path: Path
|
| 92 |
+
output: Path | None
|
| 93 |
+
_extras: list[str]
|
| 94 |
+
_all_extras: bool
|
| 95 |
+
|
| 96 |
+
@classmethod
|
| 97 |
+
def parse(cls, argv: Sequence[str] | None = None) -> Self:
|
| 98 |
+
"""Parse CLI arguments."""
|
| 99 |
+
return cls.parser().parse_args(argv, cls())
|
| 100 |
+
|
| 101 |
+
@classmethod
|
| 102 |
+
def parser(cls) -> argparse.ArgumentParser:
|
| 103 |
+
"""Construct a CLI argument parser."""
|
| 104 |
+
parser = argparse.ArgumentParser(
|
| 105 |
+
prog="min-deps",
|
| 106 |
+
description=cls.__doc__,
|
| 107 |
+
usage="pip install `python min-deps.py pyproject.toml`",
|
| 108 |
+
allow_abbrev=False,
|
| 109 |
+
)
|
| 110 |
+
parser.add_argument(
|
| 111 |
+
"_path",
|
| 112 |
+
metavar="pyproject.toml",
|
| 113 |
+
type=Path,
|
| 114 |
+
help="Path to pyproject.toml to parse minimum dependencies from",
|
| 115 |
+
)
|
| 116 |
+
parser.add_argument(
|
| 117 |
+
"--extras",
|
| 118 |
+
dest="_extras",
|
| 119 |
+
metavar="EXTRA",
|
| 120 |
+
type=str,
|
| 121 |
+
nargs="*",
|
| 122 |
+
default=(),
|
| 123 |
+
help="extras to install",
|
| 124 |
+
)
|
| 125 |
+
parser.add_argument(
|
| 126 |
+
"--all-extras",
|
| 127 |
+
dest="_all_extras",
|
| 128 |
+
action="store_true",
|
| 129 |
+
help="get all extras",
|
| 130 |
+
)
|
| 131 |
+
parser.add_argument(
|
| 132 |
+
*("--output", "-o"),
|
| 133 |
+
metavar="FILE",
|
| 134 |
+
type=Path,
|
| 135 |
+
default=None,
|
| 136 |
+
help=(
|
| 137 |
+
"output file (default: stdout). "
|
| 138 |
+
"Without this option, output is space-separated for direct passing to `pip install`. "
|
| 139 |
+
"With this option, output written to a file newline-separated file usable as `requirements.txt` or `constraints.txt`."
|
| 140 |
+
),
|
| 141 |
+
)
|
| 142 |
+
return parser
|
| 143 |
+
|
| 144 |
+
@cached_property
|
| 145 |
+
def pyproject(self) -> dict[str, Any]:
|
| 146 |
+
"""Return the parsed `pyproject.toml`."""
|
| 147 |
+
return tomllib.loads(self._path.read_text())
|
| 148 |
+
|
| 149 |
+
@cached_property
|
| 150 |
+
def extras(self) -> AbstractSet[str]:
|
| 151 |
+
"""Return the extras to install."""
|
| 152 |
+
if self._extras:
|
| 153 |
+
if self._all_extras:
|
| 154 |
+
sys.exit("Cannot specify both --extras and --all-extras")
|
| 155 |
+
return dict.fromkeys(self._extras).keys()
|
| 156 |
+
if not self._all_extras:
|
| 157 |
+
return set()
|
| 158 |
+
return self.pyproject["project"]["optional-dependencies"].keys()
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def main(argv: Sequence[str] | None = None) -> None:
|
| 162 |
+
"""Run main entry point."""
|
| 163 |
+
args = Args.parse(argv)
|
| 164 |
+
|
| 165 |
+
project_name = args.pyproject["project"]["name"]
|
| 166 |
+
deps = [
|
| 167 |
+
*map(Requirement, args.pyproject["project"]["dependencies"]),
|
| 168 |
+
*(Requirement(f"{project_name}[{extra}]") for extra in args.extras),
|
| 169 |
+
]
|
| 170 |
+
|
| 171 |
+
min_deps = extract_min_deps(deps, pyproject=args.pyproject)
|
| 172 |
+
|
| 173 |
+
sep = "\n" if args.output else " "
|
| 174 |
+
with ExitStack() as stack:
|
| 175 |
+
f = stack.enter_context(args.output.open("w")) if args.output else sys.stdout
|
| 176 |
+
print(sep.join(map(str, min_deps)), file=f)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
if __name__ == "__main__":
|
| 180 |
+
main()
|
scanpy/source/ci/scripts/towncrier_automation.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# /// script
|
| 3 |
+
# dependencies = [ "towncrier", "packaging" ]
|
| 4 |
+
# ///
|
| 5 |
+
"""Script to automate towncrier release note PRs."""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import subprocess
|
| 11 |
+
from typing import TYPE_CHECKING
|
| 12 |
+
|
| 13 |
+
from packaging.version import Version
|
| 14 |
+
|
| 15 |
+
if TYPE_CHECKING:
|
| 16 |
+
from collections.abc import Sequence
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Args(argparse.Namespace):
|
| 20 |
+
"""Command line arguments."""
|
| 21 |
+
|
| 22 |
+
version: str
|
| 23 |
+
dry_run: bool
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def parse_args(argv: Sequence[str] | None = None) -> Args:
|
| 27 |
+
"""Construct a CLI argument parser."""
|
| 28 |
+
parser = argparse.ArgumentParser(
|
| 29 |
+
prog="towncrier-automation",
|
| 30 |
+
description=(
|
| 31 |
+
"This script runs towncrier for a given version, "
|
| 32 |
+
"creates a branch off of the current one, "
|
| 33 |
+
"and then creates a PR into the original branch with the changes. "
|
| 34 |
+
"The PR will be backported to main if the current branch is not main."
|
| 35 |
+
),
|
| 36 |
+
)
|
| 37 |
+
parser.add_argument(
|
| 38 |
+
"version",
|
| 39 |
+
type=str,
|
| 40 |
+
help=(
|
| 41 |
+
"The new version for the release must have at least three parts, like `major.minor.patch` and no `major.minor`. "
|
| 42 |
+
"It can have a suffix like `major.minor.patch.dev0` or `major.minor.0rc1`."
|
| 43 |
+
),
|
| 44 |
+
)
|
| 45 |
+
parser.add_argument(
|
| 46 |
+
"--dry-run",
|
| 47 |
+
help="Whether or not to dry-run the actual creation of the pull request",
|
| 48 |
+
action="store_true",
|
| 49 |
+
)
|
| 50 |
+
args = parser.parse_args(argv, Args())
|
| 51 |
+
# validate the version
|
| 52 |
+
if len(Version(args.version).release) != 3:
|
| 53 |
+
msg = f"Version argument {args.version} must contain major, minor, and patch version."
|
| 54 |
+
raise ValueError(msg)
|
| 55 |
+
return args
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def main(argv: Sequence[str] | None = None) -> None:
|
| 59 |
+
"""Run main entry point."""
|
| 60 |
+
args = parse_args(argv)
|
| 61 |
+
|
| 62 |
+
# Run towncrier
|
| 63 |
+
subprocess.run(
|
| 64 |
+
["towncrier", "build", f"--version={args.version}", "--yes"], check=True
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
# Check if we are on the main branch to know if we need to backport
|
| 68 |
+
base_branch = subprocess.run(
|
| 69 |
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
| 70 |
+
capture_output=True,
|
| 71 |
+
text=True,
|
| 72 |
+
check=True,
|
| 73 |
+
).stdout.strip()
|
| 74 |
+
pr_description = "- [x] Release notes not necessary because: compiles release notes"
|
| 75 |
+
if base_branch != "main":
|
| 76 |
+
pr_description += "\n\n@meeseeksdev backport to main"
|
| 77 |
+
branch_name = f"release_notes_{args.version}"
|
| 78 |
+
|
| 79 |
+
# Create a new branch + commit
|
| 80 |
+
subprocess.run(["git", "switch", "-c", branch_name], check=True)
|
| 81 |
+
subprocess.run(["git", "add", "docs/release-notes"], check=True)
|
| 82 |
+
pr_title = f"docs: generate {args.version} release notes"
|
| 83 |
+
subprocess.run(["git", "commit", "-m", pr_title], check=True)
|
| 84 |
+
|
| 85 |
+
# push
|
| 86 |
+
if not args.dry_run:
|
| 87 |
+
subprocess.run(
|
| 88 |
+
["git", "push", "--set-upstream", "origin", branch_name], check=True
|
| 89 |
+
)
|
| 90 |
+
else:
|
| 91 |
+
print("Dry run, not pushing")
|
| 92 |
+
|
| 93 |
+
# Create a PR
|
| 94 |
+
subprocess.run(
|
| 95 |
+
[
|
| 96 |
+
"gh",
|
| 97 |
+
"pr",
|
| 98 |
+
"create",
|
| 99 |
+
f"--base={base_branch}",
|
| 100 |
+
f"--title={pr_title}",
|
| 101 |
+
f"--body={pr_description}",
|
| 102 |
+
*(
|
| 103 |
+
["--label=no milestone", "--label=Development Process 🚀"]
|
| 104 |
+
if base_branch == "main"
|
| 105 |
+
else []
|
| 106 |
+
),
|
| 107 |
+
*(["--dry-run"] if args.dry_run else []),
|
| 108 |
+
],
|
| 109 |
+
check=True,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
# Enable auto-merge
|
| 113 |
+
if not args.dry_run:
|
| 114 |
+
subprocess.run(
|
| 115 |
+
["gh", "pr", "merge", branch_name, "--auto", "--squash"], check=True
|
| 116 |
+
)
|
| 117 |
+
else:
|
| 118 |
+
print("Dry run, not merging")
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
if __name__ == "__main__":
|
| 122 |
+
main()
|
scanpy/source/docs/Makefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Minimal makefile for Sphinx documentation
|
| 2 |
+
#
|
| 3 |
+
|
| 4 |
+
# You can set these variables from the command line.
|
| 5 |
+
SPHINXOPTS =
|
| 6 |
+
SPHINXBUILD = python3 -msphinx
|
| 7 |
+
SPHINXPROJ = Scanpy
|
| 8 |
+
SOURCEDIR = .
|
| 9 |
+
BUILDDIR = _build
|
| 10 |
+
|
| 11 |
+
# Put it first so that "make" without argument is like "make help".
|
| 12 |
+
help:
|
| 13 |
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
| 14 |
+
|
| 15 |
+
.PHONY: help Makefile
|
| 16 |
+
|
| 17 |
+
clean:
|
| 18 |
+
rm -r "$(BUILDDIR)"
|
| 19 |
+
rm -r "generated"
|
| 20 |
+
find . -name scanpy.*.rst -delete
|
| 21 |
+
|
| 22 |
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
| 23 |
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
| 24 |
+
%: Makefile
|
| 25 |
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|