├── .github └── workflows │ ├── pre-commit.yml │ ├── python-package.yml │ └── python-publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── _temp │ └── example_cci.py ├── _templates │ └── autosummary │ │ ├── base.rst │ │ └── class.rst ├── api.rst ├── authors.rst ├── conf.py ├── index.rst ├── installation.rst ├── interactive.rst ├── list_tutorial.txt ├── make.bat ├── references.rst ├── release_notes │ ├── 0.3.2.rst │ ├── 0.4.6.rst │ └── index.rst ├── requirements.txt └── tutorials.rst ├── requirements.txt ├── resource ├── README.md ├── connectomedb2020.txt ├── label_transfer_bc.csv ├── means.txt └── predictions.csv ├── setup.cfg ├── setup.py ├── stlearn ├── __init__.py ├── __main__.py ├── _compat.py ├── _datasets │ ├── __init__.py │ └── _datasets.py ├── _settings.py ├── add.py ├── adds │ ├── __init__.py │ ├── add_deconvolution.py │ ├── add_image.py │ ├── add_labels.py │ ├── add_loupe_clusters.py │ ├── add_lr.py │ ├── add_mask.py │ ├── add_positions.py │ ├── annotation.py │ └── parsing.py ├── app │ ├── __init__.py │ ├── app.py │ ├── cli.py │ ├── requirements.txt │ ├── source │ │ ├── __init__.py │ │ ├── forms │ │ │ ├── __init__.py │ │ │ ├── form_validators.py │ │ │ ├── forms.py │ │ │ ├── helper_functions.py │ │ │ ├── utils.py │ │ │ ├── view_helpers.py │ │ │ └── views.py │ │ └── readme.md │ ├── static │ │ ├── css │ │ │ ├── material-dashboard.min.css │ │ │ └── style.css │ │ ├── img │ │ │ ├── Settings.gif │ │ │ ├── favicon.png │ │ │ └── loading_gif.gif │ │ └── js │ │ │ ├── core │ │ │ ├── bootstrap-material-design.min.js │ │ │ ├── jquery.min.js │ │ │ └── popper.min.js │ │ │ └── plugins │ │ │ └── perfect-scrollbar.jquery.min.js │ └── templates │ │ ├── __init__.py │ │ ├── annotate_plot.html │ │ ├── base.html │ │ ├── cci.html │ │ ├── cci_old.html │ │ ├── cci_plot.html │ │ ├── choose_cluster.html │ │ ├── cluster_plot.html │ │ ├── clustering.html │ │ ├── dea.html │ │ ├── flash_header.html │ │ ├── gene_plot.html │ │ ├── index.html │ │ ├── lr.html │ │ ├── lr_plot.html │ │ ├── preprocessing.html │ │ ├── progress.html │ │ ├── psts.html │ │ ├── spatial_cci_plot.html │ │ ├── superform.html │ │ └── upload.html ├── classes.py ├── datasets.py ├── em.py ├── embedding │ ├── __init__.py │ ├── diffmap.py │ ├── fa.py │ ├── ica.py │ ├── pca.py │ └── umap.py ├── gui.py ├── image_preprocessing │ ├── __init__.py │ ├── feature_extractor.py │ ├── image_tiling.py │ ├── model_zoo.py │ └── segmentation.py ├── logging.py ├── pl.py ├── plotting │ ├── QC_plot.py │ ├── __init__.py │ ├── _docs.py │ ├── cci_plot.py │ ├── cci_plot_helpers.py │ ├── classes.py │ ├── classes_bokeh.py │ ├── cluster_plot.py │ ├── deconvolution_plot.py │ ├── feat_plot.py │ ├── gene_plot.py │ ├── mask_plot.py │ ├── non_spatial_plot.py │ ├── palettes_st.py │ ├── stack_3d_plot.py │ ├── subcluster_plot.py │ ├── trajectory │ │ ├── DE_transition_plot.py │ │ ├── __init__.py │ │ ├── check_trajectory.py │ │ ├── local_plot.py │ │ ├── pseudotime_plot.py │ │ ├── transition_markers_plot.py │ │ ├── tree_plot.py │ │ ├── tree_plot_simple.py │ │ └── utils.py │ └── utils.py ├── pp.py ├── preprocessing │ ├── __init__.py │ ├── filter_genes.py │ ├── graph.py │ ├── log_scale.py │ └── normalize.py ├── spatial.py ├── spatials │ ├── SME │ │ ├── __init__.py │ │ ├── _weighting_matrix.py │ │ ├── impute.py │ │ └── normalize.py │ ├── __init__.py │ ├── clustering │ │ ├── __init__.py │ │ └── localization.py │ ├── morphology │ │ ├── __init__.py │ │ └── adjust.py │ ├── smooth │ │ ├── __init__.py │ │ └── disk.py │ └── trajectory │ │ ├── __init__.py │ │ ├── compare_transitions.py │ │ ├── detect_transition_markers.py │ │ ├── global_level.py │ │ ├── local_level.py │ │ ├── pseudotime.py │ │ ├── pseudotimespace.py │ │ ├── set_root.py │ │ ├── shortest_path_spatial_PAGA.py │ │ ├── utils.py │ │ └── weight_optimization.py ├── tl.py ├── tools │ ├── __init__.py │ ├── clustering │ │ ├── __init__.py │ │ ├── annotate.py │ │ ├── kmeans.py │ │ └── louvain.py │ ├── label │ │ ├── __init__.py │ │ ├── label.py │ │ ├── label_transfer.R │ │ ├── rctd.R │ │ └── singleR.R │ └── microenv │ │ ├── __init__.py │ │ └── cci │ │ ├── __init__.py │ │ ├── analysis.py │ │ ├── base.py │ │ ├── base_grouping.py │ │ ├── databases │ │ ├── __init__.py │ │ ├── connectomeDB2020_lit.txt │ │ └── connectomeDB2020_put.txt │ │ ├── go.R │ │ ├── go.py │ │ ├── het.py │ │ ├── het_helpers.py │ │ ├── merge.py │ │ ├── perm_utils.py │ │ ├── permutation.py │ │ └── r_helpers.py ├── utils.py └── wrapper │ ├── __init__.py │ ├── concatenate_spatial_adata.py │ ├── convert_scanpy.py │ └── read.py ├── tests ├── __init__.py ├── test_CCI.py ├── test_PSTS.py ├── test_SME.py ├── test_data │ ├── test_data.h5 │ └── test_image.jpg ├── test_install.py └── utils.py └── tox.ini /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v2 14 | - uses: pre-commit/action@v2.0.0 15 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [3.8] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | python -m pip install pytest 30 | 31 | pip install leidenalg 32 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 33 | pip install louvain 34 | - name: Test with pytest 35 | run: | 36 | pytest 37 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .ipynb_checkpoints 3 | */.ipynb_checkpoints/* 4 | build/ 5 | dist/ 6 | *.egg-info 7 | /*.ipynb 8 | /*.csv 9 | output/ 10 | .DS_Store 11 | */.DS_Store 12 | .idea/ 13 | docs/_build 14 | data/ 15 | tiling/ 16 | .pytest_cache 17 | figures/ 18 | *.h5ad 19 | inferCNV/ 20 | stlearn/tools/microenv/cci/junk_code.py 21 | stlearn/tools/microenv/cci/.Rhistory 22 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.4.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - id: check-added-large-files 9 | - id: check-docstring-first 10 | - id: check-byte-order-marker 11 | - id: requirements-txt-fixer 12 | 13 | - repo: https://github.com/psf/black 14 | rev: 22.3.0 15 | hooks: 16 | - id: black 17 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | build: 2 | image: latest 3 | python: 4 | version: 3.8 5 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Genomics and Machine Learning lab 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/duypham2108/stlearn/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | stLearn could always use more documentation, whether as part of the 42 | official stLearn docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/duypham2108/stlearn/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `stlearn` for local development. 61 | 62 | 1. Fork the `stlearn` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/stlearn.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv stlearn 70 | $ cd stlearn/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 stlearn tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check 106 | https://travis-ci.org/duypham2108/stlearn/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | 115 | $ python -m unittest tests.test_stlearn 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bump2version patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.4.11 (2022-11-25) 6 | ------------------ 7 | 0.4.10 (2022-11-22) 8 | ------------------ 9 | 0.4.8 (2022-06-15) 10 | ------------------ 11 | 0.4.7 (2022-03-28) 12 | ------------------ 13 | 0.4.6 (2022-03-09) 14 | ------------------ 15 | 0.4.5 (2022-03-02) 16 | ------------------ 17 | 0.4.0 (2022-02-03) 18 | ------------------ 19 | 0.3.2 (2021-03-29) 20 | ------------------ 21 | 0.3.1 (2020-12-24) 22 | ------------------ 23 | 0.2.7 (2020-09-12) 24 | ------------------ 25 | 0.2.6 (2020-08-04) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | BSD License 4 | 5 | Copyright (c) 2020, Genomics and Machine Learning lab 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, this 15 | list of conditions and the following disclaimer in the documentation and/or 16 | other materials provided with the distribution. 17 | 18 | * Neither the name of the copyright holder nor the names of its 19 | contributors may be used to endorse or promote products derived from this 20 | software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 26 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 29 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 30 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 31 | OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | include requirements.txt 7 | 8 | recursive-include tests * 9 | recursive-exclude * __pycache__ 10 | recursive-exclude * *.py[co] 11 | 12 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 13 | recursive-include stlearn/app *.txt 14 | recursive-include stlearn/app/templates * 15 | recursive-include stlearn/app/static * 16 | recursive-include stlearn/tools/microenv/cci/databases *.txt 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | deepreg_logo 4 |

5 | 6 | 7 | 8 | 11 | 26 | 27 | 28 | 31 | 36 | 37 | 38 | 41 | 45 | 46 | 47 | 50 | 54 | 55 |
9 | Package 10 | 12 | 13 | PyPI Version 14 | 15 | 16 | PyPI downloads 18 | 19 | 20 | Conda downloads 21 | 22 | 23 | Install 24 | 25 |
29 | Documentation 30 | 32 | 33 | Documentation Status 34 | 35 |
39 | Paper 40 | 42 | DOI 44 |
48 | License 49 | 51 | LICENSE 53 |
56 | 57 | 58 | # stLearn - A downstream analysis toolkit for Spatial Transcriptomic data 59 | 60 | **stLearn** is designed to comprehensively analyse Spatial Transcriptomics (ST) data to investigate complex biological processes within an undissociated tissue. ST is emerging as the “next generation” of single-cell RNA sequencing because it adds spatial and morphological context to the transcriptional profile of cells in an intact tissue section. However, existing ST analysis methods typically use the captured spatial and/or morphological data as a visualisation tool rather than as informative features for model development. We have developed an analysis method that exploits all three data types: Spatial distance, tissue Morphology, and gene Expression measurements (SME) from ST data. This combinatorial approach allows us to more accurately model underlying tissue biology, and allows researchers to address key questions in three major research areas: cell type identification, spatial trajectory reconstruction, and the study of cell-cell interactions within an undissociated tissue sample. 61 | 62 | --- 63 | 64 | ## Getting Started 65 | 66 | - [Documentation and Tutorials](https://stlearn.readthedocs.io/en/latest/) 67 | 68 | ## Citing stLearn 69 | 70 | If you have used stLearn in your research, please consider citing us: 71 | 72 | > Pham, Duy, et al. "Robust mapping of spatiotemporal trajectories and cell–cell interactions in healthy and diseased tissues." 73 | > Nature Communications 14.1 (2023): 7739. 74 | > [https://doi.org/10.1101/2020.05.31.125658](https://doi.org/10.1038/s41467-023-43120-6) 75 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = stlearn 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 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_templates/autosummary/base.rst: -------------------------------------------------------------------------------- 1 | 2 | {% extends "!autosummary/base.rst" %} 3 | 4 | .. http://www.sphinx-doc.org/en/stable/ext/autosummary.html#customizing-templates 5 | -------------------------------------------------------------------------------- /docs/_templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. add toctree option to make autodoc generate the pages 6 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. module:: stlearn 2 | .. automodule:: stlearn 3 | :noindex: 4 | 5 | API 6 | ====================================== 7 | 8 | Import stLearn as:: 9 | 10 | import stlearn as st 11 | 12 | 13 | Wrapper functions: `wrapper` 14 | ------------------------------ 15 | 16 | .. module:: stlearn.wrapper 17 | .. currentmodule:: stlearn 18 | 19 | .. autosummary:: 20 | :toctree: . 21 | 22 | Read10X 23 | ReadOldST 24 | ReadSlideSeq 25 | ReadMERFISH 26 | ReadSeqFish 27 | convert_scanpy 28 | create_stlearn 29 | 30 | 31 | Add: `add` 32 | ------------------- 33 | 34 | .. module:: stlearn.add 35 | .. currentmodule:: stlearn 36 | 37 | .. autosummary:: 38 | :toctree: . 39 | 40 | add.image 41 | add.positions 42 | add.parsing 43 | add.lr 44 | add.labels 45 | add.annotation 46 | add.add_loupe_clusters 47 | add.add_mask 48 | add.apply_mask 49 | add.add_deconvolution 50 | 51 | 52 | Preprocessing: `pp` 53 | ------------------- 54 | 55 | .. module:: stlearn.pp 56 | .. currentmodule:: stlearn 57 | 58 | .. autosummary:: 59 | :toctree: . 60 | 61 | pp.filter_genes 62 | pp.log1p 63 | pp.normalize_total 64 | pp.scale 65 | pp.neighbors 66 | pp.tiling 67 | pp.extract_feature 68 | 69 | 70 | 71 | Embedding: `em` 72 | ------------------- 73 | 74 | .. module:: stlearn.em 75 | .. currentmodule:: stlearn 76 | 77 | .. autosummary:: 78 | :toctree: . 79 | 80 | em.run_pca 81 | em.run_umap 82 | em.run_ica 83 | em.run_fa 84 | em.run_diffmap 85 | 86 | 87 | Spatial: `spatial` 88 | ------------------- 89 | 90 | .. module:: stlearn.spatial.clustering 91 | .. currentmodule:: stlearn 92 | 93 | .. autosummary:: 94 | :toctree: . 95 | 96 | spatial.clustering.localization 97 | 98 | .. module:: stlearn.spatial.trajectory 99 | .. currentmodule:: stlearn 100 | 101 | .. autosummary:: 102 | :toctree: . 103 | 104 | spatial.trajectory.pseudotime 105 | spatial.trajectory.pseudotimespace_global 106 | spatial.trajectory.pseudotimespace_local 107 | spatial.trajectory.compare_transitions 108 | spatial.trajectory.detect_transition_markers_clades 109 | spatial.trajectory.detect_transition_markers_branches 110 | spatial.trajectory.set_root 111 | 112 | .. module:: stlearn.spatial.morphology 113 | .. currentmodule:: stlearn 114 | 115 | .. autosummary:: 116 | :toctree: . 117 | 118 | spatial.morphology.adjust 119 | 120 | .. module:: stlearn.spatial.SME 121 | .. currentmodule:: stlearn 122 | 123 | .. autosummary:: 124 | :toctree: . 125 | 126 | spatial.SME.SME_impute0 127 | spatial.SME.pseudo_spot 128 | spatial.SME.SME_normalize 129 | 130 | Tools: `tl` 131 | ------------------- 132 | 133 | .. module:: stlearn.tl.clustering 134 | .. currentmodule:: stlearn 135 | 136 | .. autosummary:: 137 | :toctree: . 138 | 139 | tl.clustering.kmeans 140 | tl.clustering.louvain 141 | 142 | 143 | .. module:: stlearn.tl.cci 144 | .. currentmodule:: stlearn 145 | 146 | .. autosummary:: 147 | :toctree: . 148 | 149 | tl.cci.load_lrs 150 | tl.cci.grid 151 | tl.cci.run 152 | tl.cci.adj_pvals 153 | tl.cci.run_lr_go 154 | tl.cci.run_cci 155 | 156 | Plot: `pl` 157 | ------------------- 158 | 159 | .. module:: stlearn.pl 160 | .. currentmodule:: stlearn 161 | 162 | .. autosummary:: 163 | :toctree: . 164 | 165 | pl.QC_plot 166 | pl.gene_plot 167 | pl.gene_plot_interactive 168 | pl.cluster_plot 169 | pl.cluster_plot_interactive 170 | pl.subcluster_plot 171 | pl.subcluster_plot 172 | pl.non_spatial_plot 173 | pl.deconvolution_plot 174 | pl.plot_mask 175 | pl.lr_summary 176 | pl.lr_diagnostics 177 | pl.lr_n_spots 178 | pl.lr_go 179 | pl.lr_result_plot 180 | pl.lr_plot 181 | pl.cci_check 182 | pl.ccinet_plot 183 | pl.lr_chord_plot 184 | pl.lr_cci_map 185 | pl.cci_map 186 | pl.lr_plot_interactive 187 | pl.spatialcci_plot_interactive 188 | 189 | .. module:: stlearn.pl.trajectory 190 | .. currentmodule:: stlearn 191 | 192 | .. autosummary:: 193 | :toctree: . 194 | 195 | pl.trajectory.pseudotime_plot 196 | pl.trajectory.local_plot 197 | pl.trajectory.tree_plot 198 | pl.trajectory.transition_markers_plot 199 | pl.trajectory.DE_transition_plot 200 | 201 | Tools: `datasets` 202 | ------------------- 203 | 204 | .. module:: stlearn.datasets 205 | .. currentmodule:: stlearn 206 | 207 | .. autosummary:: 208 | :toctree: . 209 | 210 | datasets.example_bcba() 211 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | stLearn - a downstream analysis toolkit for Spatial Transcriptomics data 4 | ============================================================================ 5 | 6 | |PyPI| |PyPIDownloads| |Docs| 7 | 8 | .. |PyPI| image:: https://img.shields.io/pypi/v/stlearn?logo=PyPI 9 | :target: https://pypi.org/project/stlearn 10 | .. |PyPIDownloads| image:: https://pepy.tech/badge/stlearn 11 | .. |Docs| image:: https://readthedocs.org/projects/stlearn/badge/?version=latest 12 | :target: https://stlearn.readthedocs.io 13 | 14 | 15 | 16 | .. image:: https://i.imgur.com/yfXlCYO.png 17 | :width: 300px 18 | :align: left 19 | 20 | stLearn is designed to comprehensively analyse Spatial Transcriptomics (ST) data to investigate complex biological processes within an undissociated tissue. ST is emerging as the “next generation” of single-cell RNA sequencing because it adds spatial and morphological context to the transcriptional profile of cells in an intact tissue section. However, existing ST analysis methods typically use the captured spatial and/or morphological data as a visualisation tool rather than as informative features for model development. We have developed an analysis method that exploits all three data types: Spatial distance, tissue Morphology, and gene Expression measurements (SME) from ST data. This combinatorial approach allows us to more accurately model underlying tissue biology, and allows researchers to address key questions in three major research areas: cell type identification, cell trajectory reconstruction, and the study of cell-cell interactions within an undissociated tissue sample. 21 | 22 | We also published stLearn-interactive which is a python-based interactive website for working with all the functions from stLearn and upgrade with some bokeh-based plots. 23 | 24 | To run the stlearn interaction webapp in your local, run: 25 | :: 26 | 27 | stlearn launch 28 | 29 | 30 | New features 31 | ********************** 32 | 33 | In the new release, we provide the interactive plots: 34 | 35 | .. image:: https://media.giphy.com/media/hUHAZcbVMm5pdUKMq4/giphy.gif 36 | :width: 600px 37 | 38 | 39 | 40 | Latest additions 41 | ---------------- 42 | 43 | .. include:: release_notes/0.4.11.rst 44 | 45 | .. include:: release_notes/0.4.6.rst 46 | 47 | .. include:: release_notes/0.3.2.rst 48 | 49 | 50 | 51 | 52 | .. toctree:: 53 | :maxdepth: 1 54 | :hidden: 55 | 56 | 57 | installation 58 | interactive 59 | tutorials 60 | api 61 | release_notes/index 62 | authors 63 | references 64 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Install by Anaconda 9 | --------------- 10 | 11 | **Step 1:** 12 | 13 | Prepare conda environment for stLearn 14 | :: 15 | 16 | conda create -n stlearn python=3.8 17 | conda activate stlearn 18 | 19 | **Step 2:** 20 | 21 | You can directly install stlearn in the anaconda by: 22 | :: 23 | 24 | conda install -c conda-forge stlearn 25 | 26 | Install by PyPi 27 | --------------- 28 | 29 | **Step 1:** 30 | 31 | Prepare conda environment for stLearn 32 | :: 33 | 34 | conda create -n stlearn python=3.8 35 | conda activate stlearn 36 | 37 | **Step 2:** 38 | 39 | Install stlearn using `pip` 40 | :: 41 | 42 | pip install -U stlearn 43 | 44 | 45 | 46 | Popular bugs 47 | --------------- 48 | 49 | - `DLL load failed while importing utilsextension: The specified module could not be found.` 50 | 51 | You need to uninstall package `tables` and install it again 52 | :: 53 | 54 | pip uninstall tables 55 | conda install pytables 56 | 57 | If conda version does not work, you can access to this site and download the .whl file: `https://www.lfd.uci.edu/~gohlke/pythonlibs/#pytables` 58 | 59 | :: 60 | 61 | pip install tables-3.7.0-cp38-cp38-win_amd64.whl 62 | -------------------------------------------------------------------------------- /docs/interactive.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Interactive web application 5 | ============ 6 | 7 | 8 | Launch stlearn in your local 9 | --------------- 10 | 11 | Run the launch command in the terminal: 12 | :: 13 | 14 | stlearn launch 15 | 16 | After that, you can access `https://:5000` in your web browser. 17 | 18 | Check the detail tutorial in this pdf file: `Link `_ 19 | -------------------------------------------------------------------------------- /docs/list_tutorial.txt: -------------------------------------------------------------------------------- 1 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/Pseudo-time-space-tutorial.ipynb 2 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/Read_MERFISH.ipynb 3 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/Read_seqfish.ipynb 4 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/Read_slideseq.ipynb 5 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/ST_deconvolution_visualization.ipynb 6 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/Working-with-Old-Spatial-Transcriptomics-data.ipynb 7 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/stLearn-CCI.ipynb 8 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/stSME_clustering.ipynb 9 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/stSME_comparison.ipynb 10 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/Xenium_PSTS.ipynb 11 | https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/master/tutorials/Xenium_CCI.ipynb 12 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=stlearn 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/references.rst: -------------------------------------------------------------------------------- 1 | References 2 | ---------- 3 | .. [Coifman05] Coifman *et al.* (2005), 4 | *Geometric diffusions as a tool for harmonic analysis and structure definition of data: Diffusion maps*, 5 | `PNAS `__. 6 | 7 | .. [Haghverdi15] Haghverdi *et al.* (2015), 8 | *Diffusion maps for high-dimensional single-cell analysis of differentiation data*, 9 | `Bioinformatics `__. 10 | 11 | .. [Haghverdi16] Haghverdi *et al.* (2016), 12 | *Diffusion pseudotime robustly reconstructs branching cellular lineages*, 13 | `Nature Methods `__. 14 | 15 | .. [Wolf18] Wolf *et al.* (2018), 16 | *Scanpy: large-scale single-cell gene expression data analysis*, 17 | `Genome Biology `__. 18 | 19 | .. [Pedregosa11] Pedregosa *et al.* (2011), 20 | *Scikit-learn: Machine Learning in Python*, 21 | `JMLR `__. 22 | 23 | .. [McInnes18] McInnes & Healy (2018), 24 | *UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction*, 25 | `arXiv `__. 26 | 27 | .. [Weinreb16] Weinreb *et al.* (2016), 28 | *SPRING: a kinetic interface for visualizing high dimensional single-cell expression data*, 29 | `bioRxiv `__. 30 | 31 | .. [Satija15] Satija *et al.* (2015), 32 | *Spatial reconstruction of single-cell gene expression data*, 33 | `Nature Biotechnology `__. 34 | 35 | .. [Zheng17] Zheng *et al.* (2017), 36 | *Massively parallel digital transcriptional profiling of single cells*, 37 | `Nature Communications `__. 38 | 39 | .. [Weinreb17] Weinreb *et al.* (2016), 40 | *SPRING: a kinetic interface for visualizing high dimensional single-cell expression data*, 41 | `bioRxiv `__. 42 | 43 | .. [Blondel08] Blondel *et al.* (2008), 44 | *Fast unfolding of communities in large networks*, 45 | `J. Stat. Mech. `__. 46 | 47 | .. [Levine15] Levine *et al.* (2015), 48 | *Data-Driven Phenotypic Dissection of AML Reveals Progenitor--like Cells that Correlate with Prognosis*, 49 | `Cell `__. 50 | 51 | .. [Traag17] (2017), 52 | *Louvain*, 53 | `GitHub `__. 54 | 55 | .. [Lambiotte09] Lambiotte *et al.* (2009) 56 | *Laplacian Dynamics and Multiscale Modular Structure in Networks* 57 | `arXiv `__. 58 | -------------------------------------------------------------------------------- /docs/release_notes/0.3.2.rst: -------------------------------------------------------------------------------- 1 | 0.3.2 `2021-03-29` 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | .. rubric:: Feature 5 | 6 | - Add interactive plotting functions: :func:`~stlearn.pl.gene_plot_interactive`, :func:`~stlearn.pl.cluster_plot_interactive`, :func:`~stlearn.pl.het_plot_interactive` 7 | - Add basic unittest (will add more in the future). 8 | - Add `'contour'` parameter to use contour plot in :func:`~stlearn.pl.gene_plot` and :func:`~stlearn.pl.het_plot`. 9 | - Add :func:`~stlearn.convert_scanpy` to convert object from scanpy to stLearn. 10 | 11 | .. rubric:: Bug fixes 12 | 13 | - Refactor :func:`~stlearn.pl.gene_plot` 14 | - Refactor :func:`~stlearn.pl.cluster_plot` 15 | - Refactor :func:`~stlearn.pl.het_plot` 16 | - Fixed issue with `networkx` object cannot write `h5ad` file. 17 | -------------------------------------------------------------------------------- /docs/release_notes/0.4.6.rst: -------------------------------------------------------------------------------- 1 | 0.4.0 `2022-02-03` 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | .. rubric:: Feature 5 | 6 | - Upgrade stSME, PSTS and CCI analysis methods. 7 | 8 | - Add CCI analysis functions: :func:`~stlearn.tl.cci.load_lrs`, :func:`~stlearn.tl.cci.grid`, :func:`~stlearn.tl.cci.run`, :func:`~stlearn.tl.cci.adj_pvals`, :func:`~stlearn.tl.cci.run_lr_go`, :func:`~stlearn.pl.lr_summary`, :func:`~stlearn.pl.lr_diagnostics`, :func:`~stlearn.pl.lr_n_spots`, :func:`~stlearn.pl.lr_go`, :func:`~stlearn.pl.lr_result_plot`, :func:`~stlearn.pl.lr_plot`, :func:`~stlearn.pl.cci_check`, :func:`~stlearn.pl.ccinet_plot`, :func:`~stlearn.pl.lr_chord_plot`, :func:`~stlearn.pl.lr_cci_map`, :func:`~stlearn.pl.cci_map`, :func:`~stlearn.pl.lr_plot_interactive`, :func:`~stlearn.pl.spatialcci_plot_interactive` 9 | -------------------------------------------------------------------------------- /docs/release_notes/index.rst: -------------------------------------------------------------------------------- 1 | Release notes 2 | =================================================== 3 | 4 | Version 0.4.9 5 | --------------------------- 6 | 7 | .. include:: 0.4.10.rst 8 | 9 | .. include:: 0.4.6.rst 10 | 11 | .. include:: 0.3.2.rst 12 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | -r ../requirements.txt 2 | ipyvolume 3 | ipywebrtc 4 | ipywidgets 5 | jupyter_sphinx 6 | nbclean 7 | nbformat 8 | nbsphinx 9 | pygments 10 | recommonmark 11 | sphinx 12 | sphinx-autodoc-typehints 13 | sphinx_gallery==0.10.1 14 | sphinx_rtd_theme 15 | typing_extensions 16 | -------------------------------------------------------------------------------- /docs/tutorials.rst: -------------------------------------------------------------------------------- 1 | Tutorials 2 | =========================== 3 | 4 | .. nbgallery:: 5 | :caption: Main features: 6 | 7 | tutorials/stSME_clustering 8 | tutorials/stSME_comparison 9 | tutorials/Pseudo-time-space-tutorial 10 | tutorials/stLearn-CCI 11 | tutorials/Xenium_PSTS 12 | tutorials/Xenium_CCI 13 | 14 | .. nbgallery:: 15 | :caption: Visualisation and additional functionalities: 16 | 17 | tutorials/Interactive_plot 18 | tutorials/Core_plots 19 | tutorials/ST_deconvolution_visualization 20 | tutorials/Integration_multiple_datasets 21 | 22 | 23 | .. nbgallery:: 24 | :caption: Supporting platforms: 25 | 26 | 27 | tutorials/Read_MERFISH 28 | tutorials/Read_seqfish 29 | tutorials/Working-with-Old-Spatial-Transcriptomics-data 30 | tutorials/Read_slideseq 31 | 32 | .. nbgallery:: 33 | :caption: Integration with other spatial tools: 34 | 35 | tutorials/Read_any_data 36 | tutorials/Working_with_scanpy 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bokeh>= 2.4.2 2 | click>=8.0.4 3 | leidenalg 4 | louvain 5 | numba<=0.57.1 6 | numpy>=1.18,<1.22 7 | Pillow>=9.0.1 8 | scanpy>=1.8.2 9 | scikit-image>=0.19.2 10 | tensorflow 11 | -------------------------------------------------------------------------------- /resource/README.md: -------------------------------------------------------------------------------- 1 | **This folder** contains different stlearn tutorials for performing different analysis tasks of spatial transcriptomics data. The tutorials are being actively updated. The official tutorial versions will be hosted in another site linked to this repository. 2 | -------------------------------------------------------------------------------- /resource/connectomedb2020.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/resource/connectomedb2020.txt -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.4.11 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:stlearn/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | # Define setup.py command aliases here 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open("README.md", encoding="utf8") as readme_file: 8 | readme = readme_file.read() 9 | 10 | with open("HISTORY.rst") as history_file: 11 | history = history_file.read() 12 | 13 | with open("requirements.txt") as f: 14 | requirements = f.read().splitlines() 15 | 16 | 17 | setup_requirements = [] 18 | 19 | test_requirements = [] 20 | 21 | setup( 22 | author="Genomics and Machine Learning lab", 23 | author_email="duy.pham@uq.edu.au", 24 | python_requires=">=3.7", 25 | classifiers=[ 26 | "Development Status :: 2 - Pre-Alpha", 27 | "Intended Audience :: Developers", 28 | "License :: OSI Approved :: BSD License", 29 | "Natural Language :: English", 30 | "Programming Language :: Python :: 3", 31 | "Programming Language :: Python :: 3.7", 32 | "Programming Language :: Python :: 3.8", 33 | ], 34 | description="A downstream analysis toolkit for Spatial Transcriptomic data", 35 | entry_points={ 36 | "console_scripts": [ 37 | "stlearn=stlearn.app.cli:main", 38 | ], 39 | }, 40 | install_requires=requirements, 41 | license="BSD license", 42 | long_description=readme + "\n\n" + history, 43 | long_description_content_type="text/markdown", 44 | include_package_data=True, 45 | keywords="stlearn", 46 | name="stlearn", 47 | packages=find_packages(include=["stlearn", "stlearn.*"]), 48 | setup_requires=setup_requirements, 49 | test_suite="tests", 50 | tests_require=test_requirements, 51 | url="https://github.com/BiomedicalMachineLearning/stLearn", 52 | version="0.4.11", 53 | zip_safe=False, 54 | ) 55 | -------------------------------------------------------------------------------- /stlearn/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for stLearn.""" 2 | 3 | __author__ = """Genomics and Machine Learning lab""" 4 | __email__ = "duy.pham@uq.edu.au" 5 | __version__ = "0.4.11" 6 | 7 | 8 | from . import add 9 | from . import pp 10 | from . import em 11 | from . import tl 12 | from . import pl 13 | from . import spatial 14 | from . import datasets 15 | 16 | # Wrapper 17 | 18 | from .wrapper.read import ReadSlideSeq 19 | from .wrapper.read import Read10X 20 | from .wrapper.read import ReadOldST 21 | from .wrapper.read import ReadMERFISH 22 | from .wrapper.read import ReadSeqFish 23 | from .wrapper.read import ReadXenium 24 | from .wrapper.read import create_stlearn 25 | 26 | from ._settings import settings 27 | from .wrapper.convert_scanpy import convert_scanpy 28 | from .wrapper.concatenate_spatial_adata import concatenate_spatial_adata 29 | 30 | # from . import cli 31 | -------------------------------------------------------------------------------- /stlearn/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Package entry point.""" 4 | 5 | 6 | from stlearn.app import main 7 | 8 | 9 | if __name__ == "__main__": # pragma: no cover 10 | main() 11 | -------------------------------------------------------------------------------- /stlearn/_compat.py: -------------------------------------------------------------------------------- 1 | try: 2 | from typing import Literal 3 | except ImportError: 4 | try: 5 | from typing_extensions import Literal 6 | except ImportError: 7 | 8 | class LiteralMeta(type): 9 | def __getitem__(cls, values): 10 | if not isinstance(values, tuple): 11 | values = (values,) 12 | return type("Literal_", (Literal,), dict(__args__=values)) 13 | 14 | class Literal(metaclass=LiteralMeta): 15 | pass 16 | -------------------------------------------------------------------------------- /stlearn/_datasets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/_datasets/__init__.py -------------------------------------------------------------------------------- /stlearn/_datasets/_datasets.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | from .._settings import settings 3 | from pathlib import Path 4 | from anndata import AnnData 5 | 6 | 7 | def example_bcba() -> AnnData: 8 | """\ 9 | Download processed BCBA data (10X genomics published data). 10 | Reference: https://support.10xgenomics.com/spatial-gene-expression/datasets/1.1.0/V1_Breast_Cancer_Block_A_Section_1 11 | """ 12 | settings.datasetdir.mkdir(exist_ok=True) 13 | filename = settings.datasetdir / "example_bcba.h5" 14 | url = "https://www.dropbox.com/s/u3m2f16mvdom1am/example_bcba.h5ad?dl=1" 15 | if not filename.is_file(): 16 | sc.readwrite._download(url=url, path=filename) 17 | adata = sc.read_h5ad(filename) 18 | return adata 19 | -------------------------------------------------------------------------------- /stlearn/add.py: -------------------------------------------------------------------------------- 1 | from .adds.add_image import image 2 | from .adds.add_positions import positions 3 | from .adds.parsing import parsing 4 | from .adds.add_lr import lr 5 | from .adds.annotation import annotation 6 | from .adds.add_labels import labels 7 | from .adds.add_deconvolution import add_deconvolution 8 | from .adds.add_mask import add_mask 9 | from .adds.add_mask import apply_mask 10 | from .adds.add_loupe_clusters import add_loupe_clusters 11 | -------------------------------------------------------------------------------- /stlearn/adds/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/adds/__init__.py -------------------------------------------------------------------------------- /stlearn/adds/add_deconvolution.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | import pandas as pd 4 | import numpy as np 5 | from pathlib import Path 6 | 7 | 8 | def add_deconvolution( 9 | adata: AnnData, 10 | annotation_path: Union[Path, str], 11 | copy: bool = False, 12 | ) -> Optional[AnnData]: 13 | 14 | """\ 15 | Adding label transfered from Seurat 16 | 17 | Parameters 18 | ---------- 19 | adata 20 | Annotated data matrix. 21 | annotation_path 22 | Path of the output of label transfer result by Seurat 23 | copy 24 | Return a copy instead of writing to adata. 25 | Returns 26 | ------- 27 | Depending on `copy`, returns or updates `adata` with the following fields. 28 | **[cluster method name]_anno** : `adata.obs` field 29 | The annotation of cluster results. 30 | 31 | """ 32 | label = pd.read_csv(annotation_path, index_col=0) 33 | label = label[adata.obs_names] 34 | 35 | adata.obsm["deconvolution"] = label[adata.obs.index].T 36 | -------------------------------------------------------------------------------- /stlearn/adds/add_image.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | from matplotlib import pyplot as plt 4 | from pathlib import Path 5 | import os 6 | from PIL import Image 7 | 8 | Image.MAX_IMAGE_PIXELS = None 9 | 10 | 11 | def image( 12 | adata: AnnData, 13 | imgpath: Union[Path, str], 14 | library_id: str, 15 | quality: str = "hires", 16 | scale: float = 1.0, 17 | visium: bool = False, 18 | spot_diameter_fullres: float = 50, 19 | copy: bool = False, 20 | ) -> Optional[AnnData]: 21 | 22 | """\ 23 | Adding image data to the Anndata object 24 | 25 | Parameters 26 | ---------- 27 | adata 28 | Annotated data matrix. 29 | imgpath 30 | Image path. 31 | library_id 32 | Identifier for the visium library. Can be modified when concatenating multiple adata objects. 33 | scale 34 | Set scale factor. 35 | quality 36 | Set quality that convert to stlearn to use. Store in anndata.obs['imagecol' & 'imagerow']. 37 | visium 38 | Is this anndata read from Visium platform or not. 39 | copy 40 | Return a copy instead of writing to adata. 41 | Returns 42 | ------- 43 | Depending on `copy`, returns or updates `adata` with the following fields. 44 | **tissue_img** : `adata.uns` field 45 | Array format of image, saving by Pillow package. 46 | """ 47 | 48 | if imgpath is not None and os.path.isfile(imgpath): 49 | try: 50 | img = plt.imread(imgpath, 0) 51 | 52 | if visium: 53 | adata.uns["spatial"][library_id]["images"][quality] = img 54 | else: 55 | adata.uns["spatial"] = {} 56 | adata.uns["spatial"][library_id] = {} 57 | adata.uns["spatial"][library_id]["images"] = {} 58 | adata.uns["spatial"][library_id]["images"][quality] = img 59 | adata.uns["spatial"][library_id]["use_quality"] = quality 60 | adata.uns["spatial"][library_id]["scalefactors"] = {} 61 | adata.uns["spatial"][library_id]["scalefactors"][ 62 | "tissue_" + quality + "_scalef" 63 | ] = scale 64 | adata.uns["spatial"][library_id]["scalefactors"][ 65 | "spot_diameter_fullres" 66 | ] = spot_diameter_fullres 67 | adata.obsm["spatial"] = adata.obs[["imagecol", "imagerow"]].values 68 | adata.obs[["imagecol", "imagerow"]] = adata.obsm["spatial"] * scale 69 | 70 | print("Added tissue image to the object!") 71 | 72 | return adata if copy else None 73 | except: 74 | raise ValueError( 75 | f"""\ 76 | {imgpath!r} does not end on a valid extension. 77 | """ 78 | ) 79 | else: 80 | raise ValueError( 81 | f"""\ 82 | {imgpath!r} does not end on a valid extension. 83 | """ 84 | ) 85 | return adata if copy else None 86 | -------------------------------------------------------------------------------- /stlearn/adds/add_labels.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | from pathlib import Path 4 | import os 5 | import pandas as pd 6 | import numpy as np 7 | from natsort import natsorted 8 | 9 | 10 | def labels( 11 | adata: AnnData, 12 | label_filepath: str = None, 13 | index_col: int = 0, 14 | use_label: str = None, 15 | sep: str = "\t", 16 | copy: bool = False, 17 | ) -> Optional[AnnData]: 18 | """\ 19 | Add label transfer results into AnnData object 20 | 21 | Parameters 22 | ---------- 23 | adata: AnnData The data object to add L-R info into 24 | label_filepath: str The path to the label transfer results file 25 | use_label: str Where to store the label_transfer results, defaults to 'predictions' in adata.obs & 'label_transfer' in adata.uns. 26 | sep: str Separator of the csv file 27 | copy: bool Copy flag indicating copy or direct edit 28 | 29 | Returns 30 | ------- 31 | adata: AnnData The data object that L-R added into 32 | 33 | """ 34 | labels = pd.read_csv(label_filepath, index_col=index_col, sep=sep) 35 | uns_key = "label_transfer" if type(use_label) == type(None) else use_label 36 | adata.uns[uns_key] = labels.drop(["predicted.id", "prediction.score.max"], axis=1) 37 | 38 | key_add = "predictions" if type(use_label) == type(None) else use_label 39 | key_source = "predicted.id" 40 | adata.obs[key_add] = pd.Categorical( 41 | values=np.array(labels[key_source]).astype("U"), 42 | categories=natsorted(labels[key_source].unique().astype("U")), 43 | ) 44 | 45 | print(f"label transfer results added to adata.uns['{uns_key}']") 46 | print(f"predicted label added to adata.obs['{key_add}'].") 47 | 48 | return adata if copy else None 49 | -------------------------------------------------------------------------------- /stlearn/adds/add_loupe_clusters.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | import pandas as pd 4 | import numpy as np 5 | import stlearn 6 | from pathlib import Path 7 | from natsort import natsorted 8 | 9 | 10 | def add_loupe_clusters( 11 | adata: AnnData, 12 | loupe_path: Union[Path, str], 13 | key_add: str = "multiplex", 14 | copy: bool = False, 15 | ) -> Optional[AnnData]: 16 | 17 | """\ 18 | Adding label transfered from Seurat 19 | 20 | Parameters 21 | ---------- 22 | adata 23 | Annotated data matrix. 24 | annotation_path 25 | Path of the output of label transfer result by Seurat 26 | use_label 27 | Choosing cluster type. 28 | threshold 29 | Quantile threshold of label 30 | copy 31 | Return a copy instead of writing to adata. 32 | Returns 33 | ------- 34 | Depending on `copy`, returns or updates `adata` with the following fields. 35 | **[cluster method name]_anno** : `adata.obs` field 36 | The annotation of cluster results. 37 | 38 | """ 39 | label = pd.read_csv(loupe_path) 40 | 41 | adata.obs[key_add] = pd.Categorical( 42 | values=np.array(label[key_add]).astype("U"), 43 | categories=natsorted(label[key_add].unique().astype("U")), 44 | ) 45 | -------------------------------------------------------------------------------- /stlearn/adds/add_lr.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | from pathlib import Path 4 | import os 5 | import pandas as pd 6 | 7 | 8 | def lr( 9 | adata: AnnData, 10 | db_filepath: str = None, 11 | sep: str = "\t", 12 | source: str = "connectomedb", 13 | copy: bool = False, 14 | ) -> Optional[AnnData]: 15 | """Add significant Ligand-Receptor pairs into AnnData object 16 | 17 | Parameters 18 | ---------- 19 | adata: AnnData The data object to add L-R info into 20 | db_filepath: str The path to the CPDB results file 21 | sep: str Separator of the CPDB results file 22 | source: str Source of LR database (default: connectomedb, can also support 'cellphonedb') 23 | copy: bool Copy flag indicating copy or direct edit 24 | 25 | Returns 26 | ------- 27 | adata: AnnData The data object that L-R added into 28 | 29 | """ 30 | 31 | if source == "cellphonedb": 32 | cpdb = pd.read_csv(db_filepath, sep=sep) 33 | adata.uns["cpdb"] = cpdb 34 | lr = cpdb["interacting_pair"].to_list() 35 | lr2 = [i for i in lr if "complex" not in i] 36 | lr3 = [i for i in lr2 if " " not in i] 37 | lr4 = [i for i in lr3 if i.count("_") == 1] 38 | adata.uns["lr"] = lr4 39 | print("cpdb results added to adata.uns['cpdb']") 40 | print("Added ligand receptor pairs to adata.uns['lr'].") 41 | 42 | elif source == "connectomedb": 43 | ctdb = pd.read_csv(db_filepath, sep=sep, quotechar='"', encoding="latin1") 44 | adata.uns["lr"] = ( 45 | ctdb["Ligand gene symbol"] + "_" + ctdb["Receptor gene symbol"] 46 | ).values.tolist() 47 | print("connectomedb results added to adata.uns['ctdb']") 48 | print("Added ligand receptor pairs to adata.uns['lr'].") 49 | 50 | return adata if copy else None 51 | -------------------------------------------------------------------------------- /stlearn/adds/add_positions.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | import pandas as pd 4 | from pathlib import Path 5 | import os 6 | 7 | 8 | def positions( 9 | adata: AnnData, 10 | position_filepath: Union[Path, str] = None, 11 | scale_filepath: Union[Path, str] = None, 12 | quality: str = "low", 13 | copy: bool = False, 14 | ) -> Optional[AnnData]: 15 | 16 | """\ 17 | Adding spatial information into the Anndata object 18 | 19 | Parameters 20 | ---------- 21 | adata 22 | Annotated data matrix. 23 | position_filepath 24 | Path to tissue_positions_list file. 25 | scale_filepath 26 | Path to scalefactors_json file. 27 | quality 28 | Choosing low or high resolution image. 29 | copy 30 | Return a copy instead of writing to adata. 31 | Returns 32 | ------- 33 | Depending on `copy`, returns or updates `adata` with the following fields. 34 | **imagecol** and **imagerow** : `adata.obs` field 35 | Spatial information of the tissue image. 36 | """ 37 | 38 | tissue_positions = pd.read_csv(position_filepath, header=None) 39 | tissue_positions.columns = [ 40 | "barcode", 41 | "tissue", 42 | "row", 43 | "col", 44 | "imagerow", 45 | "imagecol", 46 | ] 47 | import json 48 | 49 | with open(scale_filepath) as json_file: 50 | scale_factors = json.load(json_file) 51 | 52 | if quality == "low": 53 | tissue_positions["imagerow"] = ( 54 | tissue_positions["imagerow"] * scale_factors["tissue_lowres_scalef"] 55 | ) 56 | tissue_positions["imagecol"] = ( 57 | tissue_positions["imagecol"] * scale_factors["tissue_lowres_scalef"] 58 | ) 59 | elif quality == "high": 60 | tissue_positions["imagerow"] = ( 61 | tissue_positions["imagerow"] * scale_factors["tissue_hires_scalef"] 62 | ) 63 | tissue_positions["imagecol"] = ( 64 | tissue_positions["imagecol"] * scale_factors["tissue_hires_scalef"] 65 | ) 66 | 67 | tmp = adata.obs.merge( 68 | tissue_positions.reset_index().set_index(["barcode"]), 69 | left_index=True, 70 | right_index=True, 71 | how="left", 72 | ).reset_index()[["imagerow", "imagecol"]] 73 | 74 | adata.obs["imagerow"] = tmp["imagerow"].values 75 | adata.obs["imagecol"] = tmp["imagecol"].values 76 | 77 | return adata if copy else None 78 | -------------------------------------------------------------------------------- /stlearn/adds/annotation.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union, List 2 | from anndata import AnnData 3 | from matplotlib import pyplot as plt 4 | from pathlib import Path 5 | import os 6 | 7 | 8 | def annotation( 9 | adata: AnnData, 10 | label_list: List[str], 11 | use_label: str = "louvain", 12 | copy: bool = False, 13 | ) -> Optional[AnnData]: 14 | """\ 15 | Adding annotation for cluster 16 | 17 | Parameters 18 | ---------- 19 | adata 20 | Annotated data matrix. 21 | label_list 22 | List of the labels which assigned to current cluster result. 23 | use_label 24 | Choosing cluster type. 25 | copy 26 | Return a copy instead of writing to adata. 27 | Returns 28 | ------- 29 | Depending on `copy`, returns or updates `adata` with the following fields. 30 | **[cluster method name]_anno** : `adata.obs` field 31 | The annotation of cluster results. 32 | """ 33 | 34 | if label_list is None: 35 | raise ValueError("Please give the label list!") 36 | 37 | if len(label_list) != len(adata.obs[use_label].unique()): 38 | raise ValueError("Please give the correct number of label list!") 39 | 40 | adata.obs[use_label + "_anno"] = adata.obs[use_label].cat.rename_categories( 41 | label_list 42 | ) 43 | 44 | print("The annotation is added to adata.obs['" + use_label + "_anno" + "']") 45 | 46 | return adata if copy else None 47 | -------------------------------------------------------------------------------- /stlearn/adds/parsing.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | from matplotlib import pyplot as plt 4 | from pathlib import Path 5 | import os 6 | import sys 7 | import numpy as np 8 | 9 | 10 | def parsing( 11 | adata: AnnData, 12 | coordinates_file: Union[Path, str], 13 | copy: bool = True, 14 | ) -> Optional[AnnData]: 15 | 16 | """\ 17 | Parsing the old spaital transcriptomics data 18 | 19 | Parameters 20 | ---------- 21 | adata 22 | Annotated data matrix. 23 | coordinates_file 24 | Coordinate file generated by st_spot_detector. 25 | copy 26 | Return a copy instead of writing to adata. 27 | Returns 28 | ------- 29 | Depending on `copy`, returns or updates `adata` with the following fields. 30 | **imagecol** and **imagerow** : `adata.obs` field 31 | Spatial information of the tissue image. 32 | """ 33 | 34 | # Get a map of the new coordinates 35 | new_coordinates = dict() 36 | with open(coordinates_file, "r") as filehandler: 37 | for line in filehandler.readlines(): 38 | tokens = line.split() 39 | assert len(tokens) >= 6 or len(tokens) == 4 40 | if tokens[0] != "x": 41 | old_x = int(tokens[0]) 42 | old_y = int(tokens[1]) 43 | new_x = round(float(tokens[2]), 2) 44 | new_y = round(float(tokens[3]), 2) 45 | if len(tokens) >= 6: 46 | pixel_x = float(tokens[4]) 47 | pixel_y = float(tokens[5]) 48 | new_coordinates[(old_x, old_y)] = (pixel_x, pixel_y) 49 | else: 50 | raise ValueError( 51 | "Error, output format is pixel coordinates but\n " 52 | "the coordinates file only contains 4 columns\n" 53 | ) 54 | 55 | counts_table = adata.to_df() 56 | new_index_values = list() 57 | 58 | imgcol = [] 59 | imgrow = [] 60 | for index in counts_table.index: 61 | tokens = index.split("x") 62 | x = int(tokens[0]) 63 | y = int(tokens[1]) 64 | try: 65 | new_x, new_y = new_coordinates[(x, y)] 66 | imgcol.append(new_x) 67 | imgrow.append(new_y) 68 | 69 | new_index_values.append("{0}x{1}".format(new_x, new_y)) 70 | except KeyError: 71 | counts_table.drop(index, inplace=True) 72 | 73 | # Assign the new indexes 74 | # counts_table.index = new_index_values 75 | 76 | # Remove genes that have now a total count of zero 77 | counts_table = counts_table.transpose()[counts_table.sum(axis=0) > 0].transpose() 78 | 79 | adata = AnnData(counts_table) 80 | 81 | adata.obs["imagecol"] = imgcol 82 | adata.obs["imagerow"] = imgrow 83 | 84 | adata.obsm["spatial"] = np.c_[[imgcol, imgrow]].reshape(-1, 2) 85 | 86 | return adata if copy else None 87 | -------------------------------------------------------------------------------- /stlearn/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/app/__init__.py -------------------------------------------------------------------------------- /stlearn/app/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | from .. import __version__ 3 | 4 | import os 5 | 6 | 7 | @click.group( 8 | name="stlearn", 9 | subcommand_metavar="COMMAND ", 10 | options_metavar="", 11 | context_settings=dict(max_content_width=85, help_option_names=["-h", "--help"]), 12 | ) 13 | @click.help_option("--help", "-h", help="Show this message and exit.") 14 | @click.version_option( 15 | version=__version__, 16 | prog_name="stlearn", 17 | message="[%(prog)s] Version %(version)s", 18 | help="Show the software version and exit.", 19 | ) 20 | def main(): 21 | os._exit 22 | click.echo("Please run `stlearn launch` to start the web app") 23 | 24 | 25 | @main.command(short_help="Launch the stlearn interactive app") 26 | def launch(): 27 | from .app import app 28 | 29 | try: 30 | app.run(host="0.0.0.0", port=5000, debug=True, use_reloader=False) 31 | except OSError as e: 32 | if e.errno == errno.EADDRINUSE: 33 | raise click.ClickException( 34 | "Port is in use, please specify an open port using the --port flag." 35 | ) from e 36 | raise 37 | -------------------------------------------------------------------------------- /stlearn/app/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.3.2 2 | flask_wtf==1.0.0 3 | markupsafe==2.1.0 4 | WTForms==3.0.1 5 | -------------------------------------------------------------------------------- /stlearn/app/source/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/app/source/__init__.py -------------------------------------------------------------------------------- /stlearn/app/source/forms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/app/source/forms/__init__.py -------------------------------------------------------------------------------- /stlearn/app/source/forms/form_validators.py: -------------------------------------------------------------------------------- 1 | """ Contains different kinds of form validators. 2 | """ 3 | from wtforms.validators import ValidationError 4 | 5 | 6 | class CheckNumberRange(object): 7 | def __init__(self, lower, upper, hint=""): 8 | self.lower = lower 9 | self.upper = upper 10 | self.hint = hint 11 | 12 | def __call__(self, form, field): 13 | 14 | if field.data is not None: 15 | if not (self.lower <= float(field.data) <= self.upper): 16 | if self.hint: 17 | raise ValidationError(self.hint) 18 | else: 19 | raise ValidationError("Not in correct range") 20 | -------------------------------------------------------------------------------- /stlearn/app/source/forms/helper_functions.py: -------------------------------------------------------------------------------- 1 | # Purpose of this script is to write the functions that help facilitate 2 | # subsetting of the data depending on the users input 3 | import numpy 4 | 5 | 6 | def printOut(text, fileName="stdout.txt", close=True, file=None): 7 | """Prints to the specified file name. Used for debugging. 8 | If close is Fale, returns open file. 9 | """ 10 | 11 | if type(file) == type(None): 12 | file = open(fileName, "w") 13 | 14 | print(text, file=file) 15 | 16 | if close: 17 | file.close() 18 | else: 19 | return file 20 | 21 | 22 | def filterOptions(metaDataSets, options): 23 | """Returns options that overlap with keys in metaDataSets dictionary""" 24 | if type(options) == type(None): 25 | options = list(metaDataSets.keys()) 26 | else: 27 | options = [option for option in options if option in metaDataSets.keys()] 28 | 29 | return options 30 | 31 | 32 | def addChoices(metaDataSets, options, elementValues): 33 | """Helper function which generates choices for SelectMultiField""" 34 | for option in options: 35 | choices = [(optioni, optioni) for optioni in metaDataSets[option]] 36 | elementValues.append(choices) 37 | 38 | 39 | # TODO update this so has 'options' as input 40 | def subsetSCA(sca, subsetForm): 41 | """Subsets the SCA based on the selected fields and the inputted genes.""" 42 | 43 | # Getting the attached fields from the form which refer subset options # 44 | options = filterOptions(sca.metaDataSets, subsetForm.elements) 45 | 46 | # Subsetting based on selection # 47 | conditionSelection = {} # selection dictionary 48 | for i, option in enumerate(options): 49 | selected = getattr(subsetForm, option).data 50 | if len(selected) != 0: 51 | conditionSelection[option] = selected 52 | 53 | # Subsetting based on conditions # 54 | if len(conditionSelection) != 0: 55 | sca = sca.createConditionSubset("subset", conditionSelection) 56 | 57 | # Subsetting based on inputted genes # 58 | geneList = getattr(subsetForm, "Select Cells Expressing Gene/s").data.split(",") 59 | if geneList != [""]: 60 | # Filter to just the genes which express all of the inputted genes # 61 | sca = sca.createGeneExprsSubset( 62 | "subset", genesToFilter=geneList, cutoff=0, keep=True, useOr=False 63 | ) 64 | 65 | return sca, conditionSelection, geneList 66 | -------------------------------------------------------------------------------- /stlearn/app/source/forms/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Helper utilities and decorators.""" 3 | from flask import flash 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | def flash_errors(form, category="warning"): 8 | """Flash all errors for a form.""" 9 | for field, errors in form.errors.items(): 10 | for error in errors: 11 | flash(getattr(form, field).label.text + " - " + error + ", category") 12 | 13 | 14 | def get_all_paths(adata): 15 | 16 | import networkx as nx 17 | 18 | G = nx.from_numpy_array(adata.uns["paga"]["connectivities_tree"].toarray()) 19 | mapping = {int(k): v for k, v in zip(G.nodes, adata.obs.clusters.cat.categories)} 20 | G = nx.relabel_nodes(G, mapping) 21 | 22 | all_paths = [] 23 | for source in G.nodes: 24 | for target in G.nodes: 25 | paths = nx.all_simple_paths(G, source=source, target=target) 26 | for path in paths: 27 | all_paths.append(path) 28 | 29 | import numpy as np 30 | 31 | all_paths = list(map(lambda x: " - ".join(np.array(x).astype(str)), all_paths)) 32 | 33 | return all_paths 34 | -------------------------------------------------------------------------------- /stlearn/app/source/forms/view_helpers.py: -------------------------------------------------------------------------------- 1 | """ Helper functions for views.py. 2 | """ 3 | 4 | import numpy 5 | 6 | 7 | def getVal(form, element): 8 | return getattr(form, element).data 9 | 10 | 11 | def getData(form): 12 | """Retrieves the data from the form and places into dictionary.""" 13 | params = {} 14 | form_elements = form.elements 15 | form_fields = form.element_fields 16 | for i, element in enumerate(form_elements): 17 | if form_fields[i] != "Title": 18 | data = getVal(form, element) 19 | params[element] = data 20 | return params 21 | 22 | 23 | def getLR(lr_input, gene_names): 24 | """Returns list of lr_inputs and error message, if any.""" 25 | if lr_input == "": 26 | return None, "ERROR: LR pairs required input." 27 | 28 | try: 29 | lrs = [lr.strip(" ") for lr in lr_input.split(",")] 30 | absent_genes = [] 31 | for lr in lrs: 32 | genes = lr.split("_") 33 | absent_genes.extend([gene for gene in genes if gene not in gene_names]) 34 | 35 | if len(absent_genes) != 0: 36 | return None, f"ERROR: inputted genes not found {absent_genes}." 37 | 38 | return lrs, "" 39 | 40 | except: 41 | return None, "ERROR: LR pairs misformatted." 42 | -------------------------------------------------------------------------------- /stlearn/app/source/readme.md: -------------------------------------------------------------------------------- 1 | # Pre-processing Form Design Notes 2 | Flow of data: 3 | 4 | app.py/preprocessing() 5 | -> source/forms/views.py/run_preprocessing(request, adata, step_log) 6 | -> source/forms/forms.py/getPreprocessForm() 7 | -> templates/preprocessing.html 8 | -> templates/superform.html 9 | 10 | Notes: 11 | 12 | * step_log defined in app.py, keeps track whether pre-processing was run. 13 | -> If not run, then run_preprocessing shows just the form. 14 | -> If has run, shows banner that preprocessing complete. 15 | 16 | * If attempt to run_preprocessing() when adata not yet loaded, shows banner 17 | indicating need to upload the data first. 18 | 19 | * source/forms/forms.py/getPreprocessForm() generates a WTForm class using a 20 | general WTForm generator, as defined in: 21 | source/forms/forms.py/createSuperForm() 22 | 23 | * templates/preprocessing.html is the preprocessing page, which also injects 24 | in a form to display using templates/superform.html. 25 | 26 | * templates/superform.html renders a general WTForm that was created using 27 | the source/forms/forms.py/createSuperForm() function, thereby allowing 28 | easy generation of new forms if need to add extra information. 29 | -------------------------------------------------------------------------------- /stlearn/app/static/css/style.css: -------------------------------------------------------------------------------- 1 | .container-fluid { 2 | width: 80%; 3 | padding-right: 15px; 4 | padding-left: 15px; 5 | margin-right: auto; 6 | margin-left: auto; 7 | } 8 | 9 | .right { 10 | float: right; 11 | width: 300px; 12 | border: 3px solid #73AD21; 13 | padding: 10px; 14 | } 15 | 16 | .card { 17 | border: 0; 18 | margin-bottom: 30px; 19 | margin-top: 10px; 20 | border-radius: 6px; 21 | color: #333; 22 | background: #fff; 23 | width: 100%; 24 | box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 20%), 0 1px 5px 0 rgb(0 0 0 / 12%); 25 | } 26 | 27 | .main-panel>.content { 28 | margin-top: 0px; 29 | padding: 30px 15px; 30 | min-height: calc(100vh - 123px); 31 | } 32 | 33 | a.disabled, fieldset:disabled a { 34 | pointer-events: none; 35 | background-color: #D8D8D8; 36 | } 37 | 38 | 39 | .list-group-item { 40 | position: relative; 41 | display: block; 42 | padding: 0.25rem 1.25rem; 43 | margin-bottom: 0; 44 | background-color: inherit; 45 | border: 0 solid rgba(0,0,0,.125); 46 | } 47 | 48 | #Cluster\ Select { 49 | height: 150px 50 | } 51 | 52 | #overlay { width:100px; 53 | height: 100px; 54 | position: fixed; 55 | top: 50%; 56 | left: 50%; 57 | z-index: -1; 58 | } 59 | 60 | #loading { width:100px; 61 | height: 200px; 62 | position: fixed; 63 | top: 50%; 64 | left: 50%; 65 | } 66 | 67 | .btn, .btn.btn-default { 68 | color: #fff; 69 | background-color: #9124a3; 70 | border-color: #9c27b0; 71 | box-shadow: 0 2px 2px 0 hsl(0deg 0% 60% / 14%), 0 3px 1px -2px hsl(0deg 0% 60% / 20%), 0 1px 5px 0 hsl(0deg 0% 60% / 12%); 72 | } 73 | 74 | .logging_window{ 75 | display: block; 76 | padding: 9.5px; 77 | font-size: 13px; 78 | line-height: 1.42857143; 79 | color: #333; 80 | word-break: break-all; 81 | word-wrap: break-word; 82 | background-color: #f5f5f5; 83 | border: 1px solid #ccc; 84 | border-radius: 4px; 85 | width: 50%; 86 | margin: auto; 87 | } 88 | 89 | .sidebar { 90 | width: 285px; 91 | } 92 | 93 | .sidebar .sidebar-wrapper { 94 | width: 285px; 95 | } 96 | -------------------------------------------------------------------------------- /stlearn/app/static/img/Settings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/app/static/img/Settings.gif -------------------------------------------------------------------------------- /stlearn/app/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/app/static/img/favicon.png -------------------------------------------------------------------------------- /stlearn/app/static/img/loading_gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/app/static/img/loading_gif.gif -------------------------------------------------------------------------------- /stlearn/app/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/app/templates/__init__.py -------------------------------------------------------------------------------- /stlearn/app/templates/annotate_plot.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

Annotation plot

6 |

Interactive version

7 |
8 |
9 | {{ script|safe }} 10 |
11 | Loading 12 | Loading plot... 13 |
14 |
15 |
16 | {% endblock %} 17 | 18 | {% block status %} 19 | {% include "progress.html" %} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /stlearn/app/templates/cci.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Cell-cell interaction analysis

7 |
8 |
9 | 10 | {# Gives the user errors if didn't fill out form correctly #} 11 | {% include "flash_header.html" %} 12 | 13 | {# Below is the form. #} 14 | {% with superForm=cci_form %} 15 | {% include "superform.html" %} 16 | {% endwith %} 17 | 18 |
19 |
20 | {% endblock %} 21 | 22 | {% block status %} 23 | {% include "progress.html" %} 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /stlearn/app/templates/cci_old.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Cell-cell interaction analysis

7 |
8 |
9 | 10 | {# Gives the user errors if didn't fill out form correctly #} 11 | {% include "flash_header.html" %} 12 | 13 | {# Below is the form. #} 14 | {% with superForm=cci_form %} 15 | {% include "superform.html" %} 16 | {% endwith %} 17 | 18 |
19 | *Optional 20 |
21 | **Required 22 |
23 |
24 |
25 | {% endblock %} 26 | 27 | {% block status %} 28 | {% include "progress.html" %} 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /stlearn/app/templates/cci_plot.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

Cell-cell interaction plot

6 |

Interactive version

7 |
8 |
9 | {{ script|safe }} 10 |
11 | Loading 12 | Loading plot... 13 |
14 |
15 |
16 | {% endblock %} 17 | 18 | {% block status %} 19 | {% include "progress.html" %} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /stlearn/app/templates/choose_cluster.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | 4 | 5 | 6 | 7 | 32 | {% endblock %} 33 | 34 | 35 | {% block status %} 36 | {% include "progress.html" %} 37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /stlearn/app/templates/cluster_plot.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

Cluster plot

6 |

Interactive version

7 |
8 |
9 | {{ script|safe }} 10 |
11 | Loading 12 | Loading plot... 13 |
14 |
15 |
16 | 17 | 18 | {% endblock %} 19 | 20 | {% block status %} 21 | {% include "progress.html" %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /stlearn/app/templates/clustering.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

Clustering

6 |
7 |
8 | 9 | {# Below indicates whether clustering already completed #} 10 | {% include "flash_header.html" %} 11 | 12 | {# Below is the form. #} 13 | {% with superForm=clustering_form %} 14 | {% include "superform.html" %} 15 | {% endwith %} 16 |
17 |
18 | 19 | 20 | {% endblock %} 21 | 22 | {% block status %} 23 | {% include "progress.html" %} 24 | {% endblock %} 25 | 26 | {% block javascript %} 27 | 46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /stlearn/app/templates/dea.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Differential expression analysis

7 |
8 |
9 | 10 | {# Below indicates whether dea already completed #} 11 | {% include "flash_header.html" %} 12 | 13 | {# Below is the form. #} 14 | {% with superForm=dea_form %} 15 | {% include "superform.html" %} 16 | {% endwith %} 17 | 18 |
19 |
20 | {% endblock %} 21 | 22 | {% block status %} 23 | {% include "progress.html" %} 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /stlearn/app/templates/flash_header.html: -------------------------------------------------------------------------------- 1 | {# Flashes message at the top of the page to the user. 2 | #} 3 | 4 | {% if flash_bool %} 5 |
6 | {% for message in get_flashed_messages() %} 7 |
8 | 9 | {{ message }} 10 |
11 | {% endfor %} 12 | 13 | {% block page_content %}{% endblock %} 14 |
15 | {% endif %} 16 | -------------------------------------------------------------------------------- /stlearn/app/templates/gene_plot.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

Gene plot

6 |

Interactive version

7 |
8 |
9 | {{ script|safe }} 10 | 11 |
12 | Loading 13 | Loading plot... 14 |
15 |
16 |
17 | 18 | 19 | {% endblock %} 20 | 21 | {% block status %} 22 | {% include "progress.html" %} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /stlearn/app/templates/lr.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Ligand-receptor interaction analysis

7 |
8 |
9 | 10 | {# Gives the user errors if didn't fill out form correctly #} 11 | {% include "flash_header.html" %} 12 | 13 | {# Below is the form. #} 14 | {% with superForm=lr_form %} 15 | {% include "superform.html" %} 16 | {% endwith %} 17 | 18 |
19 |
20 | {% endblock %} 21 | 22 | {% block status %} 23 | {% include "progress.html" %} 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /stlearn/app/templates/lr_plot.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

Ligand-receptor significant interactions plot

6 |

Interactive version

7 |
8 |
9 | {{ script|safe }} 10 |
11 | Loading 12 | Loading plot... 13 |
14 |
15 |
16 | {% endblock %} 17 | 18 | {% block status %} 19 | {% include "progress.html" %} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /stlearn/app/templates/preprocessing.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Preprocessing

7 |
8 |
9 | 10 | {# Below indicates whether pre-processing already completed #} 11 | {% include "flash_header.html" %} 12 | 13 | {# Below is the form. #} 14 | {% with superForm=preprocess_form %} 15 | {% include "superform.html" %} 16 | {% endwith %} 17 | 18 |
19 |
20 | {% endblock %} 21 | 22 | {% block status %} 23 | {% include "progress.html" %} 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /stlearn/app/templates/progress.html: -------------------------------------------------------------------------------- 1 | {# Code which displays the progress bar, indicating where in the analysis the 2 | user is. 3 | #} 4 | 5 | 6 | {% for key, value in step_log.items() %} 7 | {% if 'params' not in key %} 8 | {% if value[0] %} 9 |
  • Success {{ value[1] }}
  • 10 | {% else %} 11 |
  • None {{ value[1] }}
  • 12 | {% endif %} 13 | {% endif %} 14 | {% endfor %} 15 | 16 | 17 |
    18 | 19 |
    20 | -------------------------------------------------------------------------------- /stlearn/app/templates/psts.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | 4 | 7 |
    8 | 9 |
    10 |
    11 |

    Pseudo-time-space analysis

    12 |
    13 |
    14 | 15 | {# Below indicates whether clustering already completed #} 16 | {% include "flash_header.html" %} 17 | 18 | {# Below is the form. #} 19 | {% with superForm=psts_form %} 20 | {% include "superform.html" %} 21 | {% endwith %} 22 |
    23 |
    24 | {% endblock %} 25 | 26 | {% block status %} 27 | {% include "progress.html" %} 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /stlearn/app/templates/spatial_cci_plot.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
    4 |
    5 |

    Spatial cell-cell interactions plot

    6 |

    Interactive version

    7 |
    8 |
    9 | {{ script|safe }} 10 |
    11 | Loading 12 | Loading plot... 13 |
    14 |
    15 |
    16 | {% endblock %} 17 | 18 | {% block status %} 19 | {% include "progress.html" %} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /stlearn/app/templates/superform.html: -------------------------------------------------------------------------------- 1 | {# Generalised, injectable html for rendering a WTForm generated using: 2 | source.forms.forms.createSuperForm() 3 | #} 4 | 5 | {% block content %} 6 |
    7 | {# Pre-processing form #} 8 |
    10 | {{ superForm.csrf_token }} 11 | {{ superForm.hidden_tag() }} 12 | 13 | {# Iterating through and adding each element appropriately #} 14 | {% for element in superForm.elements %} 15 | 16 | {# Determining Field type #} 17 | {% if superForm['element_fields'][loop.index0] == 'Title' %} 18 |

    {{ element }}

    19 | 20 | {% elif superForm['element_fields'][loop.index0] == 'SelectMultipleField' %} 21 |
    22 | {{ superForm[element].label }} 23 | {{ superForm[element](class_="form-control", rows=5, multiple=True) }} 24 |
    25 | 26 | {% elif superForm['element_fields'][loop.index0] == 'SelectField' %} 27 |
    28 | {{ element }} 29 | {{ superForm[element](class_="form-control")}} 30 |
    31 | 32 | {% elif superForm['element_fields'][loop.index0] == 'StringField' %} 33 |
    34 | {{ element }} 35 | {{ superForm[element]( 36 | placeholder=superForm[element+'_placeholder'], 37 | class_="form-control")}} 38 |
    39 | 40 | {% elif superForm['element_fields'][loop.index0] == 'IntegerField' %} 41 |
    42 | {{ element }} 43 | {{ superForm[element]( 44 | value=superForm[element+'_placeholder'], 45 | class_="form-control")}} 46 |
    47 | 48 | {% elif superForm['element_fields'][loop.index0] == 'BooleanField' %} 49 |
    50 | {{ element }} 51 | {{ superForm[element]( 52 | checked=superForm[element+'_placeholder'], 53 | class_="form-control")}} 54 |
    55 | 56 | {% elif superForm['element_fields'][loop.index0] == 'FloatField' %} 57 |
    58 | {{ element }} 59 | {{ superForm[element]( 60 | value=superForm[element+'_placeholder'], 61 | class_="form-control")}} 62 |
    63 | 64 | {% elif superForm['element_fields'][loop.index0] == 'FileField' %} 65 | {{ element }} 66 | {{ superForm[element] }} 67 | 68 | {% endif %} 69 | {% endfor %} 70 | 71 | {# Button which controls form submission #} 72 |

    73 |
    74 | 75 |
    76 | {% endblock %} 77 | -------------------------------------------------------------------------------- /stlearn/app/templates/upload.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'base.html' %} 3 | 4 | {% block content %} 5 | 6 |
    7 |
    8 |

    Uploading files

    9 |
    10 |
    11 |
    12 |
    13 |
    14 |

    Option 1: Open Visium data

    15 |
    16 | 17 |
    18 |
    Please select the path to your Visium folder. It should include:
    filtered_feature_bc_matrix.h5 file and the spatial folder
    19 |
    20 | 21 | 22 | 23 |
    24 | 25 |
    26 | 27 | 28 | 29 |
    30 | 31 |
    32 | 33 |
    34 |
    35 |
    36 |
    37 |

    Option 2: Open AnnData object

    38 |
    39 | 40 |
    41 |
    Please select the path to the AnnData .h5ad file
    42 |
    43 | 44 | 45 | 46 |
    47 | 48 |
    49 | 50 | 51 | 52 |
    53 | 54 |
    55 | 56 | 57 |
    58 | 63 |
    64 | 65 | {% endblock %} 66 | 67 | 68 | {% block status %} 69 | {% include "progress.html" %} 70 | {% endblock %} 71 | 72 | {% block javascript %} 73 | 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /stlearn/classes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Title: SpatialBasePlot for all spatial coordinates and image plot 3 | Author: Duy Pham 4 | Date: 20 Feb 2021 5 | """ 6 | 7 | from typing import Optional, Union, Mapping # Special 8 | from typing import Sequence, Iterable # ABCs 9 | from typing import Tuple # Classes 10 | 11 | import numpy as np 12 | from anndata import AnnData 13 | 14 | from .utils import ( 15 | Empty, 16 | _empty, 17 | _check_spatial_data, 18 | _check_img, 19 | _check_spot_size, 20 | _check_scale_factor, 21 | _check_coords, 22 | ) 23 | 24 | 25 | class Spatial(object): 26 | def __init__( 27 | self, 28 | adata: AnnData, 29 | basis: str = "spatial", 30 | img: Union[np.ndarray, None] = None, 31 | img_key: Union[str, None, Empty] = _empty, 32 | library_id: Union[str, None] = _empty, 33 | crop_coord: Optional[bool] = True, 34 | bw: Optional[bool] = False, 35 | scale_factor: Optional[float] = None, 36 | spot_size: Optional[float] = None, 37 | use_raw: Optional[bool] = False, 38 | **kwargs, 39 | ): 40 | 41 | self.adata = (adata,) 42 | self.library_id, self.spatial_data = _check_spatial_data(adata.uns, library_id) 43 | self.img, self.img_key = _check_img(self.spatial_data, img, img_key, bw=bw) 44 | self.spot_size = _check_spot_size(self.spatial_data, spot_size) 45 | self.scale_factor = _check_scale_factor( 46 | self.spatial_data, img_key=self.img_key, scale_factor=scale_factor 47 | ) 48 | self.crop_coord = crop_coord 49 | self.use_raw = use_raw 50 | self.imagecol, self.imagerow = _check_coords(adata.obsm, self.scale_factor) 51 | -------------------------------------------------------------------------------- /stlearn/datasets.py: -------------------------------------------------------------------------------- 1 | from ._datasets._datasets import example_bcba 2 | -------------------------------------------------------------------------------- /stlearn/em.py: -------------------------------------------------------------------------------- 1 | from .embedding.pca import run_pca 2 | from .embedding.umap import run_umap 3 | from .embedding.ica import run_ica 4 | 5 | # from .embedding.scvi import run_ldvae 6 | from .embedding.fa import run_fa 7 | from .embedding.diffmap import run_diffmap 8 | -------------------------------------------------------------------------------- /stlearn/embedding/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/embedding/__init__.py -------------------------------------------------------------------------------- /stlearn/embedding/diffmap.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | import numpy as np 3 | from anndata import AnnData 4 | from numpy.random.mtrand import RandomState 5 | from scipy.sparse import issparse 6 | import scanpy 7 | 8 | 9 | def run_diffmap(adata: AnnData, n_comps: int = 15, copy: bool = False): 10 | """\ 11 | Diffusion Maps [Coifman05]_ [Haghverdi15]_ [Wolf18]_. 12 | Diffusion maps [Coifman05]_ has been proposed for visualizing single-cell 13 | data by [Haghverdi15]_. The tool uses the adapted Gaussian kernel suggested 14 | by [Haghverdi16]_ in the implementation of [Wolf18]_. 15 | The width ("sigma") of the connectivity kernel is implicitly determined by 16 | the number of neighbors used to compute the single-cell graph in 17 | :func:`~scanpy.pp.neighbors`. To reproduce the original implementation 18 | using a Gaussian kernel, use `method=='gauss'` in 19 | :func:`~scanpy.pp.neighbors`. To use an exponential kernel, use the default 20 | `method=='umap'`. Differences between these options shouldn't usually be 21 | dramatic. 22 | Parameters 23 | ---------- 24 | adata 25 | Annotated data matrix. 26 | n_comps 27 | The number of dimensions of the representation. 28 | copy 29 | Return a copy instead of writing to adata. 30 | Returns 31 | ------- 32 | Depending on `copy`, returns or updates `adata` with the following fields. 33 | `X_diffmap` : :class:`numpy.ndarray` (`adata.obsm`) 34 | Diffusion map representation of data, which is the right eigen basis of 35 | the transition matrix with eigenvectors as columns. 36 | `diffmap_evals` : :class:`numpy.ndarray` (`adata.uns`) 37 | Array of size (number of eigen vectors). 38 | Eigenvalues of transition matrix. 39 | """ 40 | 41 | scanpy.tl.diffmap(adata, n_comps=n_comps, copy=copy) 42 | 43 | print( 44 | "Diffusion Map is done! Generated in adata.obsm['X_diffmap'] nad adata.uns['diffmap_evals']" 45 | ) 46 | 47 | return adata if copy else None 48 | -------------------------------------------------------------------------------- /stlearn/embedding/fa.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from typing import Optional 4 | 5 | from anndata import AnnData 6 | from sklearn.decomposition import FactorAnalysis 7 | from scipy.sparse import issparse 8 | 9 | 10 | def run_fa( 11 | adata: AnnData, 12 | n_factors: int = 20, 13 | tol: float = 0.01, 14 | max_iter: int = 1000, 15 | svd_method: str = "randomized", 16 | iterated_power: int = 3, 17 | random_state: int = 2108, 18 | use_data: str = None, 19 | copy: bool = False, 20 | ) -> Optional[AnnData]: 21 | 22 | """\ 23 | Factor Analysis (FA) 24 | A simple linear generative model with Gaussian latent variables. 25 | The observations are assumed to be caused by a linear transformation of 26 | lower dimensional latent factors and added Gaussian noise. 27 | Without loss of generality the factors are distributed according to a 28 | Gaussian with zero mean and unit covariance. The noise is also zero mean 29 | and has an arbitrary diagonal covariance matrix. 30 | If we would restrict the model further, by assuming that the Gaussian 31 | noise is even isotropic (all diagonal entries are the same) we would obtain 32 | :class:`PPCA`. 33 | FactorAnalysis performs a maximum likelihood estimate of the so-called 34 | `loading` matrix, the transformation of the latent variables to the 35 | observed ones, using SVD based approach. 36 | 37 | Parameters 38 | ---------- 39 | adata 40 | Annotated data matrix. 41 | n_components 42 | Dimensionality of latent space, the number of components 43 | of ``X`` that are obtained after ``transform``. 44 | If None, n_components is set to the number of features. 45 | tol 46 | Stopping tolerance for log-likelihood increase. 47 | max_iter 48 | Maximum number of iterations. 49 | noise_variance_init 50 | The initial guess of the noise variance for each feature. 51 | If None, it defaults to np.ones(n_features) 52 | svd_method 53 | Which SVD method to use. If 'lapack' use standard SVD from 54 | scipy.linalg, if 'randomized' use fast ``randomized_svd`` function. 55 | Defaults to 'randomized'. For most applications 'randomized' will 56 | be sufficiently precise while providing significant speed gains. 57 | Accuracy can also be improved by setting higher values for 58 | `iterated_power`. If this is not sufficient, for maximum precision 59 | you should choose 'lapack'. 60 | iterated_power 61 | Number of iterations for the power method. 3 by default. Only used 62 | if ``svd_method`` equals 'randomized' 63 | random_state 64 | If int, random_state is the seed used by the random number generator; 65 | If RandomState instance, random_state is the random number generator; 66 | If None, the random number generator is the RandomState instance used 67 | by `np.random`. Only used when ``svd_method`` equals 'randomized'. 68 | copy 69 | Return a copy instead of writing to adata. 70 | Returns 71 | ------- 72 | Depending on `copy`, returns or updates `adata` with the following fields. 73 | `X_fa` : :class:`numpy.ndarray` (`adata.obsm`) 74 | Factor analysis representation of data. 75 | """ 76 | 77 | if use_data is None: 78 | if issparse(adata.X): 79 | matrix = adata.X.toarray() 80 | else: 81 | matrix = adata.X 82 | 83 | else: 84 | matrix = adata.obsm[use_data].values 85 | 86 | fa = FactorAnalysis( 87 | n_components=n_factors, 88 | tol=tol, 89 | max_iter=max_iter, 90 | svd_method=svd_method, 91 | iterated_power=iterated_power, 92 | random_state=random_state, 93 | ) 94 | 95 | latent = fa.fit_transform(matrix) 96 | 97 | adata.obsm["X_fa"] = latent 98 | 99 | adata.uns["fa_params"] = { 100 | "params": { 101 | "n_factors": n_factors, 102 | "tol": tol, 103 | "max_iter": max_iter, 104 | "svd_method": svd_method, 105 | "iterated_power": iterated_power, 106 | "random_state": random_state, 107 | } 108 | } 109 | 110 | print('FA is done! Generated in adata.obsm["X_fa"]') 111 | 112 | return adata if copy else None 113 | -------------------------------------------------------------------------------- /stlearn/embedding/ica.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from typing import Optional 4 | from anndata import AnnData 5 | from sklearn.decomposition import FastICA 6 | from scipy.sparse import issparse 7 | 8 | 9 | def run_ica( 10 | adata: AnnData, 11 | n_factors: int = 20, 12 | fun: str = "logcosh", 13 | tol: float = 0.0001, 14 | use_data: str = None, 15 | copy: bool = False, 16 | ) -> Optional[AnnData]: 17 | 18 | """\ 19 | FastICA: a fast algorithm for Independent Component Analysis. 20 | 21 | Parameters 22 | ---------- 23 | adata 24 | Annotated data matrix. 25 | n_factors 26 | Number of components to use. If none is passed, all are used. 27 | fun 28 | The functional form of the G function used in the 29 | approximation to neg-entropy. Could be either 'logcosh', 'exp', 30 | or 'cube'. 31 | You can also provide your own function. It should return a tuple 32 | containing the value of the function, and of its derivative, in the 33 | point. Example: 34 | def my_g(x): 35 | return x ** 3, (3 * x ** 2).mean(axis=-1) 36 | tol 37 | Tolerance on update at each iteration. 38 | use_data 39 | if None, then using all the gene expression profile. Else, use 40 | the chosen data from adata.obsm. 41 | copy 42 | Return a copy instead of writing to adata. 43 | Returns 44 | ------- 45 | Depending on `copy`, returns or updates `adata` with the following fields. 46 | `X_ica` : :class:`numpy.ndarray` (`adata.obsm`) 47 | Independent Component Analysis representation of data. 48 | """ 49 | 50 | if use_data is None: 51 | if issparse(adata.X): 52 | matrix = adata.X.toarray() 53 | else: 54 | matrix = adata.X 55 | 56 | else: 57 | matrix = adata.obsm[use_data].values 58 | 59 | ica = FastICA(n_components=n_factors, fun=fun, tol=tol) 60 | 61 | latent = ica.fit_transform(matrix) 62 | 63 | adata.obsm["X_ica"] = latent 64 | 65 | adata.uns["ica"] = {"params": {"n_factors": n_factors, "fun": fun, "tol": tol}} 66 | 67 | print( 68 | "ICA is done! Generated in adata.obsm['X_ica'] and parameters in adata.uns['ica']" 69 | ) 70 | 71 | return adata if copy else None 72 | -------------------------------------------------------------------------------- /stlearn/embedding/pca.py: -------------------------------------------------------------------------------- 1 | import logging as logg 2 | from typing import Union, Optional, Tuple, Collection, Sequence, Iterable 3 | from anndata import AnnData 4 | import numpy as np 5 | from scipy.sparse import issparse, isspmatrix_csr, csr_matrix, spmatrix 6 | from numpy.random.mtrand import RandomState 7 | import scanpy 8 | 9 | 10 | def run_pca( 11 | data: Union[AnnData, np.ndarray, spmatrix], 12 | n_comps: int = 50, 13 | zero_center: Optional[bool] = True, 14 | svd_solver: str = "auto", 15 | random_state: Optional[Union[int, RandomState]] = 0, 16 | return_info: bool = False, 17 | use_highly_variable: Optional[bool] = None, 18 | dtype: str = "float32", 19 | copy: bool = False, 20 | chunked: bool = False, 21 | chunk_size: Optional[int] = None, 22 | ) -> Union[AnnData, np.ndarray, spmatrix]: 23 | """\ 24 | Wrap function scanpy.pp.pca 25 | Principal component analysis [Pedregosa11]_. 26 | Computes PCA coordinates, loadings and variance decomposition. 27 | Uses the implementation of *scikit-learn* [Pedregosa11]_. 28 | Parameters 29 | ---------- 30 | data 31 | The (annotated) data matrix of shape `n_obs` × `n_vars`. 32 | Rows correspond to cells and columns to genes. 33 | n_comps 34 | Number of principal components to compute. 35 | zero_center 36 | If `True`, compute standard PCA from covariance matrix. 37 | If `False`, omit zero-centering variables 38 | (uses :class:`~sklearn.decomposition.TruncatedSVD`), 39 | which allows to handle sparse input efficiently. 40 | Passing `None` decides automatically based on sparseness of the data. 41 | svd_solver 42 | SVD solver to use: 43 | `'arpack'` 44 | for the ARPACK wrapper in SciPy (:func:`~scipy.sparse.linalg.svds`) 45 | `'randomized'` 46 | for the randomized algorithm due to Halko (2009). 47 | `'auto'` (the default) 48 | chooses automatically depending on the size of the problem. 49 | random_state 50 | Change to use different initial states for the optimization. 51 | return_info 52 | Only relevant when not passing an :class:`~anndata.AnnData`: 53 | see “**Returns**”. 54 | use_highly_variable 55 | Whether to use highly variable genes only, stored in 56 | `.var['highly_variable']`. 57 | By default uses them if they have been determined beforehand. 58 | dtype 59 | Numpy data type string to which to convert the result. 60 | copy 61 | If an :class:`~anndata.AnnData` is passed, determines whether a copy 62 | is returned. Is ignored otherwise. 63 | chunked 64 | If `True`, perform an incremental PCA on segments of `chunk_size`. 65 | The incremental PCA automatically zero centers and ignores settings of 66 | `random_seed` and `svd_solver`. If `False`, perform a full PCA. 67 | chunk_size 68 | Number of observations to include in each chunk. 69 | Required if `chunked=True` was passed. 70 | Returns 71 | ------- 72 | X_pca : :class:`~scipy.sparse.spmatrix`, :class:`~numpy.ndarray` 73 | If `data` is array-like and `return_info=False` was passed, 74 | this function only returns `X_pca`… 75 | adata : anndata.AnnData 76 | …otherwise if `copy=True` it returns or else adds fields to `adata`: 77 | `.obsm['X_pca']` 78 | PCA representation of data. 79 | `.varm['PCs']` 80 | The principal components containing the loadings. 81 | `.uns['pca']['variance_ratio']` 82 | Ratio of explained variance. 83 | `.uns['pca']['variance']` 84 | Explained variance, equivalent to the eigenvalues of the 85 | covariance matrix. 86 | """ 87 | 88 | scanpy.pp.pca( 89 | data, 90 | n_comps=n_comps, 91 | zero_center=zero_center, 92 | svd_solver=svd_solver, 93 | random_state=random_state, 94 | return_info=return_info, 95 | use_highly_variable=use_highly_variable, 96 | dtype=dtype, 97 | copy=copy, 98 | chunked=chunked, 99 | chunk_size=chunk_size, 100 | ) 101 | 102 | print( 103 | "PCA is done! Generated in adata.obsm['X_pca'], adata.uns['pca'] and adata.varm['PCs']" 104 | ) 105 | -------------------------------------------------------------------------------- /stlearn/embedding/umap.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | import numpy as np 4 | from anndata import AnnData 5 | from numpy.random.mtrand import RandomState 6 | 7 | from .._compat import Literal 8 | import scanpy 9 | 10 | _InitPos = Literal["paga", "spectral", "random"] 11 | 12 | 13 | def run_umap( 14 | adata: AnnData, 15 | min_dist: float = 0.5, 16 | spread: float = 1.0, 17 | n_components: int = 2, 18 | maxiter: Optional[int] = None, 19 | alpha: float = 1.0, 20 | gamma: float = 1.0, 21 | negative_sample_rate: int = 5, 22 | init_pos: Union[_InitPos, np.ndarray, None] = "spectral", 23 | random_state: Optional[Union[int, RandomState]] = 0, 24 | a: Optional[float] = None, 25 | b: Optional[float] = None, 26 | copy: bool = False, 27 | method: Literal["umap", "rapids"] = "umap", 28 | ) -> Optional[AnnData]: 29 | """\ 30 | Wrap function scanpy.pp.umap 31 | Embed the neighborhood graph using UMAP [McInnes18]_. 32 | UMAP (Uniform Manifold Approximation and Projection) is a manifold learning 33 | technique suitable for visualizing high-dimensional data. Besides tending to 34 | be faster than tSNE, it optimizes the embedding such that it best reflects 35 | the topology of the data, which we represent throughout Scanpy using a 36 | neighborhood graph. tSNE, by contrast, optimizes the distribution of 37 | nearest-neighbor distances in the embedding such that these best match the 38 | distribution of distances in the high-dimensional space. We use the 39 | implementation of `umap-learn `__ 40 | [McInnes18]_. For a few comparisons of UMAP with tSNE, see this `preprint 41 | `__. 42 | Parameters 43 | ---------- 44 | adata 45 | Annotated data matrix. 46 | n_components 47 | The number of dimensions of the embedding. 48 | random_state 49 | If `int`, `random_state` is the seed used by the random number generator; 50 | If `RandomState`, `random_state` is the random number generator; 51 | If `None`, the random number generator is the `RandomState` instance used 52 | by `np.random`. 53 | Returns 54 | ------- 55 | Depending on `copy`, returns or updates `adata` with the following fields. 56 | `X_umap` : :class:`numpy.ndarray` (`adata.obsm`) 57 | Independent Component Analysis representation of data. 58 | 59 | """ 60 | 61 | scanpy.tl.umap( 62 | adata, 63 | min_dist=min_dist, 64 | spread=spread, 65 | n_components=n_components, 66 | maxiter=maxiter, 67 | alpha=alpha, 68 | gamma=gamma, 69 | negative_sample_rate=negative_sample_rate, 70 | init_pos=init_pos, 71 | random_state=random_state, 72 | a=a, 73 | b=b, 74 | copy=copy, 75 | method=method, 76 | ) 77 | 78 | print("UMAP is done! Generated in adata.obsm['X_umap'] nad adata.uns['umap']") 79 | -------------------------------------------------------------------------------- /stlearn/gui.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/gui.py -------------------------------------------------------------------------------- /stlearn/image_preprocessing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/image_preprocessing/__init__.py -------------------------------------------------------------------------------- /stlearn/image_preprocessing/feature_extractor.py: -------------------------------------------------------------------------------- 1 | from .model_zoo import encode, Model 2 | from typing import Optional, Union 3 | from anndata import AnnData 4 | import numpy as np 5 | from .._compat import Literal 6 | from PIL import Image 7 | import pandas as pd 8 | from pathlib import Path 9 | 10 | # Test progress bar 11 | from tqdm import tqdm 12 | 13 | _CNN_BASE = Literal["resnet50", "vgg16", "inception_v3", "xception"] 14 | 15 | 16 | def extract_feature( 17 | adata: AnnData, 18 | cnn_base: _CNN_BASE = "resnet50", 19 | n_components: int = 50, 20 | verbose: bool = False, 21 | copy: bool = False, 22 | seeds: int = 1, 23 | ) -> Optional[AnnData]: 24 | """\ 25 | Extract latent morphological features from H&E images using pre-trained 26 | convolutional neural network base 27 | 28 | Parameters 29 | ---------- 30 | adata 31 | Annotated data matrix. 32 | cnn_base 33 | Established convolutional neural network bases 34 | choose one from ['resnet50', 'vgg16', 'inception_v3', 'xception'] 35 | n_components 36 | Number of principal components to compute for latent morphological features 37 | verbose 38 | Verbose output 39 | copy 40 | Return a copy instead of writing to adata. 41 | seeds 42 | Fix random state 43 | Returns 44 | ------- 45 | Depending on `copy`, returns or updates `adata` with the following fields. 46 | **X_morphology** : `adata.obsm` field 47 | Dimension reduced latent morphological features. 48 | """ 49 | feature_dfs = [] 50 | model = Model(cnn_base) 51 | 52 | if "tile_path" not in adata.obs: 53 | raise ValueError("Please run the function stlearn.pp.tiling") 54 | 55 | with tqdm( 56 | total=len(adata), 57 | desc="Extract feature", 58 | bar_format="{l_bar}{bar} [ time left: {remaining} ]", 59 | ) as pbar: 60 | for spot, tile_path in adata.obs["tile_path"].items(): 61 | tile = Image.open(tile_path) 62 | tile = np.asarray(tile, dtype="int32") 63 | tile = tile.astype(np.float32) 64 | tile = np.stack([tile]) 65 | if verbose: 66 | print("extract feature for spot: {}".format(str(spot))) 67 | features = encode(tile, model) 68 | feature_dfs.append(pd.DataFrame(features, columns=[spot])) 69 | pbar.update(1) 70 | 71 | feature_df = pd.concat(feature_dfs, axis=1) 72 | 73 | adata.obsm["X_tile_feature"] = feature_df.transpose().to_numpy() 74 | 75 | from sklearn.decomposition import PCA 76 | 77 | pca = PCA(n_components=n_components, random_state=seeds) 78 | pca.fit(feature_df.transpose().to_numpy()) 79 | 80 | adata.obsm["X_morphology"] = pca.transform(feature_df.transpose().to_numpy()) 81 | 82 | print("The morphology feature is added to adata.obsm['X_morphology']!") 83 | 84 | return adata if copy else None 85 | -------------------------------------------------------------------------------- /stlearn/image_preprocessing/image_tiling.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | from .._compat import Literal 4 | from PIL import Image 5 | from pathlib import Path 6 | 7 | # Test progress bar 8 | from tqdm import tqdm 9 | import numpy as np 10 | import os 11 | 12 | 13 | def tiling( 14 | adata: AnnData, 15 | out_path: Union[Path, str] = "./tiling", 16 | library_id: Union[str, None] = None, 17 | crop_size: int = 40, 18 | target_size: int = 299, 19 | img_fmt: str = "JPEG", 20 | verbose: bool = False, 21 | copy: bool = False, 22 | ) -> Optional[AnnData]: 23 | """\ 24 | Tiling H&E images to small tiles based on spot spatial location 25 | 26 | Parameters 27 | ---------- 28 | adata 29 | Annotated data matrix. 30 | out_path 31 | Path to save spot image tiles 32 | library_id 33 | Library id stored in AnnData. 34 | crop_size 35 | Size of tiles 36 | verbose 37 | Verbose output 38 | copy 39 | Return a copy instead of writing to adata. 40 | target_size 41 | Input size for convolutional neuron network 42 | Returns 43 | ------- 44 | Depending on `copy`, returns or updates `adata` with the following fields. 45 | **tile_path** : `adata.obs` field 46 | Saved path for each spot image tiles 47 | """ 48 | 49 | if library_id is None: 50 | library_id = list(adata.uns["spatial"].keys())[0] 51 | 52 | # Check the exist of out_path 53 | if not os.path.isdir(out_path): 54 | os.mkdir(out_path) 55 | 56 | image = adata.uns["spatial"][library_id]["images"][ 57 | adata.uns["spatial"][library_id]["use_quality"] 58 | ] 59 | if image.dtype == np.float32 or image.dtype == np.float64: 60 | image = (image * 255).astype(np.uint8) 61 | img_pillow = Image.fromarray(image) 62 | 63 | if img_pillow.mode == "RGBA": 64 | img_pillow = img_pillow.convert("RGB") 65 | 66 | tile_names = [] 67 | 68 | with tqdm( 69 | total=len(adata), 70 | desc="Tiling image", 71 | bar_format="{l_bar}{bar} [ time left: {remaining} ]", 72 | ) as pbar: 73 | for imagerow, imagecol in zip(adata.obs["imagerow"], adata.obs["imagecol"]): 74 | imagerow_down = imagerow - crop_size / 2 75 | imagerow_up = imagerow + crop_size / 2 76 | imagecol_left = imagecol - crop_size / 2 77 | imagecol_right = imagecol + crop_size / 2 78 | tile = img_pillow.crop( 79 | (imagecol_left, imagerow_down, imagecol_right, imagerow_up) 80 | ) 81 | tile.thumbnail((target_size, target_size), Image.Resampling.LANCZOS) 82 | tile = tile.resize((target_size, target_size)) 83 | tile_name = str(imagecol) + "-" + str(imagerow) + "-" + str(crop_size) 84 | 85 | if img_fmt == "JPEG": 86 | out_tile = Path(out_path) / (tile_name + ".jpeg") 87 | tile_names.append(str(out_tile)) 88 | tile.save(out_tile, "JPEG") 89 | else: 90 | out_tile = Path(out_path) / (tile_name + ".png") 91 | tile_names.append(str(out_tile)) 92 | tile.save(out_tile, "PNG") 93 | 94 | if verbose: 95 | print( 96 | "generate tile at location ({}, {})".format( 97 | str(imagecol), str(imagerow) 98 | ) 99 | ) 100 | 101 | pbar.update(1) 102 | 103 | adata.obs["tile_path"] = tile_names 104 | return adata if copy else None 105 | -------------------------------------------------------------------------------- /stlearn/image_preprocessing/model_zoo.py: -------------------------------------------------------------------------------- 1 | def encode(tiles, model): 2 | features = model.predict(tiles) 3 | features = features.ravel() 4 | return features 5 | 6 | 7 | class Model: 8 | __name__ = "CNN base model" 9 | 10 | def __init__(self, base, batch_size=1): 11 | from tensorflow.keras import backend as K 12 | 13 | self.base = base 14 | self.model, self.preprocess = self.load_model() 15 | self.batch_size = batch_size 16 | self.data_format = K.image_data_format() 17 | 18 | def load_model(self): 19 | if self.base == "resnet50": 20 | from tensorflow.keras.applications.resnet50 import ( 21 | ResNet50, 22 | preprocess_input, 23 | ) 24 | 25 | cnn_base_model = ResNet50( 26 | include_top=False, weights="imagenet", pooling="avg" 27 | ) 28 | elif self.base == "vgg16": 29 | from tensorflow.keras.applications.vgg16 import VGG16, preprocess_input 30 | 31 | cnn_base_model = VGG16(include_top=False, weights="imagenet", pooling="avg") 32 | elif self.base == "inception_v3": 33 | from tensorflow.keras.applications.inception_v3 import ( 34 | InceptionV3, 35 | preprocess_input, 36 | ) 37 | 38 | cnn_base_model = InceptionV3( 39 | include_top=False, weights="imagenet", pooling="avg" 40 | ) 41 | elif self.base == "xception": 42 | from tensorflow.keras.applications.xception import ( 43 | Xception, 44 | preprocess_input, 45 | ) 46 | 47 | cnn_base_model = Xception( 48 | include_top=False, weights="imagenet", pooling="avg" 49 | ) 50 | else: 51 | raise ValueError("{} is not a valid model".format(self.base)) 52 | return cnn_base_model, preprocess_input 53 | 54 | def predict(self, x): 55 | from tensorflow.keras import backend as K 56 | 57 | if self.data_format == "channels_first": 58 | x = x.transpose(0, 3, 1, 2) 59 | x = self.preprocess(x.astype(K.floatx())) 60 | return self.model.predict(x, batch_size=self.batch_size, verbose=False) 61 | -------------------------------------------------------------------------------- /stlearn/pl.py: -------------------------------------------------------------------------------- 1 | from .plotting.gene_plot import gene_plot 2 | from .plotting.gene_plot import gene_plot_interactive 3 | from .plotting.feat_plot import feat_plot 4 | from .plotting.cluster_plot import cluster_plot 5 | from .plotting.cluster_plot import cluster_plot_interactive 6 | from .plotting.subcluster_plot import subcluster_plot 7 | from .plotting.non_spatial_plot import non_spatial_plot 8 | from .plotting.deconvolution_plot import deconvolution_plot 9 | from .plotting.stack_3d_plot import stack_3d_plot 10 | from .plotting import trajectory 11 | from .plotting.QC_plot import QC_plot 12 | from .plotting.cci_plot import het_plot 13 | 14 | # from .plotting.cci_plot import het_plot_interactive 15 | from .plotting.cci_plot import lr_plot_interactive, spatialcci_plot_interactive 16 | from .plotting.cci_plot import grid_plot 17 | from .plotting.cci_plot import lr_diagnostics, lr_n_spots, lr_summary, lr_go 18 | from .plotting.cci_plot import lr_plot, lr_result_plot 19 | from .plotting.cci_plot import ( 20 | ccinet_plot, 21 | cci_map, 22 | lr_cci_map, 23 | lr_chord_plot, 24 | cci_check, 25 | ) 26 | from .plotting.mask_plot import plot_mask 27 | -------------------------------------------------------------------------------- /stlearn/plotting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/plotting/__init__.py -------------------------------------------------------------------------------- /stlearn/plotting/_docs.py: -------------------------------------------------------------------------------- 1 | doc_spatial_base_plot = """\ 2 | adata 3 | Annotated data matrix. 4 | title 5 | Title name of the figure. 6 | figsize 7 | Figure size with the format (width,height). 8 | cmap 9 | Color map to use for continous variables or discretes variables (e.g. viridis, Set1,...). 10 | use_label 11 | Key for the label use in `adata.obs` (e.g. `leiden`, `louvain`,...). 12 | list_clusters 13 | A set of cluster to be displayed in the figure (e.g. [0,1,2,3]). 14 | ax 15 | A matplotlib axes object. 16 | show_plot 17 | Option to display the figure. 18 | show_image 19 | Option to display the H&E image. 20 | show_color_bar 21 | Option to display color bar. 22 | crop 23 | Option to crop the figure based on the spot locations. 24 | margin 25 | Margin to crop. 26 | size 27 | Spot size to display in figure. 28 | image_alpha 29 | Opacity of H&E image. 30 | cell_alpha 31 | Opacity of spots/cells. 32 | use_raw 33 | Option to use `adata.raw` data. 34 | fname 35 | Output path to the output if user want to save the figure. 36 | dpi 37 | Dots per inch values for the output. 38 | """ 39 | 40 | doc_gene_plot = """\ 41 | gene_symbols 42 | Single gene (str) or multiple genes (list) that user wants to display. It should be available in `adata.var_names`. 43 | threshold 44 | Threshold to display genes in the figure. 45 | method 46 | Method to combine multiple genes: 47 | `'CumSum'` is cummulative sum of genes expression values, 48 | `'NaiveMean'` is the mean of the genes expression values. 49 | contour 50 | Option to show the contour plot. 51 | step_size 52 | Determines the number and positions of the contour lines / regions. 53 | """ 54 | 55 | doc_cluster_plot = """\ 56 | show_subcluster 57 | Display the subcluster in the figure. 58 | show_cluster_labels 59 | Display the labels of clusters. 60 | show_trajectories 61 | Display the spatial trajectory analysis results. 62 | reverse 63 | Reverse the direction of spatial trajectories. 64 | show_node 65 | Show node of PAGA graph mapping in spatial. 66 | threshold_spots 67 | The number of spots threshold for not display the subcluster labels 68 | text_box_size 69 | The font size in the box of labels. 70 | color_bar_size 71 | The size of color bar. 72 | bbox_to_anchor 73 | Set the position of box of color bar. Default is `(1,1)` 74 | """ 75 | 76 | doc_lr_plot = """\ 77 | adata 78 | AnnData object with run st.tl.cci_rank.run performed on. 79 | lr 80 | Ligand receptor paid (in format L_R) 81 | min_expr 82 | Minimum expression for gene to be considered expressed. 83 | sig_spots 84 | Whether to filter to significant spots or not. 85 | use_label 86 | Label to use for the inner points, can be in adata.obs or in the lr stats of adata.uns['per_lr_results'][lr].columns 87 | use_mix 88 | The deconvolution/label_transfer results to use for visualising pie charts in the inner point, not currently implimented. 89 | outer_mode 90 | Either 'binary', 'continuous', or None; controls how ligand-receptor expression shown (or not shown). 91 | l_cmap 92 | matplotlib cmap controlling ligand continous expression. 93 | r_cmap 94 | matplotlib cmap controlling receptor continuous expression. 95 | lr_cmap 96 | matplotlib cmap controlling the ligand receptor binary expression, but have atleast 4 colours. 97 | inner_cmap 98 | matplotlib cmap controlling the inner point colours. 99 | inner_size_prop 100 | multiplier which controls size of inner points. 101 | middle_size_prop 102 | Multiplier which controls size of middle point (only relevant when outer_mode='continuous') 103 | outer_size_prop 104 | Multiplier which controls size of the outter point. 105 | pt_scale 106 | Multiplier which scales overall point size of all points plotted. 107 | title 108 | Title of the plot. 109 | show_image 110 | Whether to show the background H&E or not. 111 | kwargs 112 | Extra arguments parsed to the other plotting functions such as gene_plot, cluster_plot, &/or het_plot. 113 | """ 114 | 115 | doc_het_plot = """\ 116 | use_het 117 | Single gene (str) or multiple genes (list) that user wants to display. It should be available in `adata.var_names`. 118 | contour 119 | Option to show the contour plot. 120 | step_size 121 | Determines the number and positions of the contour lines / regions. 122 | vmin 123 | Lower end of scale bar. 124 | vmax 125 | Upper end of scale bar. 126 | """ 127 | 128 | doc_subcluster_plot = """\ 129 | cluster 130 | Choose cluster to plot the sub-clusters. 131 | text_box_size 132 | The font size in the box of labels. 133 | bbox_to_anchor 134 | Set the position of box of color bar. Default is `(1,1)` 135 | """ 136 | -------------------------------------------------------------------------------- /stlearn/plotting/cluster_plot.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | from PIL import Image 3 | import pandas as pd 4 | import matplotlib 5 | import numpy as np 6 | import networkx as nx 7 | 8 | from typing import Optional, Union, Mapping # Special 9 | from typing import Sequence, Iterable # ABCs 10 | from typing import Tuple # Classes 11 | 12 | from anndata import AnnData 13 | import warnings 14 | 15 | from stlearn.plotting.classes import ClusterPlot 16 | from stlearn.plotting.classes_bokeh import BokehClusterPlot 17 | from stlearn.plotting._docs import doc_spatial_base_plot, doc_cluster_plot 18 | from stlearn.utils import _AxesSubplot, Axes, _docs_params 19 | 20 | from bokeh.io import push_notebook, output_notebook 21 | from bokeh.plotting import show 22 | 23 | 24 | @_docs_params(spatial_base_plot=doc_spatial_base_plot, cluster_plot=doc_cluster_plot) 25 | def cluster_plot( 26 | adata: AnnData, 27 | # plotting param 28 | title: Optional["str"] = None, 29 | figsize: Optional[Tuple[float, float]] = None, 30 | cmap: Optional[str] = "default", 31 | use_label: Optional[str] = None, 32 | list_clusters: Optional[list] = None, 33 | ax: Optional[matplotlib.axes.Axes] = None, 34 | fig: Optional[matplotlib.figure.Figure] = None, 35 | show_plot: Optional[bool] = True, 36 | show_axis: Optional[bool] = False, 37 | show_image: Optional[bool] = True, 38 | show_color_bar: Optional[bool] = True, 39 | zoom_coord: Optional[float] = None, 40 | crop: Optional[bool] = True, 41 | margin: Optional[bool] = 100, 42 | size: Optional[float] = 5, 43 | image_alpha: Optional[float] = 1.0, 44 | cell_alpha: Optional[float] = 1.0, 45 | fname: Optional[str] = None, 46 | dpi: Optional[int] = 120, 47 | # cluster plot param 48 | show_subcluster: Optional[bool] = False, 49 | show_cluster_labels: Optional[bool] = False, 50 | show_trajectories: Optional[bool] = False, 51 | reverse: Optional[bool] = False, 52 | show_node: Optional[bool] = False, 53 | threshold_spots: Optional[int] = 5, 54 | text_box_size: Optional[float] = 5, 55 | color_bar_size: Optional[float] = 10, 56 | bbox_to_anchor: Optional[Tuple[float, float]] = (1, 1), 57 | # trajectory 58 | trajectory_node_size: Optional[int] = 10, 59 | trajectory_alpha: Optional[float] = 1.0, 60 | trajectory_width: Optional[float] = 2.5, 61 | trajectory_edge_color: Optional[str] = "#f4efd3", 62 | trajectory_arrowsize: Optional[int] = 17, 63 | ) -> Optional[AnnData]: 64 | 65 | """\ 66 | Allows the visualization of a cluster results as the discretes values 67 | of dot points in the Spatial transcriptomics array. We also support to 68 | visualize the spatial trajectory results 69 | 70 | 71 | Parameters 72 | ------------------------------------- 73 | {spatial_base_plot} 74 | {cluster_plot} 75 | 76 | Examples 77 | ------------------------------------- 78 | >>> import stlearn as st 79 | >>> adata = st.datasets.example_bcba() 80 | >>> label = "louvain" 81 | >>> st.pl.cluster_plot(adata, use_label = label, show_trajectories = True) 82 | 83 | """ 84 | 85 | assert use_label != None, "Please select `use_label` parameter" 86 | 87 | ClusterPlot( 88 | adata, 89 | title=title, 90 | figsize=figsize, 91 | cmap=cmap, 92 | use_label=use_label, 93 | list_clusters=list_clusters, 94 | ax=ax, 95 | fig=fig, 96 | show_plot=show_plot, 97 | show_axis=show_axis, 98 | show_image=show_image, 99 | show_color_bar=show_color_bar, 100 | zoom_coord=zoom_coord, 101 | crop=crop, 102 | margin=margin, 103 | size=size, 104 | image_alpha=image_alpha, 105 | cell_alpha=cell_alpha, 106 | fname=fname, 107 | dpi=dpi, 108 | show_subcluster=show_subcluster, 109 | show_cluster_labels=show_cluster_labels, 110 | show_trajectories=show_trajectories, 111 | reverse=reverse, 112 | show_node=show_node, 113 | threshold_spots=threshold_spots, 114 | text_box_size=text_box_size, 115 | color_bar_size=color_bar_size, 116 | bbox_to_anchor=bbox_to_anchor, 117 | trajectory_node_size=trajectory_node_size, 118 | trajectory_alpha=trajectory_alpha, 119 | trajectory_width=trajectory_width, 120 | trajectory_edge_color=trajectory_edge_color, 121 | trajectory_arrowsize=trajectory_arrowsize, 122 | ) 123 | 124 | 125 | def cluster_plot_interactive( 126 | adata: AnnData, 127 | ): 128 | 129 | bokeh_object = BokehClusterPlot(adata) 130 | output_notebook() 131 | show(bokeh_object.app, notebook_handle=True) 132 | -------------------------------------------------------------------------------- /stlearn/plotting/feat_plot.py: -------------------------------------------------------------------------------- 1 | """ 2 | Plotting of continuous features stored in adata.obs. 3 | """ 4 | 5 | from matplotlib import pyplot as plt 6 | from PIL import Image 7 | import pandas as pd 8 | import matplotlib 9 | import numpy as np 10 | 11 | from typing import Optional, Union, Mapping # Special 12 | from typing import Sequence, Iterable # ABCs 13 | from typing import Tuple # Classes 14 | 15 | from anndata import AnnData 16 | import warnings 17 | 18 | from stlearn.plotting.classes import FeaturePlot 19 | from stlearn.plotting.classes_bokeh import BokehGenePlot 20 | from stlearn.plotting._docs import doc_spatial_base_plot, doc_gene_plot 21 | from stlearn.utils import Empty, _empty, _AxesSubplot, _docs_params 22 | 23 | from bokeh.io import push_notebook, output_notebook 24 | from bokeh.plotting import show 25 | 26 | # @_docs_params(spatial_base_plot=doc_spatial_base_plot, gene_plot=doc_gene_plot) 27 | def feat_plot( 28 | adata: AnnData, 29 | feature: str = None, 30 | threshold: Optional[float] = None, 31 | contour: bool = False, 32 | step_size: Optional[int] = None, 33 | title: Optional["str"] = None, 34 | figsize: Optional[Tuple[float, float]] = None, 35 | cmap: Optional[str] = "Spectral_r", 36 | use_label: Optional[str] = None, 37 | list_clusters: Optional[list] = None, 38 | ax: Optional[matplotlib.axes.Axes] = None, 39 | fig: Optional[matplotlib.figure.Figure] = None, 40 | show_plot: Optional[bool] = True, 41 | show_axis: Optional[bool] = False, 42 | show_image: Optional[bool] = True, 43 | show_color_bar: Optional[bool] = True, 44 | color_bar_label: Optional[str] = "", 45 | zoom_coord: Optional[float] = None, 46 | crop: Optional[bool] = True, 47 | margin: Optional[bool] = 100, 48 | size: Optional[float] = 7, 49 | image_alpha: Optional[float] = 1.0, 50 | cell_alpha: Optional[float] = 0.7, 51 | use_raw: Optional[bool] = False, 52 | fname: Optional[str] = None, 53 | dpi: Optional[int] = 120, 54 | vmin: Optional[float] = None, 55 | vmax: Optional[float] = None, 56 | ) -> Optional[AnnData]: 57 | """\ 58 | Allows the visualization of a continuous features stored in adata.obs 59 | for Spatial transcriptomics array. 60 | 61 | 62 | Parameters 63 | ------------------------------------- 64 | {spatial_base_plot} 65 | {feature_plot} 66 | 67 | Examples 68 | ------------------------------------- 69 | >>> import stlearn as st 70 | >>> adata = st.datasets.example_bcba() 71 | >>> st.pl.gene_plot(adata, 'dpt_pseudotime') 72 | 73 | """ 74 | FeaturePlot( 75 | adata, 76 | feature=feature, 77 | threshold=threshold, 78 | contour=contour, 79 | step_size=step_size, 80 | title=title, 81 | figsize=figsize, 82 | cmap=cmap, 83 | use_label=use_label, 84 | list_clusters=list_clusters, 85 | ax=ax, 86 | fig=fig, 87 | show_plot=show_plot, 88 | show_axis=show_axis, 89 | show_image=show_image, 90 | show_color_bar=show_color_bar, 91 | color_bar_label=color_bar_label, 92 | zoom_coord=zoom_coord, 93 | crop=crop, 94 | margin=margin, 95 | size=size, 96 | image_alpha=image_alpha, 97 | cell_alpha=cell_alpha, 98 | use_raw=use_raw, 99 | fname=fname, 100 | dpi=dpi, 101 | vmin=vmin, 102 | vmax=vmax, 103 | ) 104 | -------------------------------------------------------------------------------- /stlearn/plotting/gene_plot.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | from PIL import Image 3 | import pandas as pd 4 | import matplotlib 5 | import numpy as np 6 | 7 | from typing import Optional, Union, Mapping # Special 8 | from typing import Sequence, Iterable # ABCs 9 | from typing import Tuple # Classes 10 | 11 | from anndata import AnnData 12 | import warnings 13 | 14 | from stlearn.plotting.classes import GenePlot 15 | from stlearn.plotting.classes_bokeh import BokehGenePlot 16 | from stlearn.plotting._docs import doc_spatial_base_plot, doc_gene_plot 17 | from stlearn.utils import Empty, _empty, _AxesSubplot, _docs_params 18 | 19 | from bokeh.io import push_notebook, output_notebook 20 | from bokeh.plotting import show 21 | 22 | 23 | @_docs_params(spatial_base_plot=doc_spatial_base_plot, gene_plot=doc_gene_plot) 24 | def gene_plot( 25 | adata: AnnData, 26 | gene_symbols: Union[str, list] = None, 27 | threshold: Optional[float] = None, 28 | method: str = "CumSum", 29 | contour: bool = False, 30 | step_size: Optional[int] = None, 31 | title: Optional["str"] = None, 32 | figsize: Optional[Tuple[float, float]] = None, 33 | cmap: Optional[str] = "Spectral_r", 34 | use_label: Optional[str] = None, 35 | list_clusters: Optional[list] = None, 36 | ax: Optional[matplotlib.axes.Axes] = None, 37 | fig: Optional[matplotlib.figure.Figure] = None, 38 | show_plot: Optional[bool] = True, 39 | show_axis: Optional[bool] = False, 40 | show_image: Optional[bool] = True, 41 | show_color_bar: Optional[bool] = True, 42 | color_bar_label: Optional[str] = "", 43 | zoom_coord: Optional[float] = None, 44 | crop: Optional[bool] = True, 45 | margin: Optional[bool] = 100, 46 | size: Optional[float] = 7, 47 | image_alpha: Optional[float] = 1.0, 48 | cell_alpha: Optional[float] = 0.7, 49 | use_raw: Optional[bool] = False, 50 | fname: Optional[str] = None, 51 | dpi: Optional[int] = 120, 52 | vmin: Optional[float] = None, 53 | vmax: Optional[float] = None, 54 | ) -> Optional[AnnData]: 55 | """\ 56 | Allows the visualization of a single gene or multiple genes as the values 57 | of dot points or contour in the Spatial transcriptomics array. 58 | 59 | 60 | Parameters 61 | ------------------------------------- 62 | {spatial_base_plot} 63 | {gene_plot} 64 | 65 | Examples 66 | ------------------------------------- 67 | >>> import stlearn as st 68 | >>> adata = st.datasets.example_bcba() 69 | >>> genes = ["BRCA1","BRCA2"] 70 | >>> st.pl.gene_plot(adata, gene_symbols = genes) 71 | 72 | """ 73 | GenePlot( 74 | adata, 75 | gene_symbols=gene_symbols, 76 | threshold=threshold, 77 | method=method, 78 | contour=contour, 79 | step_size=step_size, 80 | title=title, 81 | figsize=figsize, 82 | cmap=cmap, 83 | use_label=use_label, 84 | list_clusters=list_clusters, 85 | ax=ax, 86 | fig=fig, 87 | show_plot=show_plot, 88 | show_axis=show_axis, 89 | show_image=show_image, 90 | show_color_bar=show_color_bar, 91 | color_bar_label=color_bar_label, 92 | zoom_coord=zoom_coord, 93 | crop=crop, 94 | margin=margin, 95 | size=size, 96 | image_alpha=image_alpha, 97 | cell_alpha=cell_alpha, 98 | use_raw=use_raw, 99 | fname=fname, 100 | dpi=dpi, 101 | vmin=vmin, 102 | vmax=vmax, 103 | ) 104 | 105 | 106 | def gene_plot_interactive(adata: AnnData): 107 | bokeh_object = BokehGenePlot(adata) 108 | output_notebook() 109 | show(bokeh_object.app, notebook_handle=True) 110 | -------------------------------------------------------------------------------- /stlearn/plotting/non_spatial_plot.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | from PIL import Image 3 | import pandas as pd 4 | import matplotlib 5 | import numpy as np 6 | 7 | from stlearn._compat import Literal 8 | from typing import Optional, Union 9 | from anndata import AnnData 10 | import warnings 11 | 12 | # from .utils import get_img_from_fig, checkType 13 | import scanpy 14 | 15 | 16 | def non_spatial_plot( 17 | adata: AnnData, 18 | use_label: str = "louvain", 19 | ) -> Optional[AnnData]: 20 | 21 | """\ 22 | A wrap function to plot all the non-spatial plot from scanpy. 23 | 24 | This function will produce 3 plots: PAGA graph, cluster plot in PAGA space and 25 | DPT in PAGA space. 26 | 27 | Parameters 28 | ---------- 29 | adata 30 | Annotated data matrix. 31 | use_label 32 | Use label result of cluster method. 33 | dpi 34 | Set dpi as the resolution for the plot. 35 | Returns 36 | ------- 37 | Nothing 38 | """ 39 | 40 | # plt.rcParams['figure.dpi'] = dpi 41 | 42 | if "paga" in adata.uns.keys(): 43 | # adata.uns[use_label+"_colors"] = adata.uns["tmp_color"] 44 | 45 | print("PAGA plot:") 46 | 47 | scanpy.pl.paga(adata, color=use_label) 48 | 49 | scanpy.tl.draw_graph(adata, init_pos="paga") 50 | # adata.uns[use_label+"_colors"] = adata.uns["tmp_color"] 51 | 52 | print("Gene expression (reduced dimension) plot:") 53 | scanpy.pl.draw_graph(adata, color=use_label, legend_loc="on data") 54 | 55 | print("Diffusion pseudotime plot:") 56 | scanpy.pl.draw_graph(adata, color="dpt_pseudotime") 57 | 58 | else: 59 | 60 | scanpy.pl.draw_graph(adata) 61 | # adata.uns[use_label+"_colors"] = adata.uns["tmp_color"] 62 | 63 | scanpy.pl.draw_graph(adata, color=use_label, legend_loc="on data") 64 | -------------------------------------------------------------------------------- /stlearn/plotting/palettes_st.py: -------------------------------------------------------------------------------- 1 | jana_40 = [ 2 | "#5C5C5C", 3 | "#969696", 4 | "#BEBEBE", 5 | "#8B0000", 6 | "#EE5C42", 7 | "#8B3E2F", 8 | "#CD3700", 9 | "#FF7F24", 10 | "#F4A460", 11 | "#FFA500", 12 | "#8B5A00", 13 | "#8B8B00", 14 | "#FFFF00", 15 | "#9ACD32", 16 | "#ADFF2F", 17 | "#458B00", 18 | "#90EE90", 19 | "#32CD32", 20 | "#008B45", 21 | "#7FFFD4", 22 | "#458B74", 23 | "#008B8B", 24 | "#5F9EA0", 25 | "#00C5CD", 26 | "#009ACD", 27 | "#00688B", 28 | "#B0E2FF", 29 | "#4682B4", 30 | "#1E90FF", 31 | "#000080", 32 | "#0000CD", 33 | "#8470FF", 34 | "#7B68EE", 35 | "#9B30FF", 36 | "#BF3EFF", 37 | "#EED2EE", 38 | "#8B668B", 39 | "#EE82EE", 40 | "#8B008B", 41 | "#D02090", 42 | "#FF1493", 43 | ] 44 | 45 | default = [ 46 | "#1f77b4", 47 | "#ff7f0e", 48 | "#279e68", 49 | "#d62728", 50 | "#633194", 51 | "#8c564b", 52 | "#F73BAD", 53 | "#F6E800", 54 | "#01F7F7", 55 | "#aec7e8", 56 | "#ffbb78", 57 | "#98df8a", 58 | "#ff9896", 59 | "#c5b0d5", 60 | "#c49c94", 61 | "#f7b6d2", 62 | "#dbdb8d", 63 | "#9edae5", 64 | "#ad494a", 65 | "#8c6d31", 66 | ] 67 | -------------------------------------------------------------------------------- /stlearn/plotting/stack_3d_plot.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | import pandas as pd 4 | 5 | 6 | def stack_3d_plot( 7 | adata: AnnData, 8 | slides, 9 | cmap="viridis", 10 | slide_col="sample_id", 11 | use_label=None, 12 | gene_symbol=None, 13 | ) -> Optional[AnnData]: 14 | 15 | """\ 16 | Clustering plot for sptial transcriptomics data. Also it has a function to display trajectory inference. 17 | 18 | Parameters 19 | ---------- 20 | adata 21 | Annotated data matrix. 22 | slides 23 | A list of slide id 24 | cmap 25 | Color map 26 | use_label 27 | Choose label to plot (priotize) 28 | gene_symbol 29 | Choose gene symbol to plot 30 | width 31 | Witdh of the plot 32 | height 33 | Height of the plot 34 | Returns 35 | ------- 36 | Nothing 37 | """ 38 | try: 39 | import plotly.express as px 40 | except ModuleNotFoundError: 41 | raise ModuleNotFoundError("Please install plotly by `pip install plotly`") 42 | 43 | assert ( 44 | slide_col in adata.obs.columns 45 | ), "Please provide the right column for slide_id!" 46 | 47 | list_df = [] 48 | for i, slide in enumerate(slides): 49 | tmp = data.obs[data.obs[slide_col] == slide][["imagecol", "imagerow"]] 50 | tmp["sample_id"] = slide 51 | tmp["z-dimension"] = i 52 | list_df.append(tmp) 53 | 54 | df = pd.concat(list_df) 55 | 56 | if use_label != None: 57 | assert use_label in adata.obs.columns, "Please use the right `use_label`" 58 | df[use_label] = adata[df.index].obs[use_label].values 59 | 60 | fig = px.scatter_3d( 61 | df, 62 | x="imagecol", 63 | y="imagerow", 64 | z="z-dimension", 65 | color=use_label, 66 | width=width, 67 | height=height, 68 | color_continuous_scale=cmap, 69 | ) 70 | fig.show() 71 | 72 | else: 73 | assert gene_symbol in adata.var_names, "Please use the right `gene_symbol`" 74 | df[gene_symbol] = adata[df.index][:, gene_symbol].X 75 | 76 | fig = px.scatter_3d( 77 | df, 78 | x="imagecol", 79 | y="imagerow", 80 | z="z-dimension", 81 | color=gene_symbol, 82 | width=width, 83 | height=height, 84 | color_continuous_scale=cmap, 85 | ) 86 | fig.show() 87 | -------------------------------------------------------------------------------- /stlearn/plotting/subcluster_plot.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | from PIL import Image 3 | import pandas as pd 4 | import matplotlib 5 | import numpy as np 6 | 7 | from typing import Optional, Union, Mapping # Special 8 | from typing import Sequence, Iterable # ABCs 9 | from typing import Tuple # Classes 10 | 11 | from anndata import AnnData 12 | import warnings 13 | 14 | from stlearn.plotting.classes import SubClusterPlot 15 | from stlearn.plotting._docs import doc_spatial_base_plot, doc_subcluster_plot 16 | from stlearn.utils import _AxesSubplot, Axes, _docs_params 17 | 18 | 19 | @_docs_params( 20 | spatial_base_plot=doc_spatial_base_plot, subcluster_plot=doc_subcluster_plot 21 | ) 22 | def subcluster_plot( 23 | adata: AnnData, 24 | # plotting param 25 | title: Optional["str"] = None, 26 | figsize: Optional[Tuple[float, float]] = None, 27 | cmap: Optional[str] = "jet", 28 | use_label: Optional[str] = None, 29 | list_clusters: Optional[list] = None, 30 | ax: Optional[_AxesSubplot] = None, 31 | show_plot: Optional[bool] = True, 32 | show_axis: Optional[bool] = False, 33 | show_image: Optional[bool] = True, 34 | show_color_bar: Optional[bool] = True, 35 | crop: Optional[bool] = True, 36 | margin: Optional[bool] = 100, 37 | size: Optional[float] = 5, 38 | image_alpha: Optional[float] = 1.0, 39 | cell_alpha: Optional[float] = 1.0, 40 | fname: Optional[str] = None, 41 | dpi: Optional[int] = 120, 42 | # subcluster plot param 43 | cluster: Optional[int] = 0, 44 | threshold_spots: Optional[int] = 5, 45 | text_box_size: Optional[float] = 5, 46 | bbox_to_anchor: Optional[Tuple[float, float]] = (1, 1), 47 | ) -> Optional[AnnData]: 48 | """\ 49 | Allows the visualization of a subclustering results as the discretes values 50 | of dot points in the Spatial transcriptomics array. 51 | 52 | Parameters 53 | ------------------------------------- 54 | {spatial_base_plot} 55 | {subcluster_plot} 56 | 57 | Examples 58 | ------------------------------------- 59 | >>> import stlearn as st 60 | >>> adata = st.datasets.example_bcba() 61 | >>> label = "louvain" 62 | >>> cluster = 6 63 | >>> st.pl.cluster_plot(adata, use_label = label, cluster = cluster) 64 | 65 | """ 66 | 67 | assert use_label != None, "Please select `use_label` parameter" 68 | assert ( 69 | use_label in adata.obs.columns 70 | ), "Please run `stlearn.spatial.cluster.localization` function!" 71 | 72 | SubClusterPlot( 73 | adata, 74 | title=title, 75 | figsize=figsize, 76 | cmap=cmap, 77 | use_label=use_label, 78 | list_clusters=list_clusters, 79 | ax=ax, 80 | show_plot=show_plot, 81 | show_axis=show_axis, 82 | show_image=show_image, 83 | show_color_bar=show_color_bar, 84 | crop=crop, 85 | margin=margin, 86 | size=size, 87 | image_alpha=image_alpha, 88 | cell_alpha=cell_alpha, 89 | fname=fname, 90 | dpi=dpi, 91 | text_box_size=text_box_size, 92 | bbox_to_anchor=bbox_to_anchor, 93 | cluster=cluster, 94 | threshold_spots=threshold_spots, 95 | ) 96 | -------------------------------------------------------------------------------- /stlearn/plotting/trajectory/__init__.py: -------------------------------------------------------------------------------- 1 | from .pseudotime_plot import pseudotime_plot 2 | from .local_plot import local_plot 3 | from .tree_plot_simple import tree_plot_simple 4 | from .tree_plot import tree_plot 5 | from .transition_markers_plot import transition_markers_plot 6 | from .DE_transition_plot import DE_transition_plot 7 | from .check_trajectory import check_trajectory 8 | -------------------------------------------------------------------------------- /stlearn/plotting/trajectory/check_trajectory.py: -------------------------------------------------------------------------------- 1 | from anndata import AnnData 2 | from typing import Optional, Union 3 | import matplotlib.pyplot as plt 4 | import scanpy as sc 5 | import numpy as np 6 | 7 | 8 | def check_trajectory( 9 | adata: AnnData, 10 | library_id: str = None, 11 | use_label: str = "louvain", 12 | basis: str = "umap", 13 | pseudotime_key: str = "dpt_pseudotime", 14 | trajectory: list = None, 15 | figsize=(10, 4), 16 | size_umap: int = 50, 17 | size_spatial: int = 1.5, 18 | img_key: str = "hires", 19 | ) -> Optional[AnnData]: 20 | trajectory = np.array(trajectory).astype(int) 21 | assert ( 22 | trajectory in adata.uns["available_paths"].values() 23 | ), "Please choose the right path!" 24 | trajectory = trajectory.astype(str) 25 | assert ( 26 | pseudotime_key in adata.obs.columns 27 | ), "Please run the pseudotime or choose the right one!" 28 | assert ( 29 | use_label in adata.obs.columns 30 | ), "Please run the cluster or choose the right label!" 31 | assert basis in adata.obsm, ( 32 | "Please run the " + basis + "before you check the trajectory!" 33 | ) 34 | if library_id is None: 35 | library_id = list(adata.uns["spatial"].keys())[0] 36 | 37 | adata.obsm["X_spatial"] = adata.obs[["imagecol", "imagerow"]].values 38 | 39 | fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize) 40 | 41 | ax1 = sc.pl.umap(adata, size=size_umap, show=False, ax=ax1) 42 | sc.pl.umap( 43 | adata[adata.obs[use_label].isin(trajectory)], 44 | size=size_umap, 45 | color=pseudotime_key, 46 | ax=ax1, 47 | show=False, 48 | frameon=False, 49 | ) 50 | 51 | ax2 = sc.pl.scatter( 52 | adata, 53 | size=25, 54 | show=False, 55 | basis="spatial", 56 | ax=ax2, 57 | ) 58 | sc.pl.spatial( 59 | adata[adata.obs[use_label].isin(trajectory)], 60 | size=size_spatial, 61 | ax=ax2, 62 | color=pseudotime_key, 63 | legend_loc="none", 64 | basis="spatial", 65 | frameon=False, 66 | show=False, 67 | ) 68 | 69 | im = ax2.imshow( 70 | adata.uns["spatial"][library_id]["images"][img_key], alpha=0, zorder=-1 71 | ) 72 | 73 | plt.show() 74 | 75 | del adata.obsm["X_spatial"] 76 | -------------------------------------------------------------------------------- /stlearn/plotting/trajectory/utils.py: -------------------------------------------------------------------------------- 1 | def checkType(arr, n=2): 2 | 3 | # If the first two and the last two elements 4 | # of the array are in increasing order 5 | if arr[0] <= arr[1] and arr[n - 2] <= arr[n - 1]: 6 | return True 7 | 8 | # If the first two and the last two elements 9 | # of the array are in decreasing order 10 | elif arr[0] >= arr[1] and arr[n - 2] >= arr[n - 1]: 11 | return False 12 | -------------------------------------------------------------------------------- /stlearn/pp.py: -------------------------------------------------------------------------------- 1 | from .preprocessing.filter_genes import filter_genes 2 | from .preprocessing.normalize import normalize_total 3 | from .preprocessing.log_scale import log1p 4 | from .preprocessing.log_scale import scale 5 | from .preprocessing.graph import neighbors 6 | from .image_preprocessing.image_tiling import tiling 7 | from .image_preprocessing.feature_extractor import extract_feature 8 | -------------------------------------------------------------------------------- /stlearn/preprocessing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/preprocessing/__init__.py -------------------------------------------------------------------------------- /stlearn/preprocessing/filter_genes.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional, Tuple, Collection, Sequence, Iterable 2 | from anndata import AnnData 3 | import numpy as np 4 | from scipy.sparse import issparse, isspmatrix_csr, csr_matrix, spmatrix 5 | import scanpy 6 | 7 | 8 | def filter_genes( 9 | adata: AnnData, 10 | min_counts: Optional[int] = None, 11 | min_cells: Optional[int] = None, 12 | max_counts: Optional[int] = None, 13 | max_cells: Optional[int] = None, 14 | inplace: bool = True, 15 | ) -> Union[AnnData, None, Tuple[np.ndarray, np.ndarray]]: 16 | """\ 17 | Wrap function scanpy.pp.filter_genes 18 | 19 | Filter genes based on number of cells or counts. 20 | Keep genes that have at least `min_counts` counts or are expressed in at 21 | least `min_cells` cells or have at most `max_counts` counts or are expressed 22 | in at most `max_cells` cells. 23 | Only provide one of the optional parameters `min_counts`, `min_cells`, 24 | `max_counts`, `max_cells` per call. 25 | Parameters 26 | ---------- 27 | data 28 | An annotated data matrix of shape `n_obs` × `n_vars`. Rows correspond 29 | to cells and columns to genes. 30 | min_counts 31 | Minimum number of counts required for a gene to pass filtering. 32 | min_cells 33 | Minimum number of cells expressed required for a gene to pass filtering. 34 | max_counts 35 | Maximum number of counts required for a gene to pass filtering. 36 | max_cells 37 | Maximum number of cells expressed required for a gene to pass filtering. 38 | inplace 39 | Perform computation inplace or return result. 40 | Returns 41 | ------- 42 | Depending on `inplace`, returns the following arrays or directly subsets 43 | and annotates the data matrix 44 | gene_subset 45 | Boolean index mask that does filtering. `True` means that the 46 | gene is kept. `False` means the gene is removed. 47 | number_per_gene 48 | Depending on what was tresholded (`counts` or `cells`), the array stores 49 | `n_counts` or `n_cells` per gene. 50 | """ 51 | 52 | scanpy.pp.filter_genes( 53 | adata, 54 | min_counts=min_counts, 55 | min_cells=min_cells, 56 | max_counts=max_counts, 57 | max_cells=max_cells, 58 | inplace=inplace, 59 | ) 60 | -------------------------------------------------------------------------------- /stlearn/preprocessing/graph.py: -------------------------------------------------------------------------------- 1 | from types import MappingProxyType 2 | from typing import Union, Optional, Any, Mapping, Callable 3 | 4 | import numpy as np 5 | import scipy 6 | from anndata import AnnData 7 | from numpy.random import RandomState 8 | from .._compat import Literal 9 | import scanpy 10 | 11 | _Method = Literal["umap", "gauss", "rapids"] 12 | _MetricFn = Callable[[np.ndarray, np.ndarray], float] 13 | # from sklearn.metrics.pairwise_distances.__doc__: 14 | _MetricSparseCapable = Literal[ 15 | "cityblock", "cosine", "euclidean", "l1", "l2", "manhattan" 16 | ] 17 | _MetricScipySpatial = Literal[ 18 | "braycurtis", 19 | "canberra", 20 | "chebyshev", 21 | "correlation", 22 | "dice", 23 | "hamming", 24 | "jaccard", 25 | "kulsinski", 26 | "mahalanobis", 27 | "minkowski", 28 | "rogerstanimoto", 29 | "russellrao", 30 | "seuclidean", 31 | "sokalmichener", 32 | "sokalsneath", 33 | "sqeuclidean", 34 | "yule", 35 | ] 36 | _Metric = Union[_MetricSparseCapable, _MetricScipySpatial] 37 | 38 | 39 | def neighbors( 40 | adata: AnnData, 41 | n_neighbors: int = 15, 42 | n_pcs: Optional[int] = None, 43 | use_rep: Optional[str] = None, 44 | knn: bool = True, 45 | random_state: Optional[Union[int, RandomState]] = 0, 46 | method: Optional[_Method] = "umap", 47 | metric: Union[_Metric, _MetricFn] = "euclidean", 48 | metric_kwds: Mapping[str, Any] = MappingProxyType({}), 49 | copy: bool = False, 50 | ) -> Optional[AnnData]: 51 | """\ 52 | Compute a neighborhood graph of observations [McInnes18]_. 53 | The neighbor search efficiency of this heavily relies on UMAP [McInnes18]_, 54 | which also provides a method for estimating connectivities of data points - 55 | the connectivity of the manifold (`method=='umap'`). If `method=='gauss'`, 56 | connectivities are computed according to [Coifman05]_, in the adaption of 57 | [Haghverdi16]_. 58 | Parameters 59 | ---------- 60 | adata 61 | Annotated data matrix. 62 | n_neighbors 63 | The size of local neighborhood (in terms of number of neighboring data 64 | points) used for manifold approximation. Larger values result in more 65 | global views of the manifold, while smaller values result in more local 66 | data being preserved. In general values should be in the range 2 to 100. 67 | If `knn` is `True`, number of nearest neighbors to be searched. If `knn` 68 | is `False`, a Gaussian kernel width is set to the distance of the 69 | `n_neighbors` neighbor. 70 | {n_pcs} 71 | {use_rep} 72 | knn 73 | If `True`, use a hard threshold to restrict the number of neighbors to 74 | `n_neighbors`, that is, consider a knn graph. Otherwise, use a Gaussian 75 | Kernel to assign low weights to neighbors more distant than the 76 | `n_neighbors` nearest neighbor. 77 | random_state 78 | A numpy random seed. 79 | method 80 | Use 'umap' [McInnes18]_ or 'gauss' (Gauss kernel following [Coifman05]_ 81 | with adaptive width [Haghverdi16]_) for computing connectivities. 82 | Use 'rapids' for the RAPIDS implementation of UMAP (experimental, GPU 83 | only). 84 | metric 85 | A known metric’s name or a callable that returns a distance. 86 | metric_kwds 87 | Options for the metric. 88 | copy 89 | Return a copy instead of writing to adata. 90 | Returns 91 | ------- 92 | Depending on `copy`, updates or returns `adata` with the following: 93 | **connectivities** : sparse matrix (`.uns['neighbors']`, dtype `float32`) 94 | Weighted adjacency matrix of the neighborhood graph of data 95 | points. Weights should be interpreted as connectivities. 96 | **distances** : sparse matrix (`.uns['neighbors']`, dtype `float32`) 97 | Instead of decaying weights, this stores distances for each pair of 98 | neighbors. 99 | """ 100 | 101 | scanpy.pp.neighbors( 102 | adata, 103 | n_neighbors=n_neighbors, 104 | n_pcs=n_pcs, 105 | use_rep=use_rep, 106 | knn=knn, 107 | random_state=random_state, 108 | method=method, 109 | metric=metric, 110 | metric_kwds=metric_kwds, 111 | copy=copy, 112 | ) 113 | 114 | print("Created k-Nearest-Neighbor graph in adata.uns['neighbors'] ") 115 | -------------------------------------------------------------------------------- /stlearn/preprocessing/log_scale.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional, Tuple, Collection, Sequence, Iterable 2 | from anndata import AnnData 3 | import numpy as np 4 | from scipy.sparse import issparse, isspmatrix_csr, csr_matrix, spmatrix 5 | from scipy import sparse 6 | from stlearn import logging as logg 7 | import scanpy 8 | 9 | 10 | def log1p( 11 | adata: Union[AnnData, np.ndarray, spmatrix], 12 | copy: bool = False, 13 | chunked: bool = False, 14 | chunk_size: Optional[int] = None, 15 | base: Optional[float] = None, 16 | ) -> Optional[AnnData]: 17 | """\ 18 | Wrap function of scanpy.pp.log1p 19 | Copyright (c) 2017 F. Alexander Wolf, P. Angerer, Theis Lab 20 | 21 | Logarithmize the data matrix. 22 | Computes :math:`X = \\log(X + 1)`, 23 | where :math:`log` denotes the natural logarithm unless a different base is given. 24 | Parameters 25 | ---------- 26 | data 27 | The (annotated) data matrix of shape `n_obs` × `n_vars`. 28 | Rows correspond to cells and columns to genes. 29 | copy 30 | If an :class:`~anndata.AnnData` is passed, determines whether a copy 31 | is returned. 32 | chunked 33 | Process the data matrix in chunks, which will save memory. 34 | Applies only to :class:`~anndata.AnnData`. 35 | chunk_size 36 | `n_obs` of the chunks to process the data in. 37 | base 38 | Base of the logarithm. Natural logarithm is used by default. 39 | Returns 40 | ------- 41 | Returns or updates `data`, depending on `copy`. 42 | """ 43 | 44 | scanpy.pp.log1p(adata, copy=copy, chunked=chunked, chunk_size=chunk_size, base=base) 45 | 46 | print("Log transformation step is finished in adata.X") 47 | 48 | 49 | def scale( 50 | adata: Union[AnnData, np.ndarray, spmatrix], 51 | zero_center: bool = True, 52 | max_value: Optional[float] = None, 53 | copy: bool = False, 54 | ) -> Optional[AnnData]: 55 | """\ 56 | Wrap function of scanpy.pp.scale 57 | 58 | Scale data to unit variance and zero mean. 59 | .. note:: 60 | Variables (genes) that do not display any variation (are constant across 61 | all observations) are retained and set to 0 during this operation. In 62 | the future, they might be set to NaNs. 63 | Parameters 64 | ---------- 65 | data 66 | The (annotated) data matrix of shape `n_obs` × `n_vars`. 67 | Rows correspond to cells and columns to genes. 68 | zero_center 69 | If `False`, omit zero-centering variables, which allows to handle sparse 70 | input efficiently. 71 | max_value 72 | Clip (truncate) to this value after scaling. If `None`, do not clip. 73 | copy 74 | If an :class:`~anndata.AnnData` is passed, 75 | determines whether a copy is returned. 76 | Returns 77 | ------- 78 | Depending on `copy` returns or updates `adata` with a scaled `adata.X`. 79 | """ 80 | 81 | scanpy.pp.scale(adata, zero_center=zero_center, max_value=max_value, copy=copy) 82 | 83 | print("Scale step is finished in adata.X") 84 | -------------------------------------------------------------------------------- /stlearn/preprocessing/normalize.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union, Iterable, Dict 2 | 3 | import numpy as np 4 | from anndata import AnnData 5 | from scipy.sparse import issparse 6 | from sklearn.utils import sparsefuncs 7 | from stlearn._compat import Literal 8 | import scanpy 9 | 10 | 11 | def normalize_total( 12 | adata: AnnData, 13 | target_sum: Optional[float] = None, 14 | exclude_highly_expressed: bool = False, 15 | max_fraction: float = 0.05, 16 | key_added: Optional[str] = None, 17 | layers: Union[Literal["all"], Iterable[str]] = None, 18 | layer_norm: Optional[str] = None, 19 | inplace: bool = True, 20 | ) -> Optional[Dict[str, np.ndarray]]: 21 | """\ 22 | Wrap function from scanpy.pp.log1p 23 | Normalize counts per cell. 24 | If choosing `target_sum=1e6`, this is CPM normalization. 25 | If `exclude_highly_expressed=True`, very highly expressed genes are excluded 26 | from the computation of the normalization factor (size factor) for each 27 | cell. This is meaningful as these can strongly influence the resulting 28 | normalized values for all other genes [Weinreb17]_. 29 | Similar functions are used, for example, by Seurat [Satija15]_, Cell Ranger 30 | [Zheng17]_ or SPRING [Weinreb17]_. 31 | Params 32 | ------ 33 | adata 34 | The annotated data matrix of shape `n_obs` × `n_vars`. 35 | Rows correspond to cells and columns to genes. 36 | target_sum 37 | If `None`, after normalization, each observation (cell) has a total 38 | count equal to the median of total counts for observations (cells) 39 | before normalization. 40 | exclude_highly_expressed 41 | Exclude (very) highly expressed genes for the computation of the 42 | normalization factor (size factor) for each cell. A gene is considered 43 | highly expressed, if it has more than `max_fraction` of the total counts 44 | in at least one cell. The not-excluded genes will sum up to 45 | `target_sum`. 46 | max_fraction 47 | If `exclude_highly_expressed=True`, consider cells as highly expressed 48 | that have more counts than `max_fraction` of the original total counts 49 | in at least one cell. 50 | key_added 51 | Name of the field in `adata.obs` where the normalization factor is 52 | stored. 53 | layers 54 | List of layers to normalize. Set to `'all'` to normalize all layers. 55 | layer_norm 56 | Specifies how to normalize layers: 57 | * If `None`, after normalization, for each layer in *layers* each cell 58 | has a total count equal to the median of the *counts_per_cell* before 59 | normalization of the layer. 60 | * If `'after'`, for each layer in *layers* each cell has 61 | a total count equal to `target_sum`. 62 | * If `'X'`, for each layer in *layers* each cell has a total count 63 | equal to the median of total counts for observations (cells) of 64 | `adata.X` before normalization. 65 | inplace 66 | Whether to update `adata` or return dictionary with normalized copies of 67 | `adata.X` and `adata.layers`. 68 | Returns 69 | ------- 70 | Returns dictionary with normalized copies of `adata.X` and `adata.layers` 71 | or updates `adata` with normalized version of the original 72 | `adata.X` and `adata.layers`, depending on `inplace`. 73 | """ 74 | 75 | scanpy.pp.normalize_total( 76 | adata, 77 | target_sum=target_sum, 78 | exclude_highly_expressed=exclude_highly_expressed, 79 | max_fraction=max_fraction, 80 | key_added=key_added, 81 | layers=layers, 82 | layer_norm=layer_norm, 83 | inplace=inplace, 84 | ) 85 | 86 | print("Normalization step is finished in adata.X") 87 | -------------------------------------------------------------------------------- /stlearn/spatial.py: -------------------------------------------------------------------------------- 1 | from .spatials import clustering 2 | from .spatials import smooth 3 | from .spatials import trajectory 4 | from .spatials import morphology 5 | from .spatials import SME 6 | -------------------------------------------------------------------------------- /stlearn/spatials/SME/__init__.py: -------------------------------------------------------------------------------- 1 | from .normalize import SME_normalize 2 | from .impute import SME_impute0, pseudo_spot 3 | -------------------------------------------------------------------------------- /stlearn/spatials/SME/normalize.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from anndata import AnnData 3 | import numpy as np 4 | from scipy.sparse import csr_matrix 5 | import pandas as pd 6 | from ._weighting_matrix import ( 7 | calculate_weight_matrix, 8 | impute_neighbour, 9 | _WEIGHTING_MATRIX, 10 | _PLATFORM, 11 | ) 12 | 13 | 14 | def SME_normalize( 15 | adata: AnnData, 16 | use_data: str = "raw", 17 | weights: _WEIGHTING_MATRIX = "weights_matrix_all", 18 | platform: _PLATFORM = "Visium", 19 | copy: bool = False, 20 | ) -> Optional[AnnData]: 21 | """\ 22 | using spatial location (S), tissue morphological feature (M) and gene expression (E) information to normalize data. 23 | 24 | Parameters 25 | ---------- 26 | adata 27 | Annotated data matrix. 28 | use_data 29 | Input data, can be `raw` counts or log transformed data 30 | weights 31 | Weighting matrix for imputation. 32 | if `weights_matrix_all`, matrix combined all information from spatial location (S), 33 | tissue morphological feature (M) and gene expression (E) 34 | if `weights_matrix_pd_md`, matrix combined information from spatial location (S), 35 | tissue morphological feature (M) 36 | platform 37 | `Visium` or `Old_ST` 38 | copy 39 | Return a copy instead of writing to adata. 40 | Returns 41 | ------- 42 | Anndata 43 | """ 44 | if use_data == "raw": 45 | if isinstance(adata.X, csr_matrix): 46 | count_embed = adata.X.toarray() 47 | elif isinstance(adata.X, np.ndarray): 48 | count_embed = adata.X 49 | elif isinstance(adata.X, pd.Dataframe): 50 | count_embed = adata.X.values 51 | else: 52 | raise ValueError( 53 | f"""\ 54 | {type(adata.X)} is not a valid type. 55 | """ 56 | ) 57 | else: 58 | count_embed = adata.obsm[use_data] 59 | 60 | calculate_weight_matrix(adata, platform=platform) 61 | 62 | impute_neighbour(adata, count_embed=count_embed, weights=weights) 63 | 64 | imputed_data = adata.obsm["imputed_data"].astype(float) 65 | imputed_data[imputed_data == 0] = np.nan 66 | adjusted_count_matrix = np.nanmean(np.array([count_embed, imputed_data]), axis=0) 67 | 68 | key_added = use_data + "_SME_normalized" 69 | adata.obsm[key_added] = adjusted_count_matrix 70 | 71 | print("The data adjusted by SME is added to adata.obsm['" + key_added + "']") 72 | 73 | return adata if copy else None 74 | -------------------------------------------------------------------------------- /stlearn/spatials/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/spatials/__init__.py -------------------------------------------------------------------------------- /stlearn/spatials/clustering/__init__.py: -------------------------------------------------------------------------------- 1 | from .localization import localization 2 | -------------------------------------------------------------------------------- /stlearn/spatials/clustering/localization.py: -------------------------------------------------------------------------------- 1 | from anndata import AnnData 2 | from typing import Optional, Union 3 | import numpy as np 4 | import pandas as pd 5 | from sklearn.cluster import DBSCAN 6 | from natsort import natsorted 7 | 8 | 9 | def localization( 10 | adata: AnnData, 11 | use_label: str = "louvain", 12 | eps: int = 20, 13 | min_samples: int = 0, 14 | copy: bool = False, 15 | ) -> Optional[AnnData]: 16 | 17 | """\ 18 | Perform local cluster by using DBSCAN. 19 | 20 | Parameters 21 | ---------- 22 | adata 23 | Annotated data matrix. 24 | use_label 25 | Use label result of cluster method. 26 | eps 27 | The maximum distance between two samples for one to be considered as 28 | in the neighborhood of the other. This is not a maximum bound on the 29 | distances of points within a cluster. This is the most important DBSCAN 30 | parameter to choose appropriately for your data set and distance function. 31 | min_samples 32 | The number of samples (or total weight) in a neighborhood for a point to be 33 | considered as a core point. This includes the point itself. 34 | copy 35 | Return a copy instead of writing to adata. 36 | Returns 37 | ------- 38 | Anndata 39 | """ 40 | 41 | if "sub_cluster_labels" in adata.obs.columns: 42 | adata.obs = adata.obs.drop("sub_cluster_labels", axis=1) 43 | 44 | pd.set_option("mode.chained_assignment", None) 45 | subclusters_list = [] 46 | for i in adata.obs[use_label].unique(): 47 | 48 | tmp = adata.obs[adata.obs[use_label] == i] 49 | 50 | clustering = DBSCAN(eps=eps, min_samples=1, algorithm="kd_tree").fit( 51 | tmp[["imagerow", "imagecol"]] 52 | ) 53 | 54 | labels = clustering.labels_ 55 | 56 | sublabels = [] 57 | for label in labels: 58 | sublabels.append(str(i) + "_" + str(label)) 59 | tmp["sub_labels"] = sublabels 60 | subclusters_list.append(tmp["sub_labels"]) 61 | 62 | subclusters = pd.concat(subclusters_list) 63 | pd.reset_option("mode.chained_assignment") 64 | 65 | adata.obs = pd.merge( 66 | adata.obs, 67 | pd.DataFrame({"sub_cluster_labels": subclusters}), 68 | left_index=True, 69 | right_index=True, 70 | ) 71 | 72 | # Convert to numeric 73 | converted = dict(enumerate(adata.obs["sub_cluster_labels"].unique())) 74 | inv_map = {v: str(k) for k, v in converted.items()} 75 | adata.obs["sub_cluster_labels"] = adata.obs["sub_cluster_labels"].replace(inv_map) 76 | 77 | adata.obs["sub_cluster_labels"] = pd.Categorical( 78 | values=np.array(adata.obs["sub_cluster_labels"]).astype("U"), 79 | categories=natsorted( 80 | np.unique(np.array(adata.obs["sub_cluster_labels"])).astype("U") 81 | ), 82 | ) 83 | 84 | labels_cat = adata.obs[use_label].cat.categories 85 | cat_ind = {labels_cat[i]: i for i in range(len(labels_cat))} 86 | adata.uns[use_label + "_index_dict"] = cat_ind 87 | 88 | return adata if copy else None 89 | -------------------------------------------------------------------------------- /stlearn/spatials/morphology/__init__.py: -------------------------------------------------------------------------------- 1 | from .adjust import adjust 2 | -------------------------------------------------------------------------------- /stlearn/spatials/smooth/__init__.py: -------------------------------------------------------------------------------- 1 | from .disk import disk 2 | -------------------------------------------------------------------------------- /stlearn/spatials/smooth/disk.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | import numpy as np 3 | from anndata import AnnData 4 | import logging as logg 5 | import scipy.spatial as spatial 6 | 7 | 8 | def disk( 9 | adata: AnnData, 10 | use_data: str = "X_umap", 11 | radius: float = 10.0, 12 | rates: int = 1, 13 | method: str = "mean", 14 | copy: bool = False, 15 | ) -> Optional[AnnData]: 16 | 17 | coor = adata.obs[["imagecol", "imagerow"]] 18 | count_embed = adata.obsm[use_data] 19 | point_tree = spatial.cKDTree(coor) 20 | 21 | lag_coor = [] 22 | tmp = [] 23 | 24 | for i in range(len(coor)): 25 | current_neightbor = point_tree.query_ball_point( 26 | coor.values[i], radius 27 | ) # Spatial weight 28 | # print(coor.values[i]) 29 | tmp.append(current_neightbor) 30 | # print(coor.values[current_neightbor]) 31 | main = count_embed[current_neightbor] 32 | current_neightbor.remove(i) 33 | addition = count_embed[current_neightbor] 34 | 35 | for i in range(0, rates): 36 | main = np.append(main, addition, axis=0) 37 | if method == "mean": 38 | # New umap based on SW 39 | lag_coor.append(list(np.mean(main, axis=0))) 40 | elif method == "median": 41 | # New umap based on SW 42 | lag_coor.append(list(np.median(main, axis=0))) 43 | else: 44 | raise ValueError("Only 'median' and 'mean' are aceptable") 45 | 46 | new_embed = use_data + "_disk" 47 | 48 | adata.obsm[new_embed] = np.array(lag_coor) 49 | 50 | print( 51 | 'Disk smoothing function is applied! The new data are stored in adata.obsm["X_diffmap_disk"]' 52 | ) 53 | 54 | return adata if copy else None 55 | -------------------------------------------------------------------------------- /stlearn/spatials/trajectory/__init__.py: -------------------------------------------------------------------------------- 1 | from .global_level import global_level 2 | from .local_level import local_level 3 | from .pseudotime import pseudotime 4 | from .weight_optimization import weight_optimizing_global, weight_optimizing_local 5 | from .utils import lambda_dist, resistance_distance 6 | from .pseudotimespace import pseudotimespace_global, pseudotimespace_local 7 | from .detect_transition_markers import ( 8 | detect_transition_markers_clades, 9 | detect_transition_markers_branches, 10 | ) 11 | from .compare_transitions import compare_transitions 12 | 13 | from .set_root import set_root 14 | from .shortest_path_spatial_PAGA import shortest_path_spatial_PAGA 15 | -------------------------------------------------------------------------------- /stlearn/spatials/trajectory/compare_transitions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def compare_transitions(adata, trajectories): 5 | """\ 6 | Compare transition markers between two clades 7 | 8 | Parameters 9 | ---------- 10 | adata 11 | Annotated data matrix. 12 | trajectories 13 | List of clades names user want to compare. 14 | Returns 15 | ------- 16 | Anndata 17 | """ 18 | 19 | pos_1 = list( 20 | adata.uns[trajectories[0]][adata.uns[trajectories[0]]["score"] >= 0]["gene"] 21 | ) 22 | pos_2 = list( 23 | adata.uns[trajectories[1]][adata.uns[trajectories[1]]["score"] >= 0]["gene"] 24 | ) 25 | compare_pos_1 = np.setdiff1d(pos_1, pos_2, assume_unique=True) 26 | compare_pos_2 = np.setdiff1d(pos_2, pos_1, assume_unique=True) 27 | 28 | neg_1 = list( 29 | adata.uns[trajectories[0]][adata.uns[trajectories[0]]["score"] < 0]["gene"] 30 | ) 31 | neg_2 = list( 32 | adata.uns[trajectories[1]][adata.uns[trajectories[1]]["score"] < 0]["gene"] 33 | ) 34 | compare_neg_1 = np.setdiff1d(neg_1, neg_2, assume_unique=True) 35 | compare_neg_2 = np.setdiff1d(neg_2, neg_1, assume_unique=True) 36 | 37 | compare_result = {} 38 | compare_result["pos_1"] = compare_pos_1 39 | compare_result["pos_2"] = compare_pos_2 40 | compare_result["neg_1"] = compare_neg_1 41 | compare_result["neg_2"] = compare_neg_2 42 | 43 | compare_result["trajectories"] = trajectories 44 | 45 | adata.uns["compare_result"] = compare_result 46 | print( 47 | "The result of comparison between " 48 | + trajectories[0] 49 | + " and " 50 | + trajectories[1] 51 | + " stored in 'adata.uns['compare_result']'" 52 | ) 53 | -------------------------------------------------------------------------------- /stlearn/spatials/trajectory/local_level.py: -------------------------------------------------------------------------------- 1 | from anndata import AnnData 2 | from typing import Optional, Union 3 | import numpy as np 4 | from stlearn.em import run_pca, run_diffmap 5 | from stlearn.pp import neighbors 6 | from scipy.spatial.distance import cdist 7 | 8 | 9 | def local_level( 10 | adata: AnnData, 11 | use_label: str = "louvain", 12 | cluster: int = 9, 13 | w: float = 0.5, 14 | return_matrix: bool = False, 15 | verbose: bool = True, 16 | copy: bool = False, 17 | ) -> Optional[AnnData]: 18 | 19 | """\ 20 | Perform local sptial trajectory inference (required run pseudotime first). 21 | 22 | Parameters 23 | ---------- 24 | adata 25 | Annotated data matrix. 26 | use_label 27 | Use label result of cluster method. 28 | cluster 29 | Choose cluster to perform local spatial trajectory inference. 30 | threshold 31 | Threshold to find the significant connection for PAGA graph. 32 | w 33 | Pseudo-spatio-temporal distance weight (balance between spatial effect and DPT) 34 | return_matrix 35 | Return PTS matrix for local level 36 | copy 37 | Return a copy instead of writing to adata. 38 | Returns 39 | ------- 40 | Anndata 41 | """ 42 | if verbose: 43 | print("Start construct trajectory for subcluster " + str(cluster)) 44 | 45 | tmp = adata.obs[adata.obs[use_label] == str(cluster)] 46 | cluster_data = adata[list(tmp.index)] 47 | 48 | list_cluster = cluster_data.obs["sub_cluster_labels"].unique() 49 | dpt = [] 50 | sd = [] 51 | centroid_dict = cluster_data.uns["centroid_dict"] 52 | centroid_dict = {int(key): centroid_dict[key] for key in centroid_dict} 53 | for i in list_cluster: 54 | if ( 55 | len(adata.obs[adata.obs["sub_cluster_labels"] == str(i)]) 56 | > adata.uns["threshold_spots"] 57 | ): 58 | dpt.append( 59 | cluster_data.obs[cluster_data.obs["sub_cluster_labels"] == i][ 60 | "dpt_pseudotime" 61 | ].max() 62 | ) 63 | sd.append(centroid_dict[int(i)]) 64 | dm = cdist( 65 | np.array(dpt).reshape(-1, 1), 66 | np.array(dpt).reshape(-1, 1), 67 | lambda u, v: np.abs(u - v), 68 | ) 69 | 70 | non_abs_dm = cdist( 71 | np.array(dpt).reshape(-1, 1), np.array(dpt).reshape(-1, 1), lambda u, v: u - v 72 | ) 73 | adata.uns["nonabs_dpt_distance_matrix"] = non_abs_dm 74 | 75 | scale_dm = dm / np.max(dm) 76 | sdm = cdist(np.array(sd), np.array(sd), "euclidean") 77 | scale_sdm = sdm / np.max(sdm) 78 | 79 | stdm = scale_dm * w + scale_sdm * (1 - w) 80 | adata.uns["ST_distance_matrix"] = stdm 81 | 82 | if return_matrix: 83 | return stdm 84 | 85 | return adata if copy else None 86 | -------------------------------------------------------------------------------- /stlearn/spatials/trajectory/pseudotimespace.py: -------------------------------------------------------------------------------- 1 | from anndata import AnnData 2 | from typing import Optional, Union 3 | from .weight_optimization import weight_optimizing_global, weight_optimizing_local 4 | from .global_level import global_level 5 | from .local_level import local_level 6 | 7 | 8 | def pseudotimespace_global( 9 | adata: AnnData, 10 | use_label: str = "louvain", 11 | use_rep: str = "X_pca", 12 | n_dims: int = 40, 13 | list_clusters: list = [], 14 | model: str = "spatial", 15 | step=0.01, 16 | k=10, 17 | ) -> Optional[AnnData]: 18 | 19 | """\ 20 | Perform pseudo-time-space analysis with global level. 21 | 22 | Parameters 23 | ---------- 24 | adata 25 | Annotated data matrix. 26 | use_label 27 | Use label result of cluster method. 28 | list_clusters 29 | List of cluster used to reconstruct spatial trajectory. 30 | w 31 | Weighting factor to balance between spatial data and gene expression 32 | step 33 | Step for screeing weighting factor 34 | k 35 | The number of eigenvalues to be compared 36 | Returns 37 | ------- 38 | Anndata 39 | """ 40 | 41 | if model == "mixed": 42 | 43 | w = weight_optimizing_global( 44 | adata, use_label=use_label, list_clusters=list_clusters, step=step, k=k 45 | ) 46 | elif model == "spatial": 47 | w = 0 48 | elif model == "gene_expression": 49 | w = 1 50 | else: 51 | raise ValidationError( 52 | "Please choose the right model! Available models: 'mixed', 'spatial' and 'gene_expression' " 53 | ) 54 | 55 | global_level( 56 | adata, 57 | use_label=use_label, 58 | list_clusters=list_clusters, 59 | w=w, 60 | use_rep=use_rep, 61 | n_dims=n_dims, 62 | ) 63 | 64 | 65 | def pseudotimespace_local( 66 | adata: AnnData, 67 | use_label: str = "louvain", 68 | cluster: list = [], 69 | w: float = None, 70 | ) -> Optional[AnnData]: 71 | 72 | """\ 73 | Perform pseudo-time-space analysis with local level. 74 | 75 | Parameters 76 | ---------- 77 | adata 78 | Annotated data matrix. 79 | use_label 80 | Use label result of cluster method. 81 | cluster 82 | Cluster used to reconstruct intraregional spatial trajectory. 83 | w 84 | Weighting factor to balance between spatial data and gene expression 85 | Returns 86 | ------- 87 | Anndata 88 | """ 89 | 90 | if w is None: 91 | w = weight_optimizing_local(adata, use_label=use_label, cluster=cluster) 92 | 93 | local_level(adata, use_label=use_label, cluster=cluster, w=w) 94 | -------------------------------------------------------------------------------- /stlearn/spatials/trajectory/set_root.py: -------------------------------------------------------------------------------- 1 | from anndata import AnnData 2 | from typing import Optional, Union 3 | import numpy as np 4 | from stlearn.spatials.trajectory.utils import _correlation_test_helper 5 | 6 | 7 | def set_root(adata: AnnData, use_label: str, cluster: str, use_raw: bool = False): 8 | 9 | """\ 10 | Automatically set the root index. 11 | 12 | Parameters 13 | ---------- 14 | adata 15 | Annotated data matrix. 16 | use_label 17 | Use label result of cluster method. 18 | cluster 19 | Choose cluster to use as root 20 | use_raw 21 | Use the raw layer 22 | Returns 23 | ------- 24 | Root index 25 | """ 26 | 27 | tmp_adata = adata.copy() 28 | 29 | # Subset the data based on the chosen cluster 30 | 31 | tmp_adata = tmp_adata[ 32 | tmp_adata.obs[tmp_adata.obs[use_label] == str(cluster)].index, : 33 | ] 34 | if use_raw == True: 35 | tmp_adata = tmp_adata.raw.to_adata() 36 | 37 | # Borrow from Cellrank to calculate CytoTrace score 38 | num_exp_genes = np.array((tmp_adata.X > 0).sum(axis=1)).reshape(-1) 39 | gene_corr, _, _, _ = _correlation_test_helper(tmp_adata.X.T, num_exp_genes[:, None]) 40 | tmp_adata.var["gene_corr"] = gene_corr 41 | 42 | # Use top 1000 genes rather than top 200 genes 43 | top_1000 = tmp_adata.var.sort_values(by="gene_corr", ascending=False).index[:1000] 44 | tmp_adata.var["correlates"] = False 45 | tmp_adata.var.loc[top_1000, "correlates"] = True 46 | corr_mask = tmp_adata.var["correlates"] 47 | imputed_exp = tmp_adata[:, corr_mask].X 48 | 49 | # Scale ct score 50 | cytotrace_score = np.mean(imputed_exp, axis=1) 51 | cytotrace_score -= np.min(cytotrace_score) 52 | cytotrace_score /= np.max(cytotrace_score) 53 | 54 | # Get the root index 55 | local_index = np.argmax(cytotrace_score) 56 | obs_name = tmp_adata.obs.iloc[local_index].name 57 | 58 | return np.where(adata.obs_names == obs_name)[0][0] 59 | -------------------------------------------------------------------------------- /stlearn/spatials/trajectory/shortest_path_spatial_PAGA.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import numpy as np 3 | from stlearn.utils import _read_graph 4 | 5 | 6 | def shortest_path_spatial_PAGA( 7 | adata, 8 | use_label, 9 | key="dpt_pseudotime", 10 | ): 11 | # Read original PAGA graph 12 | G = nx.from_numpy_array(adata.uns["paga"]["connectivities"].toarray()) 13 | edge_weights = nx.get_edge_attributes(G, "weight") 14 | G.remove_edges_from((e for e, w in edge_weights.items() if w < 0)) 15 | H = G.to_directed() 16 | 17 | # Get min_node and max_node 18 | min_node, max_node = find_min_max_node(adata, key, use_label) 19 | 20 | # Calculate pseudotime for each node 21 | node_pseudotime = {} 22 | 23 | for node in H.nodes: 24 | node_pseudotime[node] = adata.obs.query(use_label + " == '" + str(node) + "'")[ 25 | key 26 | ].max() 27 | 28 | # Force original PAGA to directed PAGA based on pseudotime 29 | edge_to_remove = [] 30 | for edge in H.edges: 31 | if node_pseudotime[edge[0]] - node_pseudotime[edge[1]] > 0: 32 | edge_to_remove.append(edge) 33 | H.remove_edges_from(edge_to_remove) 34 | 35 | # Extract all available paths 36 | all_paths = {} 37 | j = 0 38 | for source in H.nodes: 39 | for target in H.nodes: 40 | paths = nx.all_simple_paths(H, source=source, target=target) 41 | for i, path in enumerate(paths): 42 | j += 1 43 | all_paths[j] = path 44 | 45 | # Filter the target paths from min_node to max_node 46 | target_paths = [] 47 | for path in list(all_paths.values()): 48 | if path[0] == min_node and path[-1] == max_node: 49 | target_paths.append(path) 50 | 51 | # Get the global graph 52 | G = _read_graph(adata, "global_graph") 53 | 54 | centroid_dict = adata.uns["centroid_dict"] 55 | centroid_dict = {int(key): centroid_dict[key] for key in centroid_dict} 56 | 57 | # Generate total length of every path. Store by dictionary 58 | dist_dict = {} 59 | for path in target_paths: 60 | path_name = ",".join(list(map(str, path))) 61 | result = [] 62 | query_node = get_node(path, adata.uns["split_node"]) 63 | for edge in G.edges(): 64 | if (edge[0] in query_node) and (edge[1] in query_node): 65 | result.append(edge) 66 | if len(result) >= len(path): 67 | dist_dict[path_name] = calculate_total_dist(result, centroid_dict) 68 | 69 | # Find the shortest path 70 | shortest_path = min(dist_dict, key=lambda x: dist_dict[x]) 71 | return shortest_path.split(",") 72 | 73 | 74 | # get name of cluster by subcluster 75 | def get_cluster(search, dictionary): 76 | for cl, sub in dictionary.items(): 77 | if search in sub: 78 | return cl 79 | 80 | 81 | def get_node(node_list, split_node): 82 | result = np.array([]) 83 | for node in node_list: 84 | result = np.append(result, np.array(split_node[int(node)]).astype(int)) 85 | return result.astype(int) 86 | 87 | 88 | def find_min_max_node(adata, key="dpt_pseudotime", use_label="leiden"): 89 | min_cluster = int(adata.obs[adata.obs[key] == 0][use_label].values[0]) 90 | max_cluster = int(adata.obs[adata.obs[key] == 1][use_label].values[0]) 91 | 92 | return [min_cluster, max_cluster] 93 | 94 | 95 | def calculate_total_dist(result, centroid_dict): 96 | import math 97 | 98 | total_dist = 0 99 | for edge in result: 100 | source = centroid_dict[edge[0]] 101 | target = centroid_dict[edge[1]] 102 | dist = math.dist(source, target) 103 | total_dist += dist 104 | return total_dist 105 | -------------------------------------------------------------------------------- /stlearn/tl.py: -------------------------------------------------------------------------------- 1 | from .tools import clustering 2 | from .tools.microenv import cci 3 | from .tools.label import label 4 | -------------------------------------------------------------------------------- /stlearn/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/tools/__init__.py -------------------------------------------------------------------------------- /stlearn/tools/clustering/__init__.py: -------------------------------------------------------------------------------- 1 | from .kmeans import kmeans 2 | from .louvain import louvain 3 | from .annotate import annotate_interactive 4 | -------------------------------------------------------------------------------- /stlearn/tools/clustering/annotate.py: -------------------------------------------------------------------------------- 1 | from anndata import AnnData 2 | from stlearn.plotting.classes_bokeh import Annotate 3 | from bokeh.io import output_notebook 4 | from bokeh.plotting import show 5 | 6 | 7 | def annotate_interactive( 8 | adata: AnnData, 9 | ): 10 | """\ 11 | Allow user to manually define the clusters 12 | 13 | Parameters 14 | ------------------------------------- 15 | adata 16 | Annotated data matrix. 17 | """ 18 | 19 | bokeh_object = Annotate(adata) 20 | output_notebook() 21 | show(bokeh_object.app, notebook_handle=True) 22 | -------------------------------------------------------------------------------- /stlearn/tools/clustering/kmeans.py: -------------------------------------------------------------------------------- 1 | from sklearn.cluster import KMeans 2 | from anndata import AnnData 3 | from typing import Optional, Union 4 | import pandas as pd 5 | import numpy as np 6 | from natsort import natsorted 7 | 8 | 9 | def kmeans( 10 | adata: AnnData, 11 | n_clusters: int = 20, 12 | use_data: str = "X_pca", 13 | init: str = "k-means++", 14 | n_init: int = 10, 15 | max_iter: int = 300, 16 | tol: float = 0.0001, 17 | random_state: str = None, 18 | copy_x: bool = True, 19 | algorithm: str = "auto", 20 | key_added: str = "kmeans", 21 | copy: bool = False, 22 | ) -> Optional[AnnData]: 23 | 24 | """\ 25 | Perform kmeans cluster for spatial transcriptomics data 26 | 27 | Parameters 28 | ---------- 29 | adata 30 | Annotated data matrix. 31 | n_clusters 32 | The number of clusters to form as well as the number of 33 | centroids to generate. 34 | use_data 35 | Use dimensionality reduction result. 36 | init 37 | Method for initialization, defaults to 'k-means++' 38 | max_iter 39 | Maximum number of iterations of the k-means algorithm for a 40 | single run. 41 | tol 42 | Relative tolerance with regards to inertia to declare convergence. 43 | random_state 44 | Determines random number generation for centroid initialization. Use 45 | an int to make the randomness deterministic. 46 | copy_x 47 | When pre-computing distances it is more numerically accurate to center 48 | the data first. If copy_x is True (default), then the original data is 49 | not modified, ensuring X is C-contiguous. If False, the original data 50 | is modified, and put back before the function returns, but small 51 | numerical differences may be introduced by subtracting and then adding 52 | the data mean, in this case it will also not ensure that data is 53 | C-contiguous which may cause a significant slowdown. 54 | algorithm 55 | K-means algorithm to use. The classical EM-style algorithm is "full". 56 | The "elkan" variation is more efficient by using the triangle 57 | inequality, but currently doesn't support sparse data. "auto" chooses 58 | "elkan" for dense data and "full" for sparse data. 59 | key_added 60 | Key add to adata.obs 61 | copy 62 | Return a copy instead of writing to adata. 63 | Returns 64 | ------- 65 | Anndata 66 | """ 67 | 68 | data = adata.obsm[use_data] 69 | 70 | print("Applying Kmeans cluster ...") 71 | 72 | kmeans = KMeans( 73 | n_clusters=n_clusters, 74 | init=init, 75 | n_init=n_init, 76 | max_iter=max_iter, 77 | tol=tol, 78 | random_state=random_state, 79 | copy_x=copy_x, 80 | algorithm=algorithm, 81 | ).fit(data) 82 | 83 | adata.obs[key_added] = pd.Categorical( 84 | values=np.array(kmeans.labels_).astype("U"), 85 | categories=natsorted(np.unique(np.array(kmeans.labels_)).astype("U")), 86 | ) 87 | 88 | print('Kmeans cluster is done! The labels are stored in adata.obs["kmeans"]') 89 | 90 | return adata if copy else None 91 | -------------------------------------------------------------------------------- /stlearn/tools/clustering/louvain.py: -------------------------------------------------------------------------------- 1 | from types import MappingProxyType 2 | from typing import Optional, Tuple, Sequence, Type, Mapping, Any, Union 3 | 4 | import numpy as np 5 | import pandas as pd 6 | from anndata import AnnData 7 | from natsort import natsorted 8 | from numpy.random.mtrand import RandomState 9 | from scipy.sparse import spmatrix 10 | from stlearn._compat import Literal 11 | 12 | try: 13 | from louvain.VertexPartition import MutableVertexPartition 14 | except ImportError: 15 | 16 | class MutableVertexPartition: 17 | pass 18 | 19 | MutableVertexPartition.__module__ = "louvain.VertexPartition" 20 | import scanpy 21 | 22 | 23 | def louvain( 24 | adata: AnnData, 25 | resolution: Optional[float] = None, 26 | random_state: Optional[Union[int, RandomState]] = 0, 27 | restrict_to: Optional[Tuple[str, Sequence[str]]] = None, 28 | key_added: str = "louvain", 29 | adjacency: Optional[spmatrix] = None, 30 | flavor: Literal["vtraag", "igraph", "rapids"] = "vtraag", 31 | directed: bool = True, 32 | use_weights: bool = False, 33 | partition_type: Optional[Type[MutableVertexPartition]] = None, 34 | partition_kwargs: Mapping[str, Any] = MappingProxyType({}), 35 | copy: bool = False, 36 | ) -> Optional[AnnData]: 37 | """\ 38 | Wrap function scanpy.tl.louvain 39 | Cluster cells into subgroups [Blondel08]_ [Levine15]_ [Traag17]_. 40 | Cluster cells using the Louvain algorithm [Blondel08]_ in the implementation 41 | of [Traag17]_. The Louvain algorithm has been proposed for single-cell 42 | analysis by [Levine15]_. 43 | This requires having ran :func:`~scanpy.pp.neighbors` or 44 | :func:`~scanpy.external.pp.bbknn` first, 45 | or explicitly passing a ``adjacency`` matrix. 46 | Parameters 47 | ---------- 48 | adata 49 | The annotated data matrix. 50 | resolution 51 | For the default flavor (``'vtraag'``), you can provide a resolution 52 | (higher resolution means finding more and smaller clusters), 53 | which defaults to 1.0. 54 | See “Time as a resolution parameter” in [Lambiotte09]_. 55 | random_state 56 | Change the initialization of the optimization. 57 | restrict_to 58 | Restrict the cluster to the categories within the key for sample 59 | annotation, tuple needs to contain ``(obs_key, list_of_categories)``. 60 | key_added 61 | Key under which to add the cluster labels. (default: ``'louvain'``) 62 | adjacency 63 | Sparse adjacency matrix of the graph, defaults to 64 | ``adata.uns['neighbors']['connectivities']``. 65 | flavor 66 | Choose between to packages for computing the cluster. 67 | ``'vtraag'`` is much more powerful, and the default. 68 | directed 69 | Interpret the ``adjacency`` matrix as directed graph? 70 | use_weights 71 | Use weights from knn graph. 72 | partition_type 73 | Type of partition to use. 74 | Only a valid argument if ``flavor`` is ``'vtraag'``. 75 | partition_kwargs 76 | Key word arguments to pass to partitioning, 77 | if ``vtraag`` method is being used. 78 | copy 79 | Copy adata or modify it inplace. 80 | Returns 81 | ------- 82 | :obj:`None` 83 | By default (``copy=False``), updates ``adata`` with the following fields: 84 | ``adata.obs['louvain']`` (:class:`pandas.Series`, dtype ``category``) 85 | Array of dim (number of samples) that stores the subgroup id 86 | (``'0'``, ``'1'``, ...) for each cell. 87 | :class:`~anndata.AnnData` 88 | When ``copy=True`` is set, a copy of ``adata`` with those fields is returned. 89 | """ 90 | 91 | scanpy.tl.louvain( 92 | adata, 93 | resolution=resolution, 94 | random_state=random_state, 95 | restrict_to=restrict_to, 96 | key_added=key_added, 97 | adjacency=adjacency, 98 | flavor=flavor, 99 | directed=directed, 100 | use_weights=use_weights, 101 | partition_type=partition_type, 102 | partition_kwargs=partition_kwargs, 103 | copy=copy, 104 | ) 105 | 106 | print("Applying Louvain cluster ...") 107 | print( 108 | "Louvain cluster is done! The labels are stored in adata.obs['%s']" % key_added 109 | ) 110 | -------------------------------------------------------------------------------- /stlearn/tools/label/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/tools/label/__init__.py -------------------------------------------------------------------------------- /stlearn/tools/label/label_transfer.R: -------------------------------------------------------------------------------- 1 | # Seurat label transfer wrapper script. 2 | # Following from here: https://satijalab.org/seurat/articles/spatial_vignette.html 3 | # See the section 'Integration with single cell data' 4 | 5 | library(Seurat) 6 | library(dplyr) 7 | 8 | label_transfer <- function(st_expr_df, sc_expr_df, sc_labels) { 9 | ###### Performs Seurat label transfer from the sc data to the st data. ####### 10 | 11 | # Creating the Seurat objects # 12 | print("Creating Seurat Objects.") 13 | st <- CreateSeuratObject(st_expr_df) 14 | VariableFeatures(st) <- rownames(st@assays$RNA@data) 15 | sc <- CreateSeuratObject(sc_expr_df) 16 | sc <- AddMetaData(sc, sc_labels, col.name='cell_type') 17 | VariableFeatures(sc) <- rownames(sc@assays$RNA@data) 18 | print("Finished creating Seurat.") 19 | 20 | # Finding variable features # 21 | #st <- FindVariableFeatures(st, selection.method="vst", 22 | # nfeatures=n_highly_variable, verbose=T) 23 | #print("Finished finding variable features for ST data.") 24 | #sc <- FindVariableFeatures(sc, selection.method="vst", 25 | # nfeatures=n_highly_variable, verbose=T) 26 | #print("Finished finding variable features for SC data.") 27 | 28 | # Dim reduction # 29 | st <- ScaleData(st, verbose=T) 30 | print("Finished scaling data for ST data.") 31 | st <- RunPCA(st, verbose = T, #features=rownames(st@assays$RNA@data) 32 | ) 33 | print("Finished PCA for st data.") 34 | st <- RunUMAP(st, dims = 1:30, method='uwot-learn') 35 | print("Finished UMAP for st data.") 36 | 37 | sc <- ScaleData(sc) %>% RunPCA(verbose = T, 38 | #features=rownames(sc@assays$RNA@data) 39 | ) %>% RunUMAP(dims = 1:30, 40 | method='uwot-learn') 41 | print("Finished scaling data for SC data.") 42 | 43 | # Performing the label transfer # 44 | anchors <- FindTransferAnchors(reference = sc, query = st, 45 | #features=rownames(sc@assays$RNA@data), 46 | reference.reduction = 'pca', dims=1:30, 47 | normalization.method = "LogNormalize") 48 | print("Finished finding anchors.") 49 | predictions.assay <- TransferData(anchorset = anchors, 50 | refdata = sc$cell_type, prediction.assay=T, 51 | k.weight=20, 52 | weight.reduction = st[["pca"]], dims = 1:30) 53 | print("Finished label transferring.") 54 | transfer_scores <- predictions.assay@data 55 | 56 | return( as.data.frame( transfer_scores ) ) 57 | } 58 | -------------------------------------------------------------------------------- /stlearn/tools/label/rctd.R: -------------------------------------------------------------------------------- 1 | # RCTD deconvolution wrapper script 2 | 3 | library(RCTD) 4 | library(data.table) 5 | 6 | rctd <- function(st_counts, st_coords, sc_counts, sc_labels, 7 | doublet_mode, min_cells, n_cores) { 8 | ###### Performs RCTD deconvolution of the st data from sc data ####### 9 | 10 | # Making sure correct namings # 11 | colnames(st_counts) <- rownames(st_coords) 12 | 13 | # Subsetting to cell types with > X cells # 14 | label_set <- unique( sc_labels ) 15 | label_counts <- as.integer( lapply(label_set, 16 | function(label) { 17 | length(which(sc_labels==label)) 18 | })) 19 | new_label_set <- label_set[ label_counts > min_cells ] 20 | labels_bool <- as.logical( lapply(sc_labels, 21 | function(label) { 22 | label %in% new_label_set 23 | })) 24 | sc_counts <- sc_counts[,labels_bool] 25 | sc_labels <- as.factor( sc_labels[labels_bool] ) 26 | names(sc_labels) <- colnames( sc_counts ) 27 | 28 | ############################################################################## 29 | # Creating RCTD objects # 30 | ############################################################################## 31 | ## Single cell reference ## 32 | reference <- Reference(sc_counts, cell_types = sc_labels) 33 | 34 | ## Spots for deconvolution ## 35 | query <- SpatialRNA(st_coords, st_counts) 36 | 37 | ## Creating the RCTD object ## 38 | myRCTD <- RCTD::create.RCTD(query, reference, 39 | max_cores = n_cores, CELL_MIN_INSTANCE=min_cells) 40 | 41 | ############################################################################## 42 | # Running RCTD # 43 | ############################################################################## 44 | myRCTD <- run.RCTD(myRCTD, doublet_mode = doublet_mode) 45 | 46 | ### Getting & normalising the results ### 47 | results <- myRCTD@results$weights 48 | norm_weights <- sweep(results, 1, rowSums(as.matrix(results)), '/') 49 | norm_weights <- as.data.frame(as.matrix(norm_weights)) 50 | 51 | return( norm_weights ) 52 | } 53 | -------------------------------------------------------------------------------- /stlearn/tools/label/singleR.R: -------------------------------------------------------------------------------- 1 | # SingleR spot annotation wrapper script. 2 | 3 | library(SingleR) 4 | 5 | singleR <- function(st_expr_df, sc_expr_df, sc_labels, 6 | n_centers, de_n, de_method) { 7 | ##### Runs SingleR spot annotation ####### 8 | st_expr <- as.matrix( st_expr_df ) 9 | sc_expr <- as.matrix( sc_expr_df ) 10 | 11 | ##### Subsetting to genes in common between datasets ##### 12 | common_genes <- intersect(rownames(sc_expr), rownames(st_expr)) 13 | 14 | sc_expr <- sc_expr[common_genes,] 15 | st_expr <- st_expr[common_genes,] 16 | 17 | ###### Performs Seurat label transfer from the sc data to the st data. ####### 18 | sc_aggr <- aggregateReference(sc_expr, sc_labels, ncenters=n_centers) 19 | trained <- trainSingleR(sc_aggr, sc_aggr$label, de.n=de_n, de.method=de_method) 20 | 21 | out <- classifySingleR(st_expr, trained, fine.tune=F, prune=F) 22 | scores_df <- as.data.frame( out$scores ) 23 | scores_df[,'labels'] <- out$labels 24 | rownames(scores_df) <- out@rownames 25 | 26 | return( scores_df ) 27 | } 28 | -------------------------------------------------------------------------------- /stlearn/tools/microenv/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/tools/microenv/__init__.py -------------------------------------------------------------------------------- /stlearn/tools/microenv/cci/__init__.py: -------------------------------------------------------------------------------- 1 | # from .base import lr 2 | # from .base_grouping import get_hotspots 3 | # from . import het 4 | # from .het import edge_core, get_between_spot_edge_array 5 | # from .merge import merge 6 | # from .permutation import get_rand_pairs 7 | from .analysis import load_lrs, grid, run, adj_pvals, run_lr_go, run_cci 8 | -------------------------------------------------------------------------------- /stlearn/tools/microenv/cci/databases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/tools/microenv/cci/databases/__init__.py -------------------------------------------------------------------------------- /stlearn/tools/microenv/cci/go.R: -------------------------------------------------------------------------------- 1 | # R script that runs the LR GO analysis # 2 | 3 | library(clusterProfiler) 4 | library(org.Mm.eg.db) 5 | library(org.Hs.eg.db) 6 | #library(enrichplot) 7 | #library(ggplot2) 8 | 9 | GO_analyse <- function(genes, bg_genes, species, 10 | p_cutoff, q_cutoff, onts) { 11 | #p_cutoff=.01, q_cutoff=0.5, onts='BP') { 12 | # Main function for performing the GO analysis # 13 | 14 | # Selecting correct species database # 15 | if (species == 'human') {db <- org.Hs.eg.db 16 | } else {db <- org.Mm.eg.db} 17 | 18 | # Performing the enrichment # 19 | em <- enrichGO(genes, db, ont=onts, keyType='SYMBOL', 20 | pvalueCutoff=p_cutoff, qvalueCutoff=q_cutoff, universe=bg_genes) 21 | 22 | result <- em@result 23 | sig_results <- result[,'p.adjust'] AnnData: 12 | """Merge results from cell type heterogeneity and L-R cluster 13 | Parameters 14 | ---------- 15 | adata: AnnData The data object including the cell types to count 16 | use_lr: str CCI LR scores 17 | use_het: str CCI HET scores 18 | 19 | Returns 20 | ------- 21 | adata: AnnData With merged result stored in adata.uns['merged'] 22 | """ 23 | 24 | adata.obsm["merged"] = np.multiply(adata.obsm[use_het], adata.obsm[use_lr]) 25 | 26 | if verbose: 27 | print( 28 | "Results of spatial interaction analysis has been written to adata.uns['merged']" 29 | ) 30 | 31 | return adata 32 | -------------------------------------------------------------------------------- /stlearn/tools/microenv/cci/r_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper functions for porting R code into python/stLearn. 3 | """ 4 | 5 | import os 6 | 7 | ro = None 8 | pandas2ri = None 9 | localconverter = None 10 | 11 | 12 | def rpy2_setup(r_path): 13 | """Sets up rpy2.""" 14 | os.environ["R_HOME"] = r_path 15 | 16 | import rpy2.robjects as robjects_ 17 | from rpy2.robjects import pandas2ri as pandas2ri_ 18 | from rpy2.robjects.conversion import localconverter as localconverter_ 19 | 20 | global ro, pandas2ri, localconverter 21 | ro = robjects_ 22 | pandas2ri = pandas2ri_ 23 | localconverter = localconverter_ 24 | -------------------------------------------------------------------------------- /stlearn/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import io 4 | from PIL import Image 5 | import matplotlib 6 | from anndata import AnnData 7 | import networkx as nx 8 | 9 | from typing import Optional, Union, Mapping # Special 10 | from typing import Sequence, Iterable # ABCs 11 | from typing import Tuple # Classes 12 | 13 | from textwrap import dedent 14 | 15 | from enum import Enum 16 | 17 | 18 | class Empty(Enum): 19 | token = 0 20 | 21 | 22 | _empty = Empty.token 23 | 24 | from matplotlib import rcParams, ticker, gridspec, axes 25 | from matplotlib.axes import Axes 26 | from abc import ABC 27 | 28 | 29 | class _AxesSubplot(Axes, axes.SubplotBase): 30 | """Intersection between Axes and SubplotBase: Has methods of both""" 31 | 32 | 33 | def _check_spot_size( 34 | spatial_data: Optional[Mapping], spot_size: Optional[float] 35 | ) -> float: 36 | """ 37 | Resolve spot_size value. 38 | This is a required argument for spatial plots. 39 | """ 40 | if spatial_data is None and spot_size is None: 41 | raise ValueError( 42 | "When .uns['spatial'][library_id] does not exist, spot_size must be " 43 | "provided directly." 44 | ) 45 | elif spot_size is None: 46 | return spatial_data["scalefactors"]["spot_diameter_fullres"] 47 | else: 48 | return spot_size 49 | 50 | 51 | def _check_scale_factor( 52 | spatial_data: Optional[Mapping], 53 | img_key: Optional[str], 54 | scale_factor: Optional[float], 55 | ) -> float: 56 | """Resolve scale_factor, defaults to 1.""" 57 | if scale_factor is not None: 58 | return scale_factor 59 | elif spatial_data is not None and img_key is not None: 60 | return spatial_data["scalefactors"][f"tissue_{img_key}_scalef"] 61 | else: 62 | return 1.0 63 | 64 | 65 | def _check_spatial_data( 66 | uns: Mapping, library_id: Union[Empty, None, str] 67 | ) -> Tuple[Optional[str], Optional[Mapping]]: 68 | """ 69 | Given a mapping, try and extract a library id/ mapping with spatial data. 70 | Assumes this is `.uns` from how we parse visium data. 71 | """ 72 | spatial_mapping = uns.get("spatial", {}) 73 | if library_id is _empty: 74 | if len(spatial_mapping) > 1: 75 | raise ValueError( 76 | "Found multiple possible libraries in `.uns['spatial']. Please specify." 77 | f" Options are:\n\t{list(spatial_mapping.keys())}" 78 | ) 79 | elif len(spatial_mapping) == 1: 80 | library_id = list(spatial_mapping.keys())[0] 81 | else: 82 | library_id = None 83 | if library_id is not None: 84 | spatial_data = spatial_mapping[library_id] 85 | else: 86 | spatial_data = None 87 | return library_id, spatial_data 88 | 89 | 90 | def _check_img( 91 | spatial_data: Optional[Mapping], 92 | img: Optional[np.ndarray], 93 | img_key: Union[None, str, Empty], 94 | bw: bool = False, 95 | ) -> Tuple[Optional[np.ndarray], Optional[str]]: 96 | """ 97 | Resolve image for spatial plots. 98 | """ 99 | if img is None and spatial_data is not None and img_key is _empty: 100 | img_key = next( 101 | (k for k in ["hires", "lowres", "fulres"] if k in spatial_data["images"]), 102 | ) # Throws StopIteration Error if keys not present 103 | if img is None and spatial_data is not None and img_key is not None: 104 | img = spatial_data["images"][img_key] 105 | if bw: 106 | img = np.dot(img[..., :3], [0.2989, 0.5870, 0.1140]) 107 | return img, img_key 108 | 109 | 110 | def _check_coords( 111 | obsm: Optional[Mapping], scale_factor: Optional[float] 112 | ) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]: 113 | 114 | image_coor = obsm["spatial"] * scale_factor 115 | imagecol = image_coor[:, 0] 116 | imagerow = image_coor[:, 1] 117 | 118 | return [imagecol, imagerow] 119 | 120 | 121 | def _read_graph(adata: AnnData, graph_type: Optional[str]): 122 | 123 | if graph_type == "PTS_graph": 124 | graph = nx.from_scipy_sparse_array( 125 | adata.uns[graph_type]["graph"], create_using=nx.DiGraph 126 | ) 127 | else: 128 | graph = nx.from_scipy_sparse_array(adata.uns[graph_type]["graph"]) 129 | node_dict = adata.uns[graph_type]["node_dict"] 130 | node_dict = {int(k): int(v) for k, v in node_dict.items()} 131 | 132 | relabel_graph = nx.relabel_nodes(graph, node_dict) 133 | 134 | return relabel_graph 135 | 136 | 137 | def _docs_params(**kwds): 138 | """\ 139 | Docstrings should start with "\" in the first line for proper formatting. 140 | """ 141 | 142 | def dec(obj): 143 | obj.__orig_doc__ = obj.__doc__ 144 | obj.__doc__ = dedent(obj.__doc__).format_map(kwds) 145 | return obj 146 | 147 | return dec 148 | -------------------------------------------------------------------------------- /stlearn/wrapper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/stlearn/wrapper/__init__.py -------------------------------------------------------------------------------- /stlearn/wrapper/concatenate_spatial_adata.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from skimage.transform import resize 3 | 4 | 5 | def transform_spatial(coordinates, original, resized): 6 | # obs transform 7 | x = coordinates[:, 0] 8 | y = coordinates[:, 1] 9 | x1p = x / original[1] 10 | y1p = y / original[0] 11 | x1 = x1p * resized[1] 12 | y1 = y1p * resized[0] 13 | 14 | return np.vstack([x1, y1]).transpose() 15 | 16 | 17 | def correct_size(adata, fixed_size): 18 | 19 | image = adata.uns["spatial"][list(adata.uns["spatial"].keys())[0]]["images"][ 20 | "hires" 21 | ] 22 | image_size = image.shape[:2] 23 | if image_size != fixed_size: 24 | adata.obs[["imagerow", "imagecol"]] = transform_spatial( 25 | adata.obs[["imagerow", "imagecol"]].values, image_size, fixed_size 26 | ) 27 | adata.obsm["spatial"] = transform_spatial( 28 | adata.obsm["spatial"], image_size, fixed_size 29 | ) 30 | image_resized = resize(image, fixed_size) 31 | adata.uns["spatial"][list(adata.uns["spatial"].keys())[0]]["images"][ 32 | "hires" 33 | ] = image_resized 34 | 35 | return adata 36 | 37 | 38 | def concatenate_spatial_adata(adata_list, ncols=2, fixed_size=(2000, 2000)): 39 | """\ 40 | Concatnate multiple anndata for visualization of spatial transcriptomics 41 | 42 | Parameters 43 | ---------- 44 | adata_list 45 | A list of anndata objet. 46 | ncols 47 | Number of columns 48 | fixed_size 49 | The size that fixed for every spatial transcriptomics data 50 | Returns 51 | ------- 52 | Returns adata. 53 | """ 54 | 55 | use_adata_list = [] 56 | for adata in adata_list: 57 | use_adata_list.append(adata.copy()) 58 | 59 | import math 60 | 61 | # check valid 62 | n_adata = len(use_adata_list) 63 | nrows = math.ceil(n_adata / ncols) 64 | if ncols > n_adata: 65 | raise ValueError("Number of column is out of bound") 66 | 67 | # Correct size 68 | for adata in use_adata_list: 69 | correct_size(adata, fixed_size=fixed_size) 70 | 71 | # Transform 72 | n = 0 73 | break_out_flag = False 74 | scale = use_adata_list[0].uns["spatial"][ 75 | list(use_adata_list[0].uns["spatial"].keys())[0] 76 | ]["scalefactors"]["tissue_hires_scalef"] 77 | for i in range(0, nrows): 78 | for j in range(0, ncols): 79 | obs_spatial = use_adata_list[n].obs[["imagerow", "imagecol"]].values 80 | obsm_spatial = use_adata_list[n].obsm["spatial"] 81 | obs_spatial = np.vstack( 82 | ( 83 | obs_spatial[:, 0] + fixed_size[0] * i, 84 | obs_spatial[:, 1] + fixed_size[1] * j, 85 | ) 86 | ).transpose() 87 | obsm_spatial = np.vstack( 88 | ( 89 | obsm_spatial[:, 0] + fixed_size[0] / scale * i, 90 | obsm_spatial[:, 1] + fixed_size[1] / scale * j, 91 | ) 92 | ).transpose() 93 | use_adata_list[n].obs[["imagerow", "imagecol"]] = obs_spatial 94 | use_adata_list[n].obsm["spatial"] = obsm_spatial 95 | if n == len(use_adata_list) - 1: 96 | break_out_flag = True 97 | break 98 | n += 1 99 | if break_out_flag: 100 | break 101 | 102 | # Combine images 103 | imgs = [] 104 | for i, adata in enumerate(use_adata_list): 105 | imgs.append( 106 | adata.uns["spatial"][list(adata.uns["spatial"].keys())[0]]["images"][ 107 | "hires" 108 | ] 109 | ) 110 | 111 | from PIL import Image 112 | 113 | if (nrows * ncols - len(use_adata_list)) > 0: 114 | for i in range(0, (nrows * ncols - len(use_adata_list))): 115 | image = Image.new("RGB", fixed_size, (255, 255, 255, 255)) 116 | imgs.append(np.array(image)) 117 | 118 | print(len(imgs)) 119 | 120 | img_rows = [] 121 | for min_id in range(0, len(use_adata_list), ncols): 122 | img_row = np.hstack(imgs[min_id : min_id + ncols]) 123 | img_rows.append(img_row) 124 | imgs_comb = np.vstack((i for i in img_rows)) 125 | 126 | adata_concat = use_adata_list[0].concatenate(use_adata_list[1:]) 127 | adata_concat.uns["spatial"] = use_adata_list[0].uns["spatial"] 128 | 129 | adata_concat.uns["spatial"][list(adata_concat.uns["spatial"].keys())[0]]["images"][ 130 | "hires" 131 | ] = imgs_comb 132 | 133 | return adata_concat 134 | -------------------------------------------------------------------------------- /stlearn/wrapper/convert_scanpy.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from anndata import AnnData 3 | from matplotlib import pyplot as plt 4 | from pathlib import Path 5 | import os 6 | 7 | 8 | def convert_scanpy( 9 | adata: AnnData, 10 | use_quality: str = "hires", 11 | ) -> Optional[AnnData]: 12 | 13 | adata.var_names_make_unique() 14 | 15 | library_id = list(adata.uns["spatial"].keys())[0] 16 | 17 | if use_quality == "fulres": 18 | image_coor = adata.obsm["spatial"] 19 | else: 20 | scale = adata.uns["spatial"][library_id]["scalefactors"][ 21 | "tissue_" + use_quality + "_scalef" 22 | ] 23 | image_coor = adata.obsm["spatial"] * scale 24 | 25 | adata.obs["imagecol"] = image_coor[:, 0] 26 | adata.obs["imagerow"] = image_coor[:, 1] 27 | adata.uns["spatial"][library_id]["use_quality"] = use_quality 28 | 29 | return adata 30 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for stlearn.""" 2 | -------------------------------------------------------------------------------- /tests/test_PSTS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `stlearn` package.""" 4 | 5 | 6 | import unittest 7 | 8 | import stlearn as st 9 | import scanpy as sc 10 | from .utils import read_test_data 11 | import numpy as np 12 | 13 | global adata 14 | adata = read_test_data() 15 | 16 | 17 | class TestPSTS(unittest.TestCase): 18 | """Tests for `stlearn` package.""" 19 | 20 | def test_PSTS(self): 21 | sc.pp.pca(adata) 22 | print("Done PCA!") 23 | sc.pp.neighbors(adata) 24 | print("Done KNN!") 25 | sc.tl.leiden(adata, resolution=0.6) 26 | print("Done leiden!") 27 | # sc.tl.louvain(adata) 28 | 29 | adata.uns["iroot"] = np.flatnonzero(adata.obs["leiden"] == "0")[0] 30 | st.spatial.trajectory.pseudotime( 31 | adata, eps=100, use_rep="X_pca", use_sme=False, use_label="leiden" 32 | ) 33 | st.spatial.trajectory.pseudotimespace_global( 34 | adata, use_label="leiden", list_clusters=[0, 1] 35 | ) 36 | st.spatial.trajectory.detect_transition_markers_clades( 37 | adata, clade=0, use_raw_count=False, cutoff_spearman=0.3 38 | ) 39 | print("Done PSTS!") 40 | -------------------------------------------------------------------------------- /tests/test_SME.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `stlearn` package.""" 4 | 5 | 6 | import unittest 7 | 8 | import stlearn as st 9 | import scanpy as sc 10 | from .utils import read_test_data 11 | 12 | global adata 13 | adata = read_test_data() 14 | 15 | 16 | class TestSME(unittest.TestCase): 17 | """Tests for `stlearn` package.""" 18 | 19 | def test_SME(self): 20 | sc.pp.pca(adata) 21 | st.pp.tiling(adata, "./tiling") 22 | st.pp.extract_feature(adata) 23 | import shutil 24 | 25 | shutil.rmtree("./tiling") 26 | data_SME = adata.copy() 27 | # apply stSME to normalise log transformed data 28 | st.spatial.SME.SME_normalize(data_SME, use_data="raw") 29 | -------------------------------------------------------------------------------- /tests/test_data/test_data.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/tests/test_data/test_data.h5 -------------------------------------------------------------------------------- /tests/test_data/test_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiomedicalMachineLearning/stLearn/439692ba35b0ecb3df952f72cc5af05b96b7f600/tests/test_data/test_image.jpg -------------------------------------------------------------------------------- /tests/test_install.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests that everything is installed correctly. 3 | """ 4 | 5 | import unittest 6 | 7 | 8 | class TestCCI(unittest.TestCase): 9 | """Tests for `stlearn` importability, i.e. correct installation.""" 10 | 11 | def test_SME(self): 12 | import stlearn.spatials.SME.normalize as sme_normalise 13 | 14 | def test_cci(self): 15 | """Tests CCI can be imported.""" 16 | import stlearn.tools.microenv.cci.analysis as an 17 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import scanpy as sc 3 | from PIL import Image 4 | import numpy as np 5 | 6 | 7 | def read_test_data(): 8 | """Reads in test data to run unit tests.""" 9 | # Determining path of this file # 10 | path = os.path.dirname(os.path.realpath(__file__)) 11 | adata = sc.read_h5ad(f"{path}/test_data/test_data.h5") 12 | im = Image.open(f"{path}/test_data/test_image.jpg") 13 | adata.uns["spatial"]["V1_Breast_Cancer_Block_A_Section_1"]["images"][ 14 | "hires" 15 | ] = np.array(im) 16 | return adata 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35, py36, py37, py38, flake8 3 | 4 | [travis] 5 | python = 6 | 3.8: py38 7 | 3.7: py37 8 | 3.6: py36 9 | 3.5: py35 10 | 11 | [testenv:flake8] 12 | basepython = python 13 | deps = flake8 14 | commands = flake8 stlearn 15 | 16 | [testenv] 17 | setenv = 18 | PYTHONPATH = {toxinidir} 19 | 20 | commands = python setup.py test 21 | --------------------------------------------------------------------------------