├── .github └── workflows │ └── documentation.yml ├── LICENSE ├── README.md ├── doc ├── .ipynb_checkpoints │ ├── Makefile-checkpoint │ ├── conf-checkpoint.py │ └── make-checkpoint.bat ├── Makefile ├── build │ ├── doctrees │ │ ├── environment.pickle │ │ └── index.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _images │ │ ├── DocFigs1-1.png │ │ ├── DocFigs1-3.png │ │ ├── DocFigs1.png │ │ └── DocFigs2.png │ │ ├── _sources │ │ └── index.rst.txt │ │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── sphinx_highlight.js │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── search.html │ │ └── searchindex.js ├── conf.py ├── css │ └── alabaster.css ├── images │ ├── DocFigs1-1.png │ ├── DocFigs1-3.png │ ├── DocFigs1.png │ └── DocFigs2.png ├── index.rst └── make.bat ├── genes2genes ├── AlignmentDistMan.py ├── ClusterUtils.py ├── Main.py ├── MyFunctions.py ├── OrgAlign.py ├── PathwayAnalyser.py ├── TimeSeriesPreprocessor.py ├── Utils.py ├── VisualUtils.py └── __init__.py ├── images ├── G2G_framework_overview.png ├── G2G_logo.png ├── G2G_logo_new.png ├── cell_numbers_vs_approx_time_PAM_LPS_G2G_alignment.png └── n_interpolation_points_vs_time_PAM_LPS_G2G_alignment.png ├── notebooks ├── Supplementary_notebook1.ipynb ├── Supplementary_notebook2.ipynb ├── Tutorial.ipynb └── data │ ├── adata_lps_local.h5ad │ └── adata_pam_local.h5ad └── pyproject.toml /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-python@v3 14 | - name: Install dependencies 15 | run: | 16 | pip install sphinx sphinx_rtd_theme myst_parser 17 | - name: Sphinx build 18 | run: | 19 | sphinx-build doc _build 20 | - name: Deploy to GitHub Pages 21 | uses: peaceiris/actions-gh-pages@v3 22 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} 23 | with: 24 | publish_branch: gh-pages 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: _build/ 27 | force_orphan: true 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Dinithi Sumanaweera, Teichmann Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Image 2 | 3 | # Genes2Genes 4 | 5 | ## A new framework for aligning single-cell trajectories of gene expression 6 | G2G aims to guide downstream comparative analysis of single-cell reference and query systems along any axis of progression (e.g. pseudotime). 7 | This is done by employing a new dynamic programming (DP) based alignment algorithm which unifies dynamic time warping (DTW) and gap modelling to capture both matches and mismatches between time points. Our DP algorithm incorporates a Bayesian information-theoretic scoring scheme with a five-state probabilistic machine to generate an optimal sequential alignment between a reference trajectory (R) and query trajectory (Q) of a given gene in terms of their scRNA-seq expression. In this way, G2G framework infers a fully-descriptive alignment for each gene of a specified gene set, gene clusters of different alignment patterns, an average (cell-level) alignment across all gene alignments, and further statistics to support downstream analysis (e.g. ranking of genes based on their alignment similarities). 8 | 9 | Image 10 | 11 | G2G framework can perform comparisons of gene expression dynamics across pseudotime such as: 12 | 17 | G2G alignment enables us to pinpoint dynamic similarities and differences in gene expression between a reference and query, as well as to group genes with similar alignment patterns. 18 | 19 | ### **Installing G2G** 20 | 21 | We recommend creating a new Conda environment before installing G2G from PyPi, to avoid any version conflicts and dependency issues. 22 | ```bash 23 | conda create --name g2g_env python=3.8 24 | conda activate g2g_env 25 | pip install genes2genes 26 | ``` 27 | Or optionally install the latest version directly from GitHub: 28 | ```bash 29 | pip install git+https://github.com/Teichlab/Genes2Genes.git 30 | ``` 31 | 32 | ### **Input to G2G** 33 | (1) Reference anndata object (with `adata_ref.X` storing log1p transformed gene expression), 34 | (2) Query anndata object (with `adata_query.X` storing log1p transformed gene expression), and 35 | (3) Pseudotime estimates stored in `adata_ref.obs['time']` and `adata_query.obs['time']`. 36 | 37 | **Note:** Please ensure that you have reasonable pseudotime estimates that fairly represent the trajectories, as the accuracy and reliability of trajectory alignment entirely depend on the accuracy and reliability of your pseudotime estimation. We recommend users to inspect whether the cell density distribution along estimated pseudotime (in terms of the meta attributes such as annotated cell types, sampling time points, etc. where applicable) well-represents each trajectory of focus. Users can choose the best pseudotime estimates to compare after testing several different pseudotime estimation tools on their datasets. 38 | 39 | ### **Tutorial** 40 | 41 | [`notebooks/Tutorial.ipynb`](https://github.com/Teichlab/Genes2Genes/blob/main/notebooks/Tutorial.ipynb) is an example analysis between a reference and query dataset from literature. 42 | Also refer to https://teichlab.github.io/Genes2Genes on how to read a trajectory alignment output generated by G2G.
43 | 44 | ### **Runtime of G2G** 45 | 46 | This depends on the number of cells in the reference and query datasets, the number of interpolation time points, and the number of genes to align. 47 | Below is a simple run-time analysis of G2G for 89 genes of the reference (NR = 179 cells) and query (NQ = 290 cells) from literature used in our tutorial. 48 | Note: the number of interpolation points is 14 for the middle plot. (Reference: [`notebooks/Supplementary_notebook1.ipynb`](https://github.com/Teichlab/Genes2Genes/blob/main/notebooks/Supplementary_notebook1.ipynb)) 49 | 50 |
51 |

52 | Image 53 | Image 54 |

55 |

56 | 57 | 58 | **Further examples from the case studies of our manuscript:**
59 | (Reference: [`notebooks/Supplementary_notebook2.ipynb`](https://github.com/Teichlab/Genes2Genes/blob/main/notebooks/Supplementary_notebook2.ipynb)) 60 | 61 | It took approximately 12min to align 1371 gene trajectories of 20,327 reference cells & 17,176 query cells under 14 interpolation time points; and approximately 4.5min to align 994 gene trajectories of 3157 reference cells & 890 query cells under 13 interpolation time points. 62 | 63 | G2G can also utilize concurrency through Python multiprocessing by creating a number of processes equal to the number of cores in the system where each process performs a single gene-level alignment at one time. However we note that sequential processing (the default setting of G2G) seems to be more efficient than parallel processing, as multiprocessing seems to add an overhead when allocating and sharing resources amongst processes, thus doubling up the runtime. 64 | 65 | 66 | ### Citation 67 | Dinithi Sumanaweera†, Chenqu Suo†, Ana-Maria Cujba, Daniele Muraro, Emma Dann, Krzysztof Polanski, Alexander S. Steemers, Woochan Lee, Amanda J. Oliver, Jong-Eun Park, Kerstin B. Meyer, Bianca Dumitrascu, Sarah A. Teichmann*, **"Gene-level alignment of single cell trajectories", Nature Methods (2024)**. https://doi.org/10.1038/s41592-024-02378-4 68 | 69 | This publication is part of the Human Cell Atlas. 70 | 71 | ### Funding Acknowledgement 72 | Marie Skłodowska-Curie grant agreement No: 101026506 (Marie Curie Individual Fellowship) under the European Union’s Horizon 2020 research and innovation programme; Wellcome Trust Ph.D. Fellowship for Clinicians; Wellcome Trust (WT206194); ERC Consolidator Grant (646794); Wellcome Sanger Institute’s Translation Committee Fund. 73 | -------------------------------------------------------------------------------- /doc/.ipynb_checkpoints/Makefile-checkpoint: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?="-c." 7 | SPHINXBUILD ?= sphinx-build 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 | -------------------------------------------------------------------------------- /doc/.ipynb_checkpoints/conf-checkpoint.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'Genes2Genes' 21 | copyright = '2023, Dinithi Sumanaweera, Teichmann Lab' 22 | author = 'Dinithi Sumanaweera, Teichmann Lab' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = 'v0.2.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ["sphinx_rtd_theme",'sphinx.ext.intersphinx'] 34 | # "myst_parser" removed as myst_nb automatically activates it 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = [] 43 | 44 | 45 | # -- Options for HTML output ------------------------------------------------- 46 | 47 | # The theme to use for HTML and HTML Help pages. See the documentation for 48 | # a list of builtin themes. 49 | # 50 | html_theme = 'sphinx_rtd_theme' 51 | 52 | # Add any paths that contain custom static files (such as style sheets) here, 53 | # relative to this directory. They are copied after the builtin static files, 54 | # so a file named "default.css" will overwrite the builtin "default.css". 55 | html_static_path = ['_static'] 56 | -------------------------------------------------------------------------------- /doc/.ipynb_checkpoints/make-checkpoint.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?="-c." 7 | SPHINXBUILD ?= sphinx-build 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 | -------------------------------------------------------------------------------- /doc/build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/doctrees/environment.pickle -------------------------------------------------------------------------------- /doc/build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/doctrees/index.doctree -------------------------------------------------------------------------------- /doc/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 2d92bab5530a9992afd79242fd06b61f 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /doc/build/html/_images/DocFigs1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_images/DocFigs1-1.png -------------------------------------------------------------------------------- /doc/build/html/_images/DocFigs1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_images/DocFigs1-3.png -------------------------------------------------------------------------------- /doc/build/html/_images/DocFigs1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_images/DocFigs1.png -------------------------------------------------------------------------------- /doc/build/html/_images/DocFigs2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_images/DocFigs2.png -------------------------------------------------------------------------------- /doc/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. Genes2Genes documentation master file, created by 2 | sphinx-quickstart on Wed Aug 16 09:01:25 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Genes2Genes 7 | ======================================= 8 | 9 | **A framework for single-cell pseudotime trajectory alignment** 10 | 11 | .. contents:: Contents 12 | 13 | Genes2Genes (G2G) is a new Python framework for aligning single-cell pseudotime trajectories of gene expression between any reference and query for a pairwise comparison such as: 14 | 15 | * Organoid vs. Reference tissue 16 | * Control vs. Treatment 17 | * Healthy vs. Disease 18 | 19 | Trajectory alignment: What & Why? 20 | ########### 21 | 22 | A single-cell trajectory describes the transcriptomic state of cells along some axis of progression (such as time), due to undergoing some dynamic process (e.g. cell differentiation, treatment response, or disease infection). Given an scRNA-seq profile, there are various tools available today to infer such trajectory by estimating a pseudo ordering of the cells along an axis, commonly referred to as 'pseudotime'. The pseudotime axis of a trajectory can be descritized to represent it as a sequence of discrete time points. Given two such discrete pseudotime sequences of two trajectories, a pairwise alignment between them defines a non-linear mapping between their time points. This mapping could have 1-to-1 matches as well as 1-to-many/many-to-1 matches (a.k.a warps) between the time points, while unmapping the time points which have significantly different transcriptomic states. Below is an example visualization of two cell differentiation trajectories. 23 | 24 | 25 | .. image:: images/DocFigs1.png 26 | :width: 600 27 | :alt: What is trajectory alignment? 28 | :align: center 29 | 30 | For two trajectories representing single lineages as above, G2G generates an **optimal pairwise trajectory alignment** that captures the matches and mismatches between their time points in sequential order, allowing a user to quantify the degree of similarity between them. 31 | 32 | .. image:: images/DocFigs1-1.png 33 | :width: 600 34 | :alt: Example mapping 35 | :align: center 36 | | 37 | G2G defines 5 different states of alignment between any two **R** and **Q** time points, corresponding to all possible match and mismatch states. They are: 1-to-1 match (``M``), 1-to-many match (``V``), many-to-1 match (``W``), insertion (``I``) and deletion (``D``). Here, ``I`` or ``D`` refer to a mismatched time point in Q or R, respectively. These states jointly cover the alignment states defined in classical dynamic time warping and biological sequence alignment. 38 | 39 | .. image:: images/DocFigs1-3.png 40 | :width: 600 41 | :alt: 5 states of alignment 42 | :align: center 43 | | 44 | Accordingly, we can describe any trajectory alignment as a 5-state alignment string. For example, the 5-state alignment string of the above illustrated trajectory alignment is: 45 | 46 | .. code-block:: 47 | 48 | IIIMMMWWWIIIDDDMMMIIIIDDDD 49 | 50 | This G2G alignment string enables us to identify the time regions of match and mismatch along the trajectories. For instance, we can interpret the above illustrated alignment as follow -- *R and Q trajectories have mid and late mismatches, with the early stage of Q being mismatched, yet starting to match to the early stage of R at the middle of Q's trajectory. Overall, there are 9 R and Q pseudotime pairs getting matched (with 34.62% alignment similarity)*. 51 | 52 | 53 | Outputs from Genes2Genes 54 | ########### 55 | 56 | Given an scRNA-seq dataset with their pseudotime estimates and a specified set of genes (e.g. all transcription factors, highly variable genes, biological/signaling pathway genes), G2G generates fully-descriptive alignments for each gene (i.e. **gene-level alignment**), as well as an average (aggregate) alignment (i.e. **cell-level alignment**) across all genes. 57 | 58 | Below is an example gene-level alignment of the gene *JUNB* in T cell differentiation between a pan-fetal reference and an artificial thymic organoid system: 59 | 60 | .. image:: images/DocFigs2.png 61 | :width: 600 62 | :alt: Example gene-level alignment? 63 | :align: center 64 | | 65 | .. code-block:: 66 | 67 | IIIIIDMMMMMMMMIDDDDD 68 | 69 | Each gene-level alignment carries its 5-state string, an alignment similarity percentage statistic, and the optimal alignment cost (in *nits* -- the unit measure of information). For the above gene, the aligment similarity is 40%, and the total cost of alignment is 53.47 nits. When the degree of difference in gene expression between the reference and query is high, the alignment cost will also be high. 70 | 71 | G2G uses the inferred gene-level alignments to inform: 72 | 73 | #. **The degree of similarity between the two profiles** as an average percentage of alignment similarity across all the genes tested, 74 | #. **An aggregate cell-level alignment across all genes** to inform the average states of match and mismatch between the two profiles (again represented by a 5-state string), 75 | #. **A ranked list of genes across time (from the most distant to most similar)** based on their alignment similarity percentage statistic, 76 | #. **The diversity of different alignment patterns in genes**, by clustering gene-level alignments to identify different matching and mismatching patterns along time, 77 | 78 | between the two single-cell reference and query profiles in comparison. 79 | 80 | For further downstream analysis, G2G provides a wrapper function to check gene-set overrepresentation analysis of the identified gene-clusters and the list of the top distant (differentially-expressed) genes across time, using `GSEApy `_ Enrichr interface. The user is also able to compute an average alignment across any gene subset of their interest. 81 | 82 | **Note**: G2G has been developed only for single-lineage trajectory comparison. In the case of a trajectory with multiple branches, we recommend separating out the singe-lineage branches before any pairwise comparison. 83 | 84 | Our approach to trajectory alignment 85 | ########### 86 | 87 | We employ a **dynamic programming** (DP) based algorithm that can capture both matches and mismatches in gene expression in a unified way. This combines the classical **Gotoh's algorithm** for biological sequence alignment with **dynamic time warping**. Our DP algorithm uses a **Bayesian information-theoretic scoring scheme** based on the **minimum message length** criterion to generate an optimal alignment between two gene trajectories. This scheme evaluates the distributional similarity of gene expression between R and Q for each pair of time points, in terms of both their mean and variance of expression modelled as Gaussian distributions. 88 | For more details on the methods, please see our `manuscript `_. 89 | 90 | Getting started 91 | =========== 92 | 93 | For now, G2G needs to be installed from GitHub: 94 | 95 | .. code-block:: shell 96 | 97 | pip install git+https://github.com/Teichlab/Genes2Genes.git 98 | 99 | The package will be made available soon on PyPi. 100 | 101 | **Input to Gene2Genes** 102 | 103 | G2G takes reference and query input data as ``anndata`` objects, where each ``adata`` object has: 104 | 105 | * log1p normalised gene expression stored at ``adata.X`` 106 | * pseudotime estimates of the cells stored as ``adata.obs['time']`` 107 | 108 | The user can estimate pseudotime of the cells in their datasets using any suitable method available (such as `Diffusion pseudotime `_, `Palantir `_, `GPLVM `_, `Monocle `_ etc.). 109 | For better visualisation and interpretation of the alignment results, we recommend the data to be annotated with their cell types (manually and/or using an automatic annotation tool such as `CellTypist `_). 110 | 111 | Please refer to our `Tutorial `_ for an example analysis between a reference and query dataset from literature. 112 | 113 | Citing Genes2Genes 114 | =========== 115 | Our manuscript is currently available as a `preprint `_ at bioRxiv: 116 | 117 | *Sumanaweera, D., Suo, C., Cujba, A.M., Muraro, D., Dann, E., Polanski, K., Steemers, A.S., Lee, W., Oliver, A.J., Park, J.E. and Meyer, K.B., 2023.* **Gene-level alignment of single cell trajectories**. *bioRxiv, pp.2023-03.* 118 | 119 | This publication is part of the `Human Cell Atlas `_ 120 | 121 | Funding Acknowledgement 122 | =========== 123 | Marie Skłodowska-Curie grant agreement No: 101026506 (Marie Curie Individual Fellowship) under the European Union’s Horizon 2020 research and innovation programme; Wellcome Trust Ph.D. Fellowship for Clinicians; Wellcome Trust (WT206194); ERC Consolidator Grant (646794); Wellcome Sanger Institute’s Translation Committee Fund. 124 | 125 | 126 | .. toctree:: 127 | :hidden: 128 | 129 | self 130 | -------------------------------------------------------------------------------- /doc/build/html/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* Compatability shim for jQuery and underscores.js. 2 | * 3 | * Copyright Sphinx contributors 4 | * Released under the two clause BSD licence 5 | */ 6 | 7 | /** 8 | * small helper function to urldecode strings 9 | * 10 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 11 | */ 12 | jQuery.urldecode = function(x) { 13 | if (!x) { 14 | return x 15 | } 16 | return decodeURIComponent(x.replace(/\+/g, ' ')); 17 | }; 18 | 19 | /** 20 | * small helper function to urlencode strings 21 | */ 22 | jQuery.urlencode = encodeURIComponent; 23 | 24 | /** 25 | * This function returns the parsed url parameters of the 26 | * current request. Multiple values per key are supported, 27 | * it will always return arrays of strings for the value parts. 28 | */ 29 | jQuery.getQueryParameters = function(s) { 30 | if (typeof s === 'undefined') 31 | s = document.location.search; 32 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 33 | var result = {}; 34 | for (var i = 0; i < parts.length; i++) { 35 | var tmp = parts[i].split('=', 2); 36 | var key = jQuery.urldecode(tmp[0]); 37 | var value = jQuery.urldecode(tmp[1]); 38 | if (key in result) 39 | result[key].push(value); 40 | else 41 | result[key] = [value]; 42 | } 43 | return result; 44 | }; 45 | 46 | /** 47 | * highlight a given string on a jquery object by wrapping it in 48 | * span elements with the given class name. 49 | */ 50 | jQuery.fn.highlightText = function(text, className) { 51 | function highlight(node, addItems) { 52 | if (node.nodeType === 3) { 53 | var val = node.nodeValue; 54 | var pos = val.toLowerCase().indexOf(text); 55 | if (pos >= 0 && 56 | !jQuery(node.parentNode).hasClass(className) && 57 | !jQuery(node.parentNode).hasClass("nohighlight")) { 58 | var span; 59 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 60 | if (isInSVG) { 61 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 62 | } else { 63 | span = document.createElement("span"); 64 | span.className = className; 65 | } 66 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 67 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 68 | document.createTextNode(val.substr(pos + text.length)), 69 | node.nextSibling)); 70 | node.nodeValue = val.substr(0, pos); 71 | if (isInSVG) { 72 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 73 | var bbox = node.parentElement.getBBox(); 74 | rect.x.baseVal.value = bbox.x; 75 | rect.y.baseVal.value = bbox.y; 76 | rect.width.baseVal.value = bbox.width; 77 | rect.height.baseVal.value = bbox.height; 78 | rect.setAttribute('class', className); 79 | addItems.push({ 80 | "parent": node.parentNode, 81 | "target": rect}); 82 | } 83 | } 84 | } 85 | else if (!jQuery(node).is("button, select, textarea")) { 86 | jQuery.each(node.childNodes, function() { 87 | highlight(this, addItems); 88 | }); 89 | } 90 | } 91 | var addItems = []; 92 | var result = this.each(function() { 93 | highlight(this, addItems); 94 | }); 95 | for (var i = 0; i < addItems.length; ++i) { 96 | jQuery(addItems[i].parent).before(addItems[i].target); 97 | } 98 | return result; 99 | }; 100 | 101 | /* 102 | * backward compatibility for jQuery.browser 103 | * This will be supported until firefox bug is fixed. 104 | */ 105 | if (!jQuery.browser) { 106 | jQuery.uaMatch = function(ua) { 107 | ua = ua.toLowerCase(); 108 | 109 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 110 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 111 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 112 | /(msie) ([\w.]+)/.exec(ua) || 113 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 114 | []; 115 | 116 | return { 117 | browser: match[ 1 ] || "", 118 | version: match[ 2 ] || "0" 119 | }; 120 | }; 121 | jQuery.browser = {}; 122 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 123 | } 124 | -------------------------------------------------------------------------------- /doc/build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | div.section::after { 19 | display: block; 20 | content: ''; 21 | clear: left; 22 | } 23 | 24 | /* -- relbar ---------------------------------------------------------------- */ 25 | 26 | div.related { 27 | width: 100%; 28 | font-size: 90%; 29 | } 30 | 31 | div.related h3 { 32 | display: none; 33 | } 34 | 35 | div.related ul { 36 | margin: 0; 37 | padding: 0 0 0 10px; 38 | list-style: none; 39 | } 40 | 41 | div.related li { 42 | display: inline; 43 | } 44 | 45 | div.related li.right { 46 | float: right; 47 | margin-right: 5px; 48 | } 49 | 50 | /* -- sidebar --------------------------------------------------------------- */ 51 | 52 | div.sphinxsidebarwrapper { 53 | padding: 10px 5px 0 10px; 54 | } 55 | 56 | div.sphinxsidebar { 57 | float: left; 58 | width: 230px; 59 | margin-left: -100%; 60 | font-size: 90%; 61 | word-wrap: break-word; 62 | overflow-wrap : break-word; 63 | } 64 | 65 | div.sphinxsidebar ul { 66 | list-style: none; 67 | } 68 | 69 | div.sphinxsidebar ul ul, 70 | div.sphinxsidebar ul.want-points { 71 | margin-left: 20px; 72 | list-style: square; 73 | } 74 | 75 | div.sphinxsidebar ul ul { 76 | margin-top: 0; 77 | margin-bottom: 0; 78 | } 79 | 80 | div.sphinxsidebar form { 81 | margin-top: 10px; 82 | } 83 | 84 | div.sphinxsidebar input { 85 | border: 1px solid #98dbcc; 86 | font-family: sans-serif; 87 | font-size: 1em; 88 | } 89 | 90 | div.sphinxsidebar #searchbox form.search { 91 | overflow: hidden; 92 | } 93 | 94 | div.sphinxsidebar #searchbox input[type="text"] { 95 | float: left; 96 | width: 80%; 97 | padding: 0.25em; 98 | box-sizing: border-box; 99 | } 100 | 101 | div.sphinxsidebar #searchbox input[type="submit"] { 102 | float: left; 103 | width: 20%; 104 | border-left: none; 105 | padding: 0.25em; 106 | box-sizing: border-box; 107 | } 108 | 109 | 110 | img { 111 | border: 0; 112 | max-width: 100%; 113 | } 114 | 115 | /* -- search page ----------------------------------------------------------- */ 116 | 117 | ul.search { 118 | margin: 10px 0 0 20px; 119 | padding: 0; 120 | } 121 | 122 | ul.search li { 123 | padding: 5px 0 5px 20px; 124 | background-image: url(file.png); 125 | background-repeat: no-repeat; 126 | background-position: 0 7px; 127 | } 128 | 129 | ul.search li a { 130 | font-weight: bold; 131 | } 132 | 133 | ul.search li p.context { 134 | color: #888; 135 | margin: 2px 0 0 30px; 136 | text-align: left; 137 | } 138 | 139 | ul.keywordmatches li.goodmatch a { 140 | font-weight: bold; 141 | } 142 | 143 | /* -- index page ------------------------------------------------------------ */ 144 | 145 | table.contentstable { 146 | width: 90%; 147 | margin-left: auto; 148 | margin-right: auto; 149 | } 150 | 151 | table.contentstable p.biglink { 152 | line-height: 150%; 153 | } 154 | 155 | a.biglink { 156 | font-size: 1.3em; 157 | } 158 | 159 | span.linkdescr { 160 | font-style: italic; 161 | padding-top: 5px; 162 | font-size: 90%; 163 | } 164 | 165 | /* -- general index --------------------------------------------------------- */ 166 | 167 | table.indextable { 168 | width: 100%; 169 | } 170 | 171 | table.indextable td { 172 | text-align: left; 173 | vertical-align: top; 174 | } 175 | 176 | table.indextable ul { 177 | margin-top: 0; 178 | margin-bottom: 0; 179 | list-style-type: none; 180 | } 181 | 182 | table.indextable > tbody > tr > td > ul { 183 | padding-left: 0em; 184 | } 185 | 186 | table.indextable tr.pcap { 187 | height: 10px; 188 | } 189 | 190 | table.indextable tr.cap { 191 | margin-top: 10px; 192 | background-color: #f2f2f2; 193 | } 194 | 195 | img.toggler { 196 | margin-right: 3px; 197 | margin-top: 3px; 198 | cursor: pointer; 199 | } 200 | 201 | div.modindex-jumpbox { 202 | border-top: 1px solid #ddd; 203 | border-bottom: 1px solid #ddd; 204 | margin: 1em 0 1em 0; 205 | padding: 0.4em; 206 | } 207 | 208 | div.genindex-jumpbox { 209 | border-top: 1px solid #ddd; 210 | border-bottom: 1px solid #ddd; 211 | margin: 1em 0 1em 0; 212 | padding: 0.4em; 213 | } 214 | 215 | /* -- domain module index --------------------------------------------------- */ 216 | 217 | table.modindextable td { 218 | padding: 2px; 219 | border-collapse: collapse; 220 | } 221 | 222 | /* -- general body styles --------------------------------------------------- */ 223 | 224 | div.body { 225 | min-width: 360px; 226 | max-width: 800px; 227 | } 228 | 229 | div.body p, div.body dd, div.body li, div.body blockquote { 230 | -moz-hyphens: auto; 231 | -ms-hyphens: auto; 232 | -webkit-hyphens: auto; 233 | hyphens: auto; 234 | } 235 | 236 | a.headerlink { 237 | visibility: hidden; 238 | } 239 | 240 | h1:hover > a.headerlink, 241 | h2:hover > a.headerlink, 242 | h3:hover > a.headerlink, 243 | h4:hover > a.headerlink, 244 | h5:hover > a.headerlink, 245 | h6:hover > a.headerlink, 246 | dt:hover > a.headerlink, 247 | caption:hover > a.headerlink, 248 | p.caption:hover > a.headerlink, 249 | div.code-block-caption:hover > a.headerlink { 250 | visibility: visible; 251 | } 252 | 253 | div.body p.caption { 254 | text-align: inherit; 255 | } 256 | 257 | div.body td { 258 | text-align: left; 259 | } 260 | 261 | .first { 262 | margin-top: 0 !important; 263 | } 264 | 265 | p.rubric { 266 | margin-top: 30px; 267 | font-weight: bold; 268 | } 269 | 270 | img.align-left, figure.align-left, .figure.align-left, object.align-left { 271 | clear: left; 272 | float: left; 273 | margin-right: 1em; 274 | } 275 | 276 | img.align-right, figure.align-right, .figure.align-right, object.align-right { 277 | clear: right; 278 | float: right; 279 | margin-left: 1em; 280 | } 281 | 282 | img.align-center, figure.align-center, .figure.align-center, object.align-center { 283 | display: block; 284 | margin-left: auto; 285 | margin-right: auto; 286 | } 287 | 288 | img.align-default, figure.align-default, .figure.align-default { 289 | display: block; 290 | margin-left: auto; 291 | margin-right: auto; 292 | } 293 | 294 | .align-left { 295 | text-align: left; 296 | } 297 | 298 | .align-center { 299 | text-align: center; 300 | } 301 | 302 | .align-default { 303 | text-align: center; 304 | } 305 | 306 | .align-right { 307 | text-align: right; 308 | } 309 | 310 | /* -- sidebars -------------------------------------------------------------- */ 311 | 312 | div.sidebar, 313 | aside.sidebar { 314 | margin: 0 0 0.5em 1em; 315 | border: 1px solid #ddb; 316 | padding: 7px; 317 | background-color: #ffe; 318 | width: 40%; 319 | float: right; 320 | clear: right; 321 | overflow-x: auto; 322 | } 323 | 324 | p.sidebar-title { 325 | font-weight: bold; 326 | } 327 | 328 | nav.contents, 329 | aside.topic, 330 | div.admonition, div.topic, blockquote { 331 | clear: left; 332 | } 333 | 334 | /* -- topics ---------------------------------------------------------------- */ 335 | 336 | nav.contents, 337 | aside.topic, 338 | div.topic { 339 | border: 1px solid #ccc; 340 | padding: 7px; 341 | margin: 10px 0 10px 0; 342 | } 343 | 344 | p.topic-title { 345 | font-size: 1.1em; 346 | font-weight: bold; 347 | margin-top: 10px; 348 | } 349 | 350 | /* -- admonitions ----------------------------------------------------------- */ 351 | 352 | div.admonition { 353 | margin-top: 10px; 354 | margin-bottom: 10px; 355 | padding: 7px; 356 | } 357 | 358 | div.admonition dt { 359 | font-weight: bold; 360 | } 361 | 362 | p.admonition-title { 363 | margin: 0px 10px 5px 0px; 364 | font-weight: bold; 365 | } 366 | 367 | div.body p.centered { 368 | text-align: center; 369 | margin-top: 25px; 370 | } 371 | 372 | /* -- content of sidebars/topics/admonitions -------------------------------- */ 373 | 374 | div.sidebar > :last-child, 375 | aside.sidebar > :last-child, 376 | nav.contents > :last-child, 377 | aside.topic > :last-child, 378 | div.topic > :last-child, 379 | div.admonition > :last-child { 380 | margin-bottom: 0; 381 | } 382 | 383 | div.sidebar::after, 384 | aside.sidebar::after, 385 | nav.contents::after, 386 | aside.topic::after, 387 | div.topic::after, 388 | div.admonition::after, 389 | blockquote::after { 390 | display: block; 391 | content: ''; 392 | clear: both; 393 | } 394 | 395 | /* -- tables ---------------------------------------------------------------- */ 396 | 397 | table.docutils { 398 | margin-top: 10px; 399 | margin-bottom: 10px; 400 | border: 0; 401 | border-collapse: collapse; 402 | } 403 | 404 | table.align-center { 405 | margin-left: auto; 406 | margin-right: auto; 407 | } 408 | 409 | table.align-default { 410 | margin-left: auto; 411 | margin-right: auto; 412 | } 413 | 414 | table caption span.caption-number { 415 | font-style: italic; 416 | } 417 | 418 | table caption span.caption-text { 419 | } 420 | 421 | table.docutils td, table.docutils th { 422 | padding: 1px 8px 1px 5px; 423 | border-top: 0; 424 | border-left: 0; 425 | border-right: 0; 426 | border-bottom: 1px solid #aaa; 427 | } 428 | 429 | th { 430 | text-align: left; 431 | padding-right: 5px; 432 | } 433 | 434 | table.citation { 435 | border-left: solid 1px gray; 436 | margin-left: 1px; 437 | } 438 | 439 | table.citation td { 440 | border-bottom: none; 441 | } 442 | 443 | th > :first-child, 444 | td > :first-child { 445 | margin-top: 0px; 446 | } 447 | 448 | th > :last-child, 449 | td > :last-child { 450 | margin-bottom: 0px; 451 | } 452 | 453 | /* -- figures --------------------------------------------------------------- */ 454 | 455 | div.figure, figure { 456 | margin: 0.5em; 457 | padding: 0.5em; 458 | } 459 | 460 | div.figure p.caption, figcaption { 461 | padding: 0.3em; 462 | } 463 | 464 | div.figure p.caption span.caption-number, 465 | figcaption span.caption-number { 466 | font-style: italic; 467 | } 468 | 469 | div.figure p.caption span.caption-text, 470 | figcaption span.caption-text { 471 | } 472 | 473 | /* -- field list styles ----------------------------------------------------- */ 474 | 475 | table.field-list td, table.field-list th { 476 | border: 0 !important; 477 | } 478 | 479 | .field-list ul { 480 | margin: 0; 481 | padding-left: 1em; 482 | } 483 | 484 | .field-list p { 485 | margin: 0; 486 | } 487 | 488 | .field-name { 489 | -moz-hyphens: manual; 490 | -ms-hyphens: manual; 491 | -webkit-hyphens: manual; 492 | hyphens: manual; 493 | } 494 | 495 | /* -- hlist styles ---------------------------------------------------------- */ 496 | 497 | table.hlist { 498 | margin: 1em 0; 499 | } 500 | 501 | table.hlist td { 502 | vertical-align: top; 503 | } 504 | 505 | /* -- object description styles --------------------------------------------- */ 506 | 507 | .sig { 508 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 509 | } 510 | 511 | .sig-name, code.descname { 512 | background-color: transparent; 513 | font-weight: bold; 514 | } 515 | 516 | .sig-name { 517 | font-size: 1.1em; 518 | } 519 | 520 | code.descname { 521 | font-size: 1.2em; 522 | } 523 | 524 | .sig-prename, code.descclassname { 525 | background-color: transparent; 526 | } 527 | 528 | .optional { 529 | font-size: 1.3em; 530 | } 531 | 532 | .sig-paren { 533 | font-size: larger; 534 | } 535 | 536 | .sig-param.n { 537 | font-style: italic; 538 | } 539 | 540 | /* C++ specific styling */ 541 | 542 | .sig-inline.c-texpr, 543 | .sig-inline.cpp-texpr { 544 | font-family: unset; 545 | } 546 | 547 | .sig.c .k, .sig.c .kt, 548 | .sig.cpp .k, .sig.cpp .kt { 549 | color: #0033B3; 550 | } 551 | 552 | .sig.c .m, 553 | .sig.cpp .m { 554 | color: #1750EB; 555 | } 556 | 557 | .sig.c .s, .sig.c .sc, 558 | .sig.cpp .s, .sig.cpp .sc { 559 | color: #067D17; 560 | } 561 | 562 | 563 | /* -- other body styles ----------------------------------------------------- */ 564 | 565 | ol.arabic { 566 | list-style: decimal; 567 | } 568 | 569 | ol.loweralpha { 570 | list-style: lower-alpha; 571 | } 572 | 573 | ol.upperalpha { 574 | list-style: upper-alpha; 575 | } 576 | 577 | ol.lowerroman { 578 | list-style: lower-roman; 579 | } 580 | 581 | ol.upperroman { 582 | list-style: upper-roman; 583 | } 584 | 585 | :not(li) > ol > li:first-child > :first-child, 586 | :not(li) > ul > li:first-child > :first-child { 587 | margin-top: 0px; 588 | } 589 | 590 | :not(li) > ol > li:last-child > :last-child, 591 | :not(li) > ul > li:last-child > :last-child { 592 | margin-bottom: 0px; 593 | } 594 | 595 | ol.simple ol p, 596 | ol.simple ul p, 597 | ul.simple ol p, 598 | ul.simple ul p { 599 | margin-top: 0; 600 | } 601 | 602 | ol.simple > li:not(:first-child) > p, 603 | ul.simple > li:not(:first-child) > p { 604 | margin-top: 0; 605 | } 606 | 607 | ol.simple p, 608 | ul.simple p { 609 | margin-bottom: 0; 610 | } 611 | 612 | aside.footnote > span, 613 | div.citation > span { 614 | float: left; 615 | } 616 | aside.footnote > span:last-of-type, 617 | div.citation > span:last-of-type { 618 | padding-right: 0.5em; 619 | } 620 | aside.footnote > p { 621 | margin-left: 2em; 622 | } 623 | div.citation > p { 624 | margin-left: 4em; 625 | } 626 | aside.footnote > p:last-of-type, 627 | div.citation > p:last-of-type { 628 | margin-bottom: 0em; 629 | } 630 | aside.footnote > p:last-of-type:after, 631 | div.citation > p:last-of-type:after { 632 | content: ""; 633 | clear: both; 634 | } 635 | 636 | dl.field-list { 637 | display: grid; 638 | grid-template-columns: fit-content(30%) auto; 639 | } 640 | 641 | dl.field-list > dt { 642 | font-weight: bold; 643 | word-break: break-word; 644 | padding-left: 0.5em; 645 | padding-right: 5px; 646 | } 647 | 648 | dl.field-list > dd { 649 | padding-left: 0.5em; 650 | margin-top: 0em; 651 | margin-left: 0em; 652 | margin-bottom: 0em; 653 | } 654 | 655 | dl { 656 | margin-bottom: 15px; 657 | } 658 | 659 | dd > :first-child { 660 | margin-top: 0px; 661 | } 662 | 663 | dd ul, dd table { 664 | margin-bottom: 10px; 665 | } 666 | 667 | dd { 668 | margin-top: 3px; 669 | margin-bottom: 10px; 670 | margin-left: 30px; 671 | } 672 | 673 | .sig dd { 674 | margin-top: 0px; 675 | margin-bottom: 0px; 676 | } 677 | 678 | .sig dl { 679 | margin-top: 0px; 680 | margin-bottom: 0px; 681 | } 682 | 683 | dl > dd:last-child, 684 | dl > dd:last-child > :last-child { 685 | margin-bottom: 0; 686 | } 687 | 688 | dt:target, span.highlighted { 689 | background-color: #fbe54e; 690 | } 691 | 692 | rect.highlighted { 693 | fill: #fbe54e; 694 | } 695 | 696 | dl.glossary dt { 697 | font-weight: bold; 698 | font-size: 1.1em; 699 | } 700 | 701 | .versionmodified { 702 | font-style: italic; 703 | } 704 | 705 | .system-message { 706 | background-color: #fda; 707 | padding: 5px; 708 | border: 3px solid red; 709 | } 710 | 711 | .footnote:target { 712 | background-color: #ffa; 713 | } 714 | 715 | .line-block { 716 | display: block; 717 | margin-top: 1em; 718 | margin-bottom: 1em; 719 | } 720 | 721 | .line-block .line-block { 722 | margin-top: 0; 723 | margin-bottom: 0; 724 | margin-left: 1.5em; 725 | } 726 | 727 | .guilabel, .menuselection { 728 | font-family: sans-serif; 729 | } 730 | 731 | .accelerator { 732 | text-decoration: underline; 733 | } 734 | 735 | .classifier { 736 | font-style: oblique; 737 | } 738 | 739 | .classifier:before { 740 | font-style: normal; 741 | margin: 0 0.5em; 742 | content: ":"; 743 | display: inline-block; 744 | } 745 | 746 | abbr, acronym { 747 | border-bottom: dotted 1px; 748 | cursor: help; 749 | } 750 | 751 | .translated { 752 | background-color: rgba(207, 255, 207, 0.2) 753 | } 754 | 755 | .untranslated { 756 | background-color: rgba(255, 207, 207, 0.2) 757 | } 758 | 759 | /* -- code displays --------------------------------------------------------- */ 760 | 761 | pre { 762 | overflow: auto; 763 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 764 | } 765 | 766 | pre, div[class*="highlight-"] { 767 | clear: both; 768 | } 769 | 770 | span.pre { 771 | -moz-hyphens: none; 772 | -ms-hyphens: none; 773 | -webkit-hyphens: none; 774 | hyphens: none; 775 | white-space: nowrap; 776 | } 777 | 778 | div[class*="highlight-"] { 779 | margin: 1em 0; 780 | } 781 | 782 | td.linenos pre { 783 | border: 0; 784 | background-color: transparent; 785 | color: #aaa; 786 | } 787 | 788 | table.highlighttable { 789 | display: block; 790 | } 791 | 792 | table.highlighttable tbody { 793 | display: block; 794 | } 795 | 796 | table.highlighttable tr { 797 | display: flex; 798 | } 799 | 800 | table.highlighttable td { 801 | margin: 0; 802 | padding: 0; 803 | } 804 | 805 | table.highlighttable td.linenos { 806 | padding-right: 0.5em; 807 | } 808 | 809 | table.highlighttable td.code { 810 | flex: 1; 811 | overflow: hidden; 812 | } 813 | 814 | .highlight .hll { 815 | display: block; 816 | } 817 | 818 | div.highlight pre, 819 | table.highlighttable pre { 820 | margin: 0; 821 | } 822 | 823 | div.code-block-caption + div { 824 | margin-top: 0; 825 | } 826 | 827 | div.code-block-caption { 828 | margin-top: 1em; 829 | padding: 2px 5px; 830 | font-size: small; 831 | } 832 | 833 | div.code-block-caption code { 834 | background-color: transparent; 835 | } 836 | 837 | table.highlighttable td.linenos, 838 | span.linenos, 839 | div.highlight span.gp { /* gp: Generic.Prompt */ 840 | user-select: none; 841 | -webkit-user-select: text; /* Safari fallback only */ 842 | -webkit-user-select: none; /* Chrome/Safari */ 843 | -moz-user-select: none; /* Firefox */ 844 | -ms-user-select: none; /* IE10+ */ 845 | } 846 | 847 | div.code-block-caption span.caption-number { 848 | padding: 0.1em 0.3em; 849 | font-style: italic; 850 | } 851 | 852 | div.code-block-caption span.caption-text { 853 | } 854 | 855 | div.literal-block-wrapper { 856 | margin: 1em 0; 857 | } 858 | 859 | code.xref, a code { 860 | background-color: transparent; 861 | font-weight: bold; 862 | } 863 | 864 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 865 | background-color: transparent; 866 | } 867 | 868 | .viewcode-link { 869 | float: right; 870 | } 871 | 872 | .viewcode-back { 873 | float: right; 874 | font-family: sans-serif; 875 | } 876 | 877 | div.viewcode-block:target { 878 | margin: -1px -10px; 879 | padding: 0 10px; 880 | } 881 | 882 | /* -- math display ---------------------------------------------------------- */ 883 | 884 | img.math { 885 | vertical-align: middle; 886 | } 887 | 888 | div.body div.math p { 889 | text-align: center; 890 | } 891 | 892 | span.eqno { 893 | float: right; 894 | } 895 | 896 | span.eqno a.headerlink { 897 | position: absolute; 898 | z-index: 1; 899 | } 900 | 901 | div.math:hover a.headerlink { 902 | visibility: visible; 903 | } 904 | 905 | /* -- printout stylesheet --------------------------------------------------- */ 906 | 907 | @media print { 908 | div.document, 909 | div.documentwrapper, 910 | div.bodywrapper { 911 | margin: 0 !important; 912 | width: 100%; 913 | } 914 | 915 | div.sphinxsidebar, 916 | div.related, 917 | div.footer, 918 | #top-link { 919 | display: none; 920 | } 921 | } -------------------------------------------------------------------------------- /doc/build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /doc/build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /doc/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 14 | "TEXTAREA", 15 | "INPUT", 16 | "SELECT", 17 | "BUTTON", 18 | ]); 19 | 20 | const _ready = (callback) => { 21 | if (document.readyState !== "loading") { 22 | callback(); 23 | } else { 24 | document.addEventListener("DOMContentLoaded", callback); 25 | } 26 | }; 27 | 28 | /** 29 | * Small JavaScript module for the documentation. 30 | */ 31 | const Documentation = { 32 | init: () => { 33 | Documentation.initDomainIndexTable(); 34 | Documentation.initOnKeyListeners(); 35 | }, 36 | 37 | /** 38 | * i18n support 39 | */ 40 | TRANSLATIONS: {}, 41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 42 | LOCALE: "unknown", 43 | 44 | // gettext and ngettext don't access this so that the functions 45 | // can safely bound to a different name (_ = Documentation.gettext) 46 | gettext: (string) => { 47 | const translated = Documentation.TRANSLATIONS[string]; 48 | switch (typeof translated) { 49 | case "undefined": 50 | return string; // no translation 51 | case "string": 52 | return translated; // translation exists 53 | default: 54 | return translated[0]; // (singular, plural) translation tuple exists 55 | } 56 | }, 57 | 58 | ngettext: (singular, plural, n) => { 59 | const translated = Documentation.TRANSLATIONS[singular]; 60 | if (typeof translated !== "undefined") 61 | return translated[Documentation.PLURAL_EXPR(n)]; 62 | return n === 1 ? singular : plural; 63 | }, 64 | 65 | addTranslations: (catalog) => { 66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 67 | Documentation.PLURAL_EXPR = new Function( 68 | "n", 69 | `return (${catalog.plural_expr})` 70 | ); 71 | Documentation.LOCALE = catalog.locale; 72 | }, 73 | 74 | /** 75 | * helper function to focus on search bar 76 | */ 77 | focusSearchBar: () => { 78 | document.querySelectorAll("input[name=q]")[0]?.focus(); 79 | }, 80 | 81 | /** 82 | * Initialise the domain index toggle buttons 83 | */ 84 | initDomainIndexTable: () => { 85 | const toggler = (el) => { 86 | const idNumber = el.id.substr(7); 87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 88 | if (el.src.substr(-9) === "minus.png") { 89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 90 | toggledRows.forEach((el) => (el.style.display = "none")); 91 | } else { 92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 93 | toggledRows.forEach((el) => (el.style.display = "")); 94 | } 95 | }; 96 | 97 | const togglerElements = document.querySelectorAll("img.toggler"); 98 | togglerElements.forEach((el) => 99 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 100 | ); 101 | togglerElements.forEach((el) => (el.style.display = "")); 102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 103 | }, 104 | 105 | initOnKeyListeners: () => { 106 | // only install a listener if it is really needed 107 | if ( 108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 110 | ) 111 | return; 112 | 113 | document.addEventListener("keydown", (event) => { 114 | // bail for input elements 115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 116 | // bail with special keys 117 | if (event.altKey || event.ctrlKey || event.metaKey) return; 118 | 119 | if (!event.shiftKey) { 120 | switch (event.key) { 121 | case "ArrowLeft": 122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 123 | 124 | const prevLink = document.querySelector('link[rel="prev"]'); 125 | if (prevLink && prevLink.href) { 126 | window.location.href = prevLink.href; 127 | event.preventDefault(); 128 | } 129 | break; 130 | case "ArrowRight": 131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 132 | 133 | const nextLink = document.querySelector('link[rel="next"]'); 134 | if (nextLink && nextLink.href) { 135 | window.location.href = nextLink.href; 136 | event.preventDefault(); 137 | } 138 | break; 139 | } 140 | } 141 | 142 | // some keyboard layouts may need Shift to get / 143 | switch (event.key) { 144 | case "/": 145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 146 | Documentation.focusSearchBar(); 147 | event.preventDefault(); 148 | } 149 | }); 150 | }, 151 | }; 152 | 153 | // quick alias for translations 154 | const _ = Documentation.gettext; 155 | 156 | _ready(Documentation.init); 157 | -------------------------------------------------------------------------------- /doc/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: 'v0.2.0', 4 | LANGUAGE: 'en', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: true, 14 | }; -------------------------------------------------------------------------------- /doc/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/file.png -------------------------------------------------------------------------------- /doc/build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /doc/build/html/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /doc/build/html/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /doc/build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /doc/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/minus.png -------------------------------------------------------------------------------- /doc/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/_static/plus.png -------------------------------------------------------------------------------- /doc/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ 21 | .highlight .gr { color: #E40000 } /* Generic.Error */ 22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 23 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 24 | .highlight .go { color: #717171 } /* Generic.Output */ 25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 26 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 28 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 34 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 35 | .highlight .m { color: #666666 } /* Literal.Number */ 36 | .highlight .s { color: #BA2121 } /* Literal.String */ 37 | .highlight .na { color: #687822 } /* Name.Attribute */ 38 | .highlight .nb { color: #008000 } /* Name.Builtin */ 39 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 40 | .highlight .no { color: #880000 } /* Name.Constant */ 41 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #0000FF } /* Name.Function */ 45 | .highlight .nl { color: #767600 } /* Name.Label */ 46 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 48 | .highlight .nv { color: #19177C } /* Name.Variable */ 49 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 50 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 51 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 52 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 53 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 54 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 55 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 65 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 70 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 75 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /doc/build/html/_static/searchtools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * searchtools.js 3 | * ~~~~~~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for the full-text search. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | /** 14 | * Simple result scoring code. 15 | */ 16 | if (typeof Scorer === "undefined") { 17 | var Scorer = { 18 | // Implement the following function to further tweak the score for each result 19 | // The function takes a result array [docname, title, anchor, descr, score, filename] 20 | // and returns the new score. 21 | /* 22 | score: result => { 23 | const [docname, title, anchor, descr, score, filename] = result 24 | return score 25 | }, 26 | */ 27 | 28 | // query matches the full name of an object 29 | objNameMatch: 11, 30 | // or matches in the last dotted part of the object name 31 | objPartialMatch: 6, 32 | // Additive scores depending on the priority of the object 33 | objPrio: { 34 | 0: 15, // used to be importantResults 35 | 1: 5, // used to be objectResults 36 | 2: -5, // used to be unimportantResults 37 | }, 38 | // Used when the priority is not in the mapping. 39 | objPrioDefault: 0, 40 | 41 | // query found in title 42 | title: 15, 43 | partialTitle: 7, 44 | // query found in terms 45 | term: 5, 46 | partialTerm: 2, 47 | }; 48 | } 49 | 50 | const _removeChildren = (element) => { 51 | while (element && element.lastChild) element.removeChild(element.lastChild); 52 | }; 53 | 54 | /** 55 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping 56 | */ 57 | const _escapeRegExp = (string) => 58 | string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string 59 | 60 | const _displayItem = (item, searchTerms) => { 61 | const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; 62 | const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; 63 | const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; 64 | const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; 65 | const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; 66 | 67 | const [docName, title, anchor, descr, score, _filename] = item; 68 | 69 | let listItem = document.createElement("li"); 70 | let requestUrl; 71 | let linkUrl; 72 | if (docBuilder === "dirhtml") { 73 | // dirhtml builder 74 | let dirname = docName + "/"; 75 | if (dirname.match(/\/index\/$/)) 76 | dirname = dirname.substring(0, dirname.length - 6); 77 | else if (dirname === "index/") dirname = ""; 78 | requestUrl = docUrlRoot + dirname; 79 | linkUrl = requestUrl; 80 | } else { 81 | // normal html builders 82 | requestUrl = docUrlRoot + docName + docFileSuffix; 83 | linkUrl = docName + docLinkSuffix; 84 | } 85 | let linkEl = listItem.appendChild(document.createElement("a")); 86 | linkEl.href = linkUrl + anchor; 87 | linkEl.dataset.score = score; 88 | linkEl.innerHTML = title; 89 | if (descr) 90 | listItem.appendChild(document.createElement("span")).innerHTML = 91 | " (" + descr + ")"; 92 | else if (showSearchSummary) 93 | fetch(requestUrl) 94 | .then((responseData) => responseData.text()) 95 | .then((data) => { 96 | if (data) 97 | listItem.appendChild( 98 | Search.makeSearchSummary(data, searchTerms) 99 | ); 100 | }); 101 | Search.output.appendChild(listItem); 102 | }; 103 | const _finishSearch = (resultCount) => { 104 | Search.stopPulse(); 105 | Search.title.innerText = _("Search Results"); 106 | if (!resultCount) 107 | Search.status.innerText = Documentation.gettext( 108 | "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." 109 | ); 110 | else 111 | Search.status.innerText = _( 112 | `Search finished, found ${resultCount} page(s) matching the search query.` 113 | ); 114 | }; 115 | const _displayNextItem = ( 116 | results, 117 | resultCount, 118 | searchTerms 119 | ) => { 120 | // results left, load the summary and display it 121 | // this is intended to be dynamic (don't sub resultsCount) 122 | if (results.length) { 123 | _displayItem(results.pop(), searchTerms); 124 | setTimeout( 125 | () => _displayNextItem(results, resultCount, searchTerms), 126 | 5 127 | ); 128 | } 129 | // search finished, update title and status message 130 | else _finishSearch(resultCount); 131 | }; 132 | 133 | /** 134 | * Default splitQuery function. Can be overridden in ``sphinx.search`` with a 135 | * custom function per language. 136 | * 137 | * The regular expression works by splitting the string on consecutive characters 138 | * that are not Unicode letters, numbers, underscores, or emoji characters. 139 | * This is the same as ``\W+`` in Python, preserving the surrogate pair area. 140 | */ 141 | if (typeof splitQuery === "undefined") { 142 | var splitQuery = (query) => query 143 | .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) 144 | .filter(term => term) // remove remaining empty strings 145 | } 146 | 147 | /** 148 | * Search Module 149 | */ 150 | const Search = { 151 | _index: null, 152 | _queued_query: null, 153 | _pulse_status: -1, 154 | 155 | htmlToText: (htmlString) => { 156 | const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); 157 | htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); 158 | const docContent = htmlElement.querySelector('[role="main"]'); 159 | if (docContent !== undefined) return docContent.textContent; 160 | console.warn( 161 | "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." 162 | ); 163 | return ""; 164 | }, 165 | 166 | init: () => { 167 | const query = new URLSearchParams(window.location.search).get("q"); 168 | document 169 | .querySelectorAll('input[name="q"]') 170 | .forEach((el) => (el.value = query)); 171 | if (query) Search.performSearch(query); 172 | }, 173 | 174 | loadIndex: (url) => 175 | (document.body.appendChild(document.createElement("script")).src = url), 176 | 177 | setIndex: (index) => { 178 | Search._index = index; 179 | if (Search._queued_query !== null) { 180 | const query = Search._queued_query; 181 | Search._queued_query = null; 182 | Search.query(query); 183 | } 184 | }, 185 | 186 | hasIndex: () => Search._index !== null, 187 | 188 | deferQuery: (query) => (Search._queued_query = query), 189 | 190 | stopPulse: () => (Search._pulse_status = -1), 191 | 192 | startPulse: () => { 193 | if (Search._pulse_status >= 0) return; 194 | 195 | const pulse = () => { 196 | Search._pulse_status = (Search._pulse_status + 1) % 4; 197 | Search.dots.innerText = ".".repeat(Search._pulse_status); 198 | if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); 199 | }; 200 | pulse(); 201 | }, 202 | 203 | /** 204 | * perform a search for something (or wait until index is loaded) 205 | */ 206 | performSearch: (query) => { 207 | // create the required interface elements 208 | const searchText = document.createElement("h2"); 209 | searchText.textContent = _("Searching"); 210 | const searchSummary = document.createElement("p"); 211 | searchSummary.classList.add("search-summary"); 212 | searchSummary.innerText = ""; 213 | const searchList = document.createElement("ul"); 214 | searchList.classList.add("search"); 215 | 216 | const out = document.getElementById("search-results"); 217 | Search.title = out.appendChild(searchText); 218 | Search.dots = Search.title.appendChild(document.createElement("span")); 219 | Search.status = out.appendChild(searchSummary); 220 | Search.output = out.appendChild(searchList); 221 | 222 | const searchProgress = document.getElementById("search-progress"); 223 | // Some themes don't use the search progress node 224 | if (searchProgress) { 225 | searchProgress.innerText = _("Preparing search..."); 226 | } 227 | Search.startPulse(); 228 | 229 | // index already loaded, the browser was quick! 230 | if (Search.hasIndex()) Search.query(query); 231 | else Search.deferQuery(query); 232 | }, 233 | 234 | /** 235 | * execute search (requires search index to be loaded) 236 | */ 237 | query: (query) => { 238 | const filenames = Search._index.filenames; 239 | const docNames = Search._index.docnames; 240 | const titles = Search._index.titles; 241 | const allTitles = Search._index.alltitles; 242 | const indexEntries = Search._index.indexentries; 243 | 244 | // stem the search terms and add them to the correct list 245 | const stemmer = new Stemmer(); 246 | const searchTerms = new Set(); 247 | const excludedTerms = new Set(); 248 | const highlightTerms = new Set(); 249 | const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); 250 | splitQuery(query.trim()).forEach((queryTerm) => { 251 | const queryTermLower = queryTerm.toLowerCase(); 252 | 253 | // maybe skip this "word" 254 | // stopwords array is from language_data.js 255 | if ( 256 | stopwords.indexOf(queryTermLower) !== -1 || 257 | queryTerm.match(/^\d+$/) 258 | ) 259 | return; 260 | 261 | // stem the word 262 | let word = stemmer.stemWord(queryTermLower); 263 | // select the correct list 264 | if (word[0] === "-") excludedTerms.add(word.substr(1)); 265 | else { 266 | searchTerms.add(word); 267 | highlightTerms.add(queryTermLower); 268 | } 269 | }); 270 | 271 | if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js 272 | localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) 273 | } 274 | 275 | // console.debug("SEARCH: searching for:"); 276 | // console.info("required: ", [...searchTerms]); 277 | // console.info("excluded: ", [...excludedTerms]); 278 | 279 | // array of [docname, title, anchor, descr, score, filename] 280 | let results = []; 281 | _removeChildren(document.getElementById("search-progress")); 282 | 283 | const queryLower = query.toLowerCase(); 284 | for (const [title, foundTitles] of Object.entries(allTitles)) { 285 | if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { 286 | for (const [file, id] of foundTitles) { 287 | let score = Math.round(100 * queryLower.length / title.length) 288 | results.push([ 289 | docNames[file], 290 | titles[file] !== title ? `${titles[file]} > ${title}` : title, 291 | id !== null ? "#" + id : "", 292 | null, 293 | score, 294 | filenames[file], 295 | ]); 296 | } 297 | } 298 | } 299 | 300 | // search for explicit entries in index directives 301 | for (const [entry, foundEntries] of Object.entries(indexEntries)) { 302 | if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { 303 | for (const [file, id] of foundEntries) { 304 | let score = Math.round(100 * queryLower.length / entry.length) 305 | results.push([ 306 | docNames[file], 307 | titles[file], 308 | id ? "#" + id : "", 309 | null, 310 | score, 311 | filenames[file], 312 | ]); 313 | } 314 | } 315 | } 316 | 317 | // lookup as object 318 | objectTerms.forEach((term) => 319 | results.push(...Search.performObjectSearch(term, objectTerms)) 320 | ); 321 | 322 | // lookup as search terms in fulltext 323 | results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); 324 | 325 | // let the scorer override scores with a custom scoring function 326 | if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); 327 | 328 | // now sort the results by score (in opposite order of appearance, since the 329 | // display function below uses pop() to retrieve items) and then 330 | // alphabetically 331 | results.sort((a, b) => { 332 | const leftScore = a[4]; 333 | const rightScore = b[4]; 334 | if (leftScore === rightScore) { 335 | // same score: sort alphabetically 336 | const leftTitle = a[1].toLowerCase(); 337 | const rightTitle = b[1].toLowerCase(); 338 | if (leftTitle === rightTitle) return 0; 339 | return leftTitle > rightTitle ? -1 : 1; // inverted is intentional 340 | } 341 | return leftScore > rightScore ? 1 : -1; 342 | }); 343 | 344 | // remove duplicate search results 345 | // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept 346 | let seen = new Set(); 347 | results = results.reverse().reduce((acc, result) => { 348 | let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); 349 | if (!seen.has(resultStr)) { 350 | acc.push(result); 351 | seen.add(resultStr); 352 | } 353 | return acc; 354 | }, []); 355 | 356 | results = results.reverse(); 357 | 358 | // for debugging 359 | //Search.lastresults = results.slice(); // a copy 360 | // console.info("search results:", Search.lastresults); 361 | 362 | // print the results 363 | _displayNextItem(results, results.length, searchTerms); 364 | }, 365 | 366 | /** 367 | * search for object names 368 | */ 369 | performObjectSearch: (object, objectTerms) => { 370 | const filenames = Search._index.filenames; 371 | const docNames = Search._index.docnames; 372 | const objects = Search._index.objects; 373 | const objNames = Search._index.objnames; 374 | const titles = Search._index.titles; 375 | 376 | const results = []; 377 | 378 | const objectSearchCallback = (prefix, match) => { 379 | const name = match[4] 380 | const fullname = (prefix ? prefix + "." : "") + name; 381 | const fullnameLower = fullname.toLowerCase(); 382 | if (fullnameLower.indexOf(object) < 0) return; 383 | 384 | let score = 0; 385 | const parts = fullnameLower.split("."); 386 | 387 | // check for different match types: exact matches of full name or 388 | // "last name" (i.e. last dotted part) 389 | if (fullnameLower === object || parts.slice(-1)[0] === object) 390 | score += Scorer.objNameMatch; 391 | else if (parts.slice(-1)[0].indexOf(object) > -1) 392 | score += Scorer.objPartialMatch; // matches in last name 393 | 394 | const objName = objNames[match[1]][2]; 395 | const title = titles[match[0]]; 396 | 397 | // If more than one term searched for, we require other words to be 398 | // found in the name/title/description 399 | const otherTerms = new Set(objectTerms); 400 | otherTerms.delete(object); 401 | if (otherTerms.size > 0) { 402 | const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); 403 | if ( 404 | [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) 405 | ) 406 | return; 407 | } 408 | 409 | let anchor = match[3]; 410 | if (anchor === "") anchor = fullname; 411 | else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; 412 | 413 | const descr = objName + _(", in ") + title; 414 | 415 | // add custom score for some objects according to scorer 416 | if (Scorer.objPrio.hasOwnProperty(match[2])) 417 | score += Scorer.objPrio[match[2]]; 418 | else score += Scorer.objPrioDefault; 419 | 420 | results.push([ 421 | docNames[match[0]], 422 | fullname, 423 | "#" + anchor, 424 | descr, 425 | score, 426 | filenames[match[0]], 427 | ]); 428 | }; 429 | Object.keys(objects).forEach((prefix) => 430 | objects[prefix].forEach((array) => 431 | objectSearchCallback(prefix, array) 432 | ) 433 | ); 434 | return results; 435 | }, 436 | 437 | /** 438 | * search for full-text terms in the index 439 | */ 440 | performTermsSearch: (searchTerms, excludedTerms) => { 441 | // prepare search 442 | const terms = Search._index.terms; 443 | const titleTerms = Search._index.titleterms; 444 | const filenames = Search._index.filenames; 445 | const docNames = Search._index.docnames; 446 | const titles = Search._index.titles; 447 | 448 | const scoreMap = new Map(); 449 | const fileMap = new Map(); 450 | 451 | // perform the search on the required terms 452 | searchTerms.forEach((word) => { 453 | const files = []; 454 | const arr = [ 455 | { files: terms[word], score: Scorer.term }, 456 | { files: titleTerms[word], score: Scorer.title }, 457 | ]; 458 | // add support for partial matches 459 | if (word.length > 2) { 460 | const escapedWord = _escapeRegExp(word); 461 | Object.keys(terms).forEach((term) => { 462 | if (term.match(escapedWord) && !terms[word]) 463 | arr.push({ files: terms[term], score: Scorer.partialTerm }); 464 | }); 465 | Object.keys(titleTerms).forEach((term) => { 466 | if (term.match(escapedWord) && !titleTerms[word]) 467 | arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); 468 | }); 469 | } 470 | 471 | // no match but word was a required one 472 | if (arr.every((record) => record.files === undefined)) return; 473 | 474 | // found search word in contents 475 | arr.forEach((record) => { 476 | if (record.files === undefined) return; 477 | 478 | let recordFiles = record.files; 479 | if (recordFiles.length === undefined) recordFiles = [recordFiles]; 480 | files.push(...recordFiles); 481 | 482 | // set score for the word in each file 483 | recordFiles.forEach((file) => { 484 | if (!scoreMap.has(file)) scoreMap.set(file, {}); 485 | scoreMap.get(file)[word] = record.score; 486 | }); 487 | }); 488 | 489 | // create the mapping 490 | files.forEach((file) => { 491 | if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) 492 | fileMap.get(file).push(word); 493 | else fileMap.set(file, [word]); 494 | }); 495 | }); 496 | 497 | // now check if the files don't contain excluded terms 498 | const results = []; 499 | for (const [file, wordList] of fileMap) { 500 | // check if all requirements are matched 501 | 502 | // as search terms with length < 3 are discarded 503 | const filteredTermCount = [...searchTerms].filter( 504 | (term) => term.length > 2 505 | ).length; 506 | if ( 507 | wordList.length !== searchTerms.size && 508 | wordList.length !== filteredTermCount 509 | ) 510 | continue; 511 | 512 | // ensure that none of the excluded terms is in the search result 513 | if ( 514 | [...excludedTerms].some( 515 | (term) => 516 | terms[term] === file || 517 | titleTerms[term] === file || 518 | (terms[term] || []).includes(file) || 519 | (titleTerms[term] || []).includes(file) 520 | ) 521 | ) 522 | break; 523 | 524 | // select one (max) score for the file. 525 | const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); 526 | // add result to the result list 527 | results.push([ 528 | docNames[file], 529 | titles[file], 530 | "", 531 | null, 532 | score, 533 | filenames[file], 534 | ]); 535 | } 536 | return results; 537 | }, 538 | 539 | /** 540 | * helper function to return a node containing the 541 | * search summary for a given text. keywords is a list 542 | * of stemmed words. 543 | */ 544 | makeSearchSummary: (htmlText, keywords) => { 545 | const text = Search.htmlToText(htmlText); 546 | if (text === "") return null; 547 | 548 | const textLower = text.toLowerCase(); 549 | const actualStartPosition = [...keywords] 550 | .map((k) => textLower.indexOf(k.toLowerCase())) 551 | .filter((i) => i > -1) 552 | .slice(-1)[0]; 553 | const startWithContext = Math.max(actualStartPosition - 120, 0); 554 | 555 | const top = startWithContext === 0 ? "" : "..."; 556 | const tail = startWithContext + 240 < text.length ? "..." : ""; 557 | 558 | let summary = document.createElement("p"); 559 | summary.classList.add("context"); 560 | summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; 561 | 562 | return summary; 563 | }, 564 | }; 565 | 566 | _ready(Search.init); 567 | -------------------------------------------------------------------------------- /doc/build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | parent.insertBefore( 33 | span, 34 | parent.insertBefore( 35 | document.createTextNode(val.substr(pos + text.length)), 36 | node.nextSibling 37 | ) 38 | ); 39 | node.nodeValue = val.substr(0, pos); 40 | 41 | if (isInSVG) { 42 | const rect = document.createElementNS( 43 | "http://www.w3.org/2000/svg", 44 | "rect" 45 | ); 46 | const bbox = parent.getBBox(); 47 | rect.x.baseVal.value = bbox.x; 48 | rect.y.baseVal.value = bbox.y; 49 | rect.width.baseVal.value = bbox.width; 50 | rect.height.baseVal.value = bbox.height; 51 | rect.setAttribute("class", className); 52 | addItems.push({ parent: parent, target: rect }); 53 | } 54 | } 55 | } else if (node.matches && !node.matches("button, select, textarea")) { 56 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 57 | } 58 | }; 59 | const _highlightText = (thisNode, text, className) => { 60 | let addItems = []; 61 | _highlight(thisNode, addItems, text, className); 62 | addItems.forEach((obj) => 63 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 64 | ); 65 | }; 66 | 67 | /** 68 | * Small JavaScript module for the documentation. 69 | */ 70 | const SphinxHighlight = { 71 | 72 | /** 73 | * highlight the search words provided in localstorage in the text 74 | */ 75 | highlightSearchWords: () => { 76 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 77 | 78 | // get and clear terms from localstorage 79 | const url = new URL(window.location); 80 | const highlight = 81 | localStorage.getItem("sphinx_highlight_terms") 82 | || url.searchParams.get("highlight") 83 | || ""; 84 | localStorage.removeItem("sphinx_highlight_terms") 85 | url.searchParams.delete("highlight"); 86 | window.history.replaceState({}, "", url); 87 | 88 | // get individual terms from highlight string 89 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 90 | if (terms.length === 0) return; // nothing to do 91 | 92 | // There should never be more than one element matching "div.body" 93 | const divBody = document.querySelectorAll("div.body"); 94 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 95 | window.setTimeout(() => { 96 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 97 | }, 10); 98 | 99 | const searchBox = document.getElementById("searchbox"); 100 | if (searchBox === null) return; 101 | searchBox.appendChild( 102 | document 103 | .createRange() 104 | .createContextualFragment( 105 | '" 109 | ) 110 | ); 111 | }, 112 | 113 | /** 114 | * helper function to hide the search marks again 115 | */ 116 | hideSearchWords: () => { 117 | document 118 | .querySelectorAll("#searchbox .highlight-link") 119 | .forEach((el) => el.remove()); 120 | document 121 | .querySelectorAll("span.highlighted") 122 | .forEach((el) => el.classList.remove("highlighted")); 123 | localStorage.removeItem("sphinx_highlight_terms") 124 | }, 125 | 126 | initEscapeListener: () => { 127 | // only install a listener if it is really needed 128 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 129 | 130 | document.addEventListener("keydown", (event) => { 131 | // bail for input elements 132 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 133 | // bail with special keys 134 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 135 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 136 | SphinxHighlight.hideSearchWords(); 137 | event.preventDefault(); 138 | } 139 | }); 140 | }, 141 | }; 142 | 143 | _ready(SphinxHighlight.highlightSearchWords); 144 | _ready(SphinxHighlight.initEscapeListener); 145 | -------------------------------------------------------------------------------- /doc/build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Index — Genes2Genes v0.2.0 documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 51 | 52 |
56 | 57 |
58 |
59 |
60 |
    61 |
  • 62 | 63 |
  • 64 |
  • 65 |
66 |
67 |
68 |
69 |
70 | 71 | 72 |

Index

73 | 74 |
75 | 76 |
77 | 78 | 79 |
80 |
81 |
82 | 83 |
84 | 85 |
86 |

© Copyright 2023, Dinithi Sumanaweera, Teichmann Lab.

87 |
88 | 89 | Built with Sphinx using a 90 | theme 91 | provided by Read the Docs. 92 | 93 | 94 |
95 |
96 |
97 |
98 |
99 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /doc/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Genes2Genes — Genes2Genes v0.2.0 documentation 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 52 | 53 |
57 | 58 |
59 |
60 |
61 | 68 |
69 |
70 |
71 |
72 | 73 |
74 |

Genes2Genes

75 |

A framework for single-cell pseudotime trajectory alignment

76 | 91 |

Genes2Genes (G2G) is a new Python framework for aligning single-cell pseudotime trajectories of gene expression between any reference and query for a pairwise comparison such as:

92 |
93 |
    94 |
  • Organoid vs. Reference tissue

  • 95 |
  • Control vs. Treatment

  • 96 |
  • Healthy vs. Disease

  • 97 |
98 |
99 |
100 |

Trajectory alignment: What & Why?

101 |

A single-cell trajectory describes the transcriptomic state of cells along some axis of progression (such as time), due to undergoing some dynamic process (e.g. cell differentiation, treatment response, or disease infection). Given an scRNA-seq profile, there are various tools available today to infer such trajectory by estimating a pseudo ordering of the cells along an axis, commonly referred to as ‘pseudotime’. The pseudotime axis of a trajectory can be descritized to represent it as a sequence of discrete time points. Given two such discrete pseudotime sequences of two trajectories, a pairwise alignment between them defines a non-linear mapping between their time points. This mapping could have 1-to-1 matches as well as 1-to-many/many-to-1 matches (a.k.a warps) between the time points, while unmapping the time points which have significantly different transcriptomic states. Below is an example visualization of two cell differentiation trajectories.

102 | What is trajectory alignment? 103 |

For two trajectories representing single lineages as above, G2G generates an optimal pairwise trajectory alignment that captures the matches and mismatches between their time points in sequential order, allowing a user to quantify the degree of similarity between them.

104 | Example mapping 105 |
106 |

107 |
108 |

G2G defines 5 different states of alignment between any two R and Q time points, corresponding to all possible match and mismatch states. They are: 1-to-1 match (M), 1-to-many match (V), many-to-1 match (W), insertion (I) and deletion (D). Here, I or D refer to a mismatched time point in Q or R, respectively. These states jointly cover the alignment states defined in classical dynamic time warping and biological sequence alignment.

109 | 5 states of alignment 110 |
111 |

112 |
113 |

Accordingly, we can describe any trajectory alignment as a 5-state alignment string. For example, the 5-state alignment string of the above illustrated trajectory alignment is:

114 |
IIIMMMWWWIIIDDDMMMIIIIDDDD
115 | 
116 |
117 |

This G2G alignment string enables us to identify the time regions of match and mismatch along the trajectories. For instance, we can interpret the above illustrated alignment as follow – R and Q trajectories have mid and late mismatches, with the early stage of Q being mismatched, yet starting to match to the early stage of R at the middle of Q’s trajectory. Overall, there are 9 R and Q pseudotime pairs getting matched (with 34.62% alignment similarity).

118 |
119 |
120 |

Outputs from Genes2Genes

121 |

Given an scRNA-seq dataset with their pseudotime estimates and a specified set of genes (e.g. all transcription factors, highly variable genes, biological/signaling pathway genes), G2G generates fully-descriptive alignments for each gene (i.e. gene-level alignment), as well as an average (aggregate) alignment (i.e. cell-level alignment) across all genes.

122 |

Below is an example gene-level alignment of the gene JUNB in T cell differentiation between a pan-fetal reference and an artificial thymic organoid system:

123 | Example gene-level alignment? 124 |
125 |

126 |
127 |
IIIIIDMMMMMMMMIDDDDD
128 | 
129 |
130 |

Each gene-level alignment carries its 5-state string, an alignment similarity percentage statistic, and the optimal alignment cost (in nits – the unit measure of information). For the above gene, the aligment similarity is 40%, and the total cost of alignment is 53.47 nits. When the degree of difference in gene expression between the reference and query is high, the alignment cost will also be high.

131 |

G2G uses the inferred gene-level alignments to inform:

132 |
    133 |
  1. The degree of similarity between the two profiles as an average percentage of alignment similarity across all the genes tested,

  2. 134 |
  3. An aggregate cell-level alignment across all genes to inform the average states of match and mismatch between the two profiles (again represented by a 5-state string),

  4. 135 |
  5. A ranked list of genes across time (from the most distant to most similar) based on their alignment similarity percentage statistic,

  6. 136 |
  7. The diversity of different alignment patterns in genes, by clustering gene-level alignments to identify different matching and mismatching patterns along time,

  8. 137 |
138 |

between the two single-cell reference and query profiles in comparison.

139 |

For further downstream analysis, G2G provides a wrapper function to check gene-set overrepresentation analysis of the identified gene-clusters and the list of the top distant (differentially-expressed) genes across time, using GSEApy Enrichr interface. The user is also able to compute an average alignment across any gene subset of their interest.

140 |

Note: G2G has been developed only for single-lineage trajectory comparison. In the case of a trajectory with multiple branches, we recommend separating out the singe-lineage branches before any pairwise comparison.

141 |
142 |
143 |

Our approach to trajectory alignment

144 |

We employ a dynamic programming (DP) based algorithm that can capture both matches and mismatches in gene expression in a unified way. This combines the classical Gotoh’s algorithm for biological sequence alignment with dynamic time warping. Our DP algorithm uses a Bayesian information-theoretic scoring scheme based on the minimum message length criterion to generate an optimal alignment between two gene trajectories. This scheme evaluates the distributional similarity of gene expression between R and Q for each pair of time points, in terms of both their mean and variance of expression modelled as Gaussian distributions. 145 | For more details on the methods, please see our manuscript.

146 |
147 |
148 |
149 |

Getting started

150 |

For now, G2G needs to be installed from GitHub:

151 |
pip install git+https://github.com/Teichlab/Genes2Genes.git
152 | 
153 |
154 |

The package will be made available soon on PyPi.

155 |

Input to Gene2Genes

156 |

G2G takes reference and query input data as anndata objects, where each adata object has:

157 |
    158 |
  • log1p normalised gene expression stored at adata.X

  • 159 |
  • pseudotime estimates of the cells stored as adata.obs['time']

  • 160 |
161 |

The user can estimate pseudotime of the cells in their datasets using any suitable method available (such as Diffusion pseudotime, Palantir, GPLVM, Monocle etc.). 162 | For better visualisation and interpretation of the alignment results, we recommend the data to be annotated with their cell types (manually and/or using an automatic annotation tool such as CellTypist).

163 |

Please refer to our Tutorial for an example analysis between a reference and query dataset from literature.

164 |
165 |
166 |

Citing Genes2Genes

167 |

Our manuscript is currently available as a preprint at bioRxiv:

168 |

Sumanaweera, D., Suo, C., Cujba, A.M., Muraro, D., Dann, E., Polanski, K., Steemers, A.S., Lee, W., Oliver, A.J., Park, J.E. and Meyer, K.B., 2023. Gene-level alignment of single cell trajectories. bioRxiv, pp.2023-03.

169 |

This publication is part of the Human Cell Atlas

170 |
171 |
172 |

Funding Acknowledgement

173 |

Marie Skłodowska-Curie grant agreement No: 101026506 (Marie Curie Individual Fellowship) under the European Union’s Horizon 2020 research and innovation programme; Wellcome Trust Ph.D. Fellowship for Clinicians; Wellcome Trust (WT206194); ERC Consolidator Grant (646794); Wellcome Sanger Institute’s Translation Committee Fund.

174 |
175 |
176 |
177 | 178 | 179 |
180 |
181 |
182 | 183 |
184 | 185 |
186 |

© Copyright 2023, Dinithi Sumanaweera, Teichmann Lab.

187 |
188 | 189 | Built with Sphinx using a 190 | theme 191 | provided by Read the Docs. 192 | 193 | 194 |
195 |
196 |
197 |
198 |
199 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /doc/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/build/html/objects.inv -------------------------------------------------------------------------------- /doc/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — Genes2Genes v0.2.0 documentation 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 54 | 55 |
59 | 60 |
61 |
62 |
63 |
    64 |
  • 65 | 66 |
  • 67 |
  • 68 |
69 |
70 |
71 |
72 |
73 | 74 | 81 | 82 | 83 |
84 | 85 |
86 | 87 |
88 |
89 |
90 | 91 |
92 | 93 |
94 |

© Copyright 2023, Dinithi Sumanaweera, Teichmann Lab.

95 |
96 | 97 | Built with Sphinx using a 98 | theme 99 | provided by Read the Docs. 100 | 101 | 102 |
103 |
104 |
105 |
106 |
107 | 112 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /doc/build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Genes2Genes"], "terms": {"A": 0, "framework": 0, "singl": 0, "cell": 0, "pseudotim": 0, "g2g": 0, "i": 0, "new": 0, "python": 0, "gene": 0, "express": 0, "between": 0, "ani": 0, "refer": 0, "queri": 0, "pairwis": 0, "comparison": 0, "organoid": 0, "v": 0, "tissu": 0, "control": 0, "treatment": 0, "healthi": 0, "diseas": 0, "describ": 0, "transcriptom": 0, "state": 0, "along": 0, "some": 0, "axi": 0, "progress": 0, "time": 0, "due": 0, "undergo": 0, "dynam": 0, "process": 0, "e": 0, "g": 0, "differenti": 0, "respons": 0, "infect": 0, "given": 0, "an": 0, "scrna": 0, "seq": 0, "profil": 0, "ar": 0, "variou": 0, "tool": 0, "avail": 0, "todai": 0, "infer": 0, "estim": 0, "pseudo": 0, "order": 0, "commonli": 0, "The": 0, "can": 0, "descrit": 0, "repres": 0, "sequenc": 0, "discret": 0, "point": 0, "two": 0, "them": 0, "defin": 0, "non": 0, "linear": 0, "map": 0, "thi": 0, "could": 0, "have": 0, "1": 0, "match": 0, "well": 0, "mani": 0, "k": 0, "warp": 0, "while": 0, "unmap": 0, "which": 0, "significantli": 0, "differ": 0, "below": 0, "exampl": 0, "visual": 0, "For": 0, "lineag": 0, "abov": 0, "gener": 0, "optim": 0, "captur": 0, "mismatch": 0, "sequenti": 0, "allow": 0, "user": 0, "quantifi": 0, "degre": 0, "similar": 0, "5": 0, "r": 0, "q": 0, "correspond": 0, "all": 0, "possibl": 0, "thei": 0, "m": 0, "w": 0, "insert": 0, "delet": 0, "d": 0, "here": 0, "respect": 0, "These": 0, "jointli": 0, "cover": 0, "classic": 0, "biolog": 0, "accordingli": 0, "we": 0, "string": 0, "illustr": 0, "iiimmmwwwiiidddmmmiiiidddd": 0, "enabl": 0, "u": 0, "identifi": 0, "region": 0, "instanc": 0, "interpret": 0, "follow": 0, "mid": 0, "late": 0, "earli": 0, "stage": 0, "being": 0, "yet": 0, "middl": 0, "": 0, "overal": 0, "9": 0, "pair": 0, "34": 0, "62": 0, "dataset": 0, "specifi": 0, "set": 0, "transcript": 0, "factor": 0, "highli": 0, "variabl": 0, "signal": 0, "pathwai": 0, "fulli": 0, "descript": 0, "each": 0, "level": 0, "averag": 0, "aggreg": 0, "across": 0, "junb": 0, "t": 0, "pan": 0, "fetal": 0, "artifici": 0, "thymic": 0, "system": 0, "iiiiidmmmmmmmmiddddd": 0, "carri": 0, "its": 0, "percentag": 0, "statist": 0, "cost": 0, "nit": 0, "unit": 0, "measur": 0, "inform": 0, "alig": 0, "40": 0, "total": 0, "53": 0, "47": 0, "when": 0, "high": 0, "also": 0, "us": 0, "test": 0, "again": 0, "rank": 0, "list": 0, "most": 0, "distant": 0, "base": 0, "divers": 0, "pattern": 0, "cluster": 0, "further": 0, "downstream": 0, "analysi": 0, "provid": 0, "wrapper": 0, "function": 0, "check": 0, "overrepresent": 0, "top": 0, "gseapi": 0, "enrichr": 0, "interfac": 0, "abl": 0, "comput": 0, "subset": 0, "interest": 0, "note": 0, "ha": 0, "been": 0, "develop": 0, "onli": 0, "In": 0, "case": 0, "multipl": 0, "branch": 0, "recommend": 0, "separ": 0, "out": 0, "sing": 0, "befor": 0, "emploi": 0, "program": 0, "dp": 0, "algorithm": 0, "both": 0, "unifi": 0, "wai": 0, "combin": 0, "gotoh": 0, "bayesian": 0, "theoret": 0, "score": 0, "scheme": 0, "minimum": 0, "messag": 0, "length": 0, "criterion": 0, "evalu": 0, "distribut": 0, "term": 0, "mean": 0, "varianc": 0, "model": 0, "gaussian": 0, "more": 0, "detail": 0, "method": 0, "pleas": 0, "see": 0, "manuscript": 0, "now": 0, "need": 0, "instal": 0, "github": 0, "pip": 0, "git": 0, "http": 0, "com": 0, "teichlab": 0, "packag": 0, "made": 0, "soon": 0, "pypi": 0, "input": 0, "gene2gen": 0, "take": 0, "data": 0, "anndata": 0, "object": 0, "where": 0, "adata": 0, "log1p": 0, "normalis": 0, "store": 0, "x": 0, "ob": 0, "suitabl": 0, "diffus": 0, "palantir": 0, "gplvm": 0, "monocl": 0, "etc": 0, "better": 0, "visualis": 0, "result": 0, "annot": 0, "type": 0, "manual": 0, "automat": 0, "celltypist": 0, "tutori": 0, "literatur": 0, "current": 0, "preprint": 0, "biorxiv": 0, "sumanaweera": 0, "suo": 0, "c": 0, "cujba": 0, "muraro": 0, "dann": 0, "polanski": 0, "steemer": 0, "lee": 0, "oliv": 0, "j": 0, "park": 0, "meyer": 0, "b": 0, "2023": 0, "pp": 0, "03": 0, "public": 0, "part": 0, "human": 0, "atla": 0, "mari": 0, "sk\u0142odowska": 0, "curi": 0, "grant": 0, "agreement": 0, "No": 0, "101026506": 0, "individu": 0, "fellowship": 0, "under": 0, "european": 0, "union": 0, "horizon": 0, "2020": 0, "research": 0, "innov": 0, "programm": 0, "wellcom": 0, "trust": 0, "ph": 0, "clinician": 0, "wt206194": 0, "erc": 0, "consolid": 0, "646794": 0, "sanger": 0, "institut": 0, "translat": 0, "committe": 0}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"genes2gen": 0, "content": 0, "trajectori": 0, "align": 0, "what": 0, "why": 0, "output": 0, "from": 0, "our": 0, "approach": 0, "get": 0, "start": 0, "cite": 0, "fund": 0, "acknowledg": 0}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx": 58}, "alltitles": {"Genes2Genes": [[0, "genes2genes"]], "Contents": [[0, "contents"]], "Trajectory alignment: What & Why?": [[0, "trajectory-alignment-what-why"]], "Outputs from Genes2Genes": [[0, "outputs-from-genes2genes"]], "Our approach to trajectory alignment": [[0, "our-approach-to-trajectory-alignment"]], "Getting started": [[0, "getting-started"]], "Citing Genes2Genes": [[0, "citing-genes2genes"]], "Funding Acknowledgement": [[0, "funding-acknowledgement"]]}, "indexentries": {}}) -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'Genes2Genes' 21 | copyright = '2023, Dinithi Sumanaweera, Teichmann Lab' 22 | author = 'Dinithi Sumanaweera, Teichmann Lab' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = 'v0.2.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ["sphinx_rtd_theme",'sphinx.ext.intersphinx'] 34 | # "myst_parser" removed as myst_nb automatically activates it 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = [] 43 | 44 | 45 | # -- Options for HTML output ------------------------------------------------- 46 | 47 | # The theme to use for HTML and HTML Help pages. See the documentation for 48 | # a list of builtin themes. 49 | # 50 | html_theme = 'sphinx_rtd_theme' 51 | 52 | # Add any paths that contain custom static files (such as style sheets) here, 53 | # relative to this directory. They are copied after the builtin static files, 54 | # so a file named "default.css" will overwrite the builtin "default.css". 55 | html_static_path = ['_static'] 56 | -------------------------------------------------------------------------------- /doc/css/alabaster.css: -------------------------------------------------------------------------------- 1 | @import url("basic.css"); 2 | 3 | /* -- page layout ----------------------------------------------------------- */ 4 | 5 | body { 6 | font-family: "Myriad-Pro"; 7 | font-size: 17px; 8 | background-color: #fff; 9 | color: #000; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | 15 | div.document { 16 | width: 940px; 17 | margin: 30px auto 0 auto; 18 | } 19 | 20 | div.documentwrapper { 21 | float: left; 22 | width: 100%; 23 | } 24 | 25 | div.bodywrapper { 26 | margin: 0 0 0 220px; 27 | } 28 | 29 | div.sphinxsidebar { 30 | width: 220px; 31 | font-size: 14px; 32 | line-height: 1.5; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #fff; 41 | color: #3E4349; 42 | padding: 0 30px 0 30px; 43 | } 44 | 45 | div.body > .section { 46 | text-align: left; 47 | } 48 | 49 | div.footer { 50 | width: 940px; 51 | margin: 20px auto 30px auto; 52 | font-size: 14px; 53 | color: #888; 54 | text-align: right; 55 | } 56 | 57 | div.footer a { 58 | color: #888; 59 | } 60 | 61 | p.caption { 62 | font-family: inherit; 63 | font-size: inherit; 64 | } 65 | 66 | 67 | div.relations { 68 | display: none; 69 | } 70 | 71 | 72 | div.sphinxsidebar a { 73 | color: #444; 74 | text-decoration: none; 75 | border-bottom: 1px dotted #999; 76 | } 77 | 78 | div.sphinxsidebar a:hover { 79 | border-bottom: 1px solid #999; 80 | } 81 | 82 | div.sphinxsidebarwrapper { 83 | padding: 18px 10px; 84 | } 85 | 86 | div.sphinxsidebarwrapper p.logo { 87 | padding: 0; 88 | margin: -10px 0 0 0px; 89 | text-align: center; 90 | } 91 | 92 | div.sphinxsidebarwrapper h1.logo { 93 | margin-top: -10px; 94 | text-align: center; 95 | margin-bottom: 5px; 96 | text-align: left; 97 | } 98 | 99 | div.sphinxsidebarwrapper h1.logo-name { 100 | margin-top: 0px; 101 | } 102 | 103 | div.sphinxsidebarwrapper p.blurb { 104 | margin-top: 0; 105 | font-style: normal; 106 | } 107 | 108 | div.sphinxsidebar h3, 109 | div.sphinxsidebar h4 { 110 | font-family: "Myriad-Pro"; 111 | color: #444; 112 | font-size: 24px; 113 | font-weight: normal; 114 | margin: 0 0 5px 0; 115 | padding: 0; 116 | } 117 | 118 | div.sphinxsidebar h4 { 119 | font-size: 20px; 120 | } 121 | 122 | div.sphinxsidebar h3 a { 123 | color: #444; 124 | } 125 | 126 | div.sphinxsidebar p.logo a, 127 | div.sphinxsidebar h3 a, 128 | div.sphinxsidebar p.logo a:hover, 129 | div.sphinxsidebar h3 a:hover { 130 | border: none; 131 | } 132 | 133 | div.sphinxsidebar p { 134 | color: #555; 135 | margin: 10px 0; 136 | } 137 | 138 | div.sphinxsidebar ul { 139 | margin: 10px 0; 140 | padding: 0; 141 | color: #000; 142 | } 143 | 144 | div.sphinxsidebar ul li.toctree-l1 > a { 145 | font-size: 120%; 146 | } 147 | 148 | div.sphinxsidebar ul li.toctree-l2 > a { 149 | font-size: 110%; 150 | } 151 | 152 | div.sphinxsidebar input { 153 | border: 1px solid #CCC; 154 | font-family: "Myriad-Pro"; 155 | font-size: 1em; 156 | } 157 | 158 | div.sphinxsidebar hr { 159 | border: none; 160 | height: 1px; 161 | color: #AAA; 162 | background: #AAA; 163 | 164 | text-align: left; 165 | margin-left: 0; 166 | width: 50%; 167 | } 168 | 169 | div.sphinxsidebar .badge { 170 | border-bottom: none; 171 | } 172 | 173 | div.sphinxsidebar .badge:hover { 174 | border-bottom: none; 175 | } 176 | 177 | /* To address an issue with donation coming after search */ 178 | div.sphinxsidebar h3.donation { 179 | margin-top: 10px; 180 | } 181 | 182 | /* -- body styles ----------------------------------------------------------- */ 183 | 184 | a { 185 | color: #004B6B; 186 | text-decoration: underline; 187 | } 188 | 189 | a:hover { 190 | color: #6D4100; 191 | text-decoration: underline; 192 | } 193 | 194 | div.body h1, 195 | div.body h2, 196 | div.body h3, 197 | div.body h4, 198 | div.body h5, 199 | div.body h6 { 200 | font-family: "Myriad-Pro"; 201 | font-weight: normal; 202 | margin: 30px 0px 10px 0px; 203 | padding: 0; 204 | } 205 | 206 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 207 | div.body h2 { font-size: 180%; } 208 | div.body h3 { font-size: 150%; } 209 | div.body h4 { font-size: 130%; } 210 | div.body h5 { font-size: 100%; } 211 | div.body h6 { font-size: 100%; } 212 | 213 | a.headerlink { 214 | color: #DDD; 215 | padding: 0 4px; 216 | text-decoration: none; 217 | } 218 | 219 | a.headerlink:hover { 220 | color: #444; 221 | background: #EAEAEA; 222 | } 223 | 224 | div.body p, div.body dd, div.body li { 225 | line-height: 1.4em; 226 | } 227 | 228 | div.admonition { 229 | margin: 20px 0px; 230 | padding: 10px 30px; 231 | background-color: #EEE; 232 | border: 1px solid #CCC; 233 | } 234 | 235 | div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { 236 | background-color: #FBFBFB; 237 | border-bottom: 1px solid #fafafa; 238 | } 239 | 240 | div.admonition p.admonition-title { 241 | font-family: "Myriad-Pro"; 242 | font-weight: normal; 243 | font-size: 24px; 244 | margin: 0 0 10px 0; 245 | padding: 0; 246 | line-height: 1; 247 | } 248 | 249 | div.admonition p.last { 250 | margin-bottom: 0; 251 | } 252 | 253 | div.highlight { 254 | background-color: #fff; 255 | } 256 | 257 | dt:target, .highlight { 258 | background: #FAF3E8; 259 | } 260 | 261 | div.warning { 262 | background-color: #FCC; 263 | border: 1px solid #FAA; 264 | } 265 | 266 | div.danger { 267 | background-color: #FCC; 268 | border: 1px solid #FAA; 269 | -moz-box-shadow: 2px 2px 4px #D52C2C; 270 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 271 | box-shadow: 2px 2px 4px #D52C2C; 272 | } 273 | 274 | div.error { 275 | background-color: #FCC; 276 | border: 1px solid #FAA; 277 | -moz-box-shadow: 2px 2px 4px #D52C2C; 278 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 279 | box-shadow: 2px 2px 4px #D52C2C; 280 | } 281 | 282 | div.caution { 283 | background-color: #FCC; 284 | border: 1px solid #FAA; 285 | } 286 | 287 | div.attention { 288 | background-color: #FCC; 289 | border: 1px solid #FAA; 290 | } 291 | 292 | div.important { 293 | background-color: #EEE; 294 | border: 1px solid #CCC; 295 | } 296 | 297 | div.note { 298 | background-color: #EEE; 299 | border: 1px solid #CCC; 300 | } 301 | 302 | div.tip { 303 | background-color: #EEE; 304 | border: 1px solid #CCC; 305 | } 306 | 307 | div.hint { 308 | background-color: #EEE; 309 | border: 1px solid #CCC; 310 | } 311 | 312 | div.seealso { 313 | background-color: #EEE; 314 | border: 1px solid #CCC; 315 | } 316 | 317 | div.topic { 318 | background-color: #EEE; 319 | } 320 | 321 | p.admonition-title { 322 | display: inline; 323 | } 324 | 325 | p.admonition-title:after { 326 | content: ":"; 327 | } 328 | 329 | pre, tt, code { 330 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 331 | font-size: 0.9em; 332 | } 333 | 334 | .hll { 335 | background-color: #FFC; 336 | margin: 0 -12px; 337 | padding: 0 12px; 338 | display: block; 339 | } 340 | 341 | img.screenshot { 342 | } 343 | 344 | tt.descname, tt.descclassname, code.descname, code.descclassname { 345 | font-size: 0.95em; 346 | } 347 | 348 | tt.descname, code.descname { 349 | padding-right: 0.08em; 350 | } 351 | 352 | img.screenshot { 353 | -moz-box-shadow: 2px 2px 4px #EEE; 354 | -webkit-box-shadow: 2px 2px 4px #EEE; 355 | box-shadow: 2px 2px 4px #EEE; 356 | } 357 | 358 | table.docutils { 359 | border: 1px solid #888; 360 | -moz-box-shadow: 2px 2px 4px #EEE; 361 | -webkit-box-shadow: 2px 2px 4px #EEE; 362 | box-shadow: 2px 2px 4px #EEE; 363 | } 364 | 365 | table.docutils td, table.docutils th { 366 | border: 1px solid #888; 367 | padding: 0.25em 0.7em; 368 | } 369 | 370 | table.field-list, table.footnote { 371 | border: none; 372 | -moz-box-shadow: none; 373 | -webkit-box-shadow: none; 374 | box-shadow: none; 375 | } 376 | 377 | table.footnote { 378 | margin: 15px 0; 379 | width: 100%; 380 | border: 1px solid #EEE; 381 | background: #FDFDFD; 382 | font-size: 0.9em; 383 | } 384 | 385 | table.footnote + table.footnote { 386 | margin-top: -15px; 387 | border-top: none; 388 | } 389 | 390 | table.field-list th { 391 | padding: 0 0.8em 0 0; 392 | } 393 | 394 | table.field-list td { 395 | padding: 0; 396 | } 397 | 398 | table.field-list p { 399 | margin-bottom: 0.8em; 400 | } 401 | 402 | /* Cloned from 403 | * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 404 | */ 405 | .field-name { 406 | -moz-hyphens: manual; 407 | -ms-hyphens: manual; 408 | -webkit-hyphens: manual; 409 | hyphens: manual; 410 | } 411 | 412 | table.footnote td.label { 413 | width: .1px; 414 | padding: 0.3em 0 0.3em 0.5em; 415 | } 416 | 417 | table.footnote td { 418 | padding: 0.3em 0.5em; 419 | } 420 | 421 | dl { 422 | margin: 0; 423 | padding: 0; 424 | } 425 | 426 | dl dd { 427 | margin-left: 30px; 428 | } 429 | 430 | blockquote { 431 | margin: 0 0 0 30px; 432 | padding: 0; 433 | } 434 | 435 | ul, ol { 436 | /* Matches the 30px from the narrow-screen "li > ul" selector below */ 437 | margin: 10px 0 10px 30px; 438 | padding: 0; 439 | } 440 | 441 | pre { 442 | background: #EEE; 443 | padding: 7px 30px; 444 | margin: 15px 0px; 445 | line-height: 1.3em; 446 | } 447 | 448 | div.viewcode-block:target { 449 | background: #ffd; 450 | } 451 | 452 | dl pre, blockquote pre, li pre { 453 | margin-left: 0; 454 | padding-left: 30px; 455 | } 456 | 457 | tt, code { 458 | background-color: #ecf0f3; 459 | color: #222; 460 | /* padding: 1px 2px; */ 461 | } 462 | 463 | tt.xref, code.xref, a tt { 464 | background-color: #FBFBFB; 465 | border-bottom: 1px solid #fff; 466 | } 467 | 468 | a.reference { 469 | text-decoration: none; 470 | border-bottom: 1px dotted #004B6B; 471 | } 472 | 473 | /* Don't put an underline on images */ 474 | a.image-reference, a.image-reference:hover { 475 | border-bottom: none; 476 | } 477 | 478 | a.reference:hover { 479 | border-bottom: 1px solid #6D4100; 480 | } 481 | 482 | a.footnote-reference { 483 | text-decoration: none; 484 | font-size: 0.7em; 485 | vertical-align: top; 486 | border-bottom: 1px dotted #004B6B; 487 | } 488 | 489 | a.footnote-reference:hover { 490 | border-bottom: 1px solid #6D4100; 491 | } 492 | 493 | a:hover tt, a:hover code { 494 | background: #EEE; 495 | } 496 | 497 | 498 | @media screen and (max-width: 870px) { 499 | 500 | div.sphinxsidebar { 501 | display: none; 502 | } 503 | 504 | div.document { 505 | width: 100%; 506 | 507 | } 508 | 509 | div.documentwrapper { 510 | margin-left: 0; 511 | margin-top: 0; 512 | margin-right: 0; 513 | margin-bottom: 0; 514 | } 515 | 516 | div.bodywrapper { 517 | margin-top: 0; 518 | margin-right: 0; 519 | margin-bottom: 0; 520 | margin-left: 0; 521 | } 522 | 523 | ul { 524 | margin-left: 0; 525 | } 526 | 527 | li > ul { 528 | /* Matches the 30px from the "ul, ol" selector above */ 529 | margin-left: 30px; 530 | } 531 | 532 | .document { 533 | width: auto; 534 | } 535 | 536 | .footer { 537 | width: auto; 538 | } 539 | 540 | .bodywrapper { 541 | margin: 0; 542 | } 543 | 544 | .footer { 545 | width: auto; 546 | } 547 | 548 | .github { 549 | display: none; 550 | } 551 | 552 | 553 | 554 | } 555 | 556 | 557 | 558 | @media screen and (max-width: 875px) { 559 | 560 | body { 561 | margin: 0; 562 | padding: 20px 30px; 563 | } 564 | 565 | div.documentwrapper { 566 | float: none; 567 | background: #fff; 568 | } 569 | 570 | div.sphinxsidebar { 571 | display: block; 572 | float: none; 573 | width: 102.5%; 574 | margin: 50px -30px -20px -30px; 575 | padding: 10px 20px; 576 | background: #333; 577 | color: #FFF; 578 | } 579 | 580 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 581 | div.sphinxsidebar h3 a { 582 | color: #fff; 583 | } 584 | 585 | div.sphinxsidebar a { 586 | color: #AAA; 587 | } 588 | 589 | div.sphinxsidebar p.logo { 590 | display: none; 591 | } 592 | 593 | div.document { 594 | width: 100%; 595 | margin: 0; 596 | } 597 | 598 | div.footer { 599 | display: none; 600 | } 601 | 602 | div.bodywrapper { 603 | margin: 0; 604 | } 605 | 606 | div.body { 607 | min-height: 0; 608 | padding: 0; 609 | } 610 | 611 | .rtd_doc_footer { 612 | display: none; 613 | } 614 | 615 | .document { 616 | width: auto; 617 | } 618 | 619 | .footer { 620 | width: auto; 621 | } 622 | 623 | .footer { 624 | width: auto; 625 | } 626 | 627 | .github { 628 | display: none; 629 | } 630 | } 631 | 632 | 633 | /* misc. */ 634 | 635 | .revsys-inline { 636 | display: none!important; 637 | } 638 | 639 | /* Make nested-list/multi-paragraph items look better in Releases changelog 640 | * pages. Without this, docutils' magical list fuckery causes inconsistent 641 | * formatting between different release sub-lists. 642 | */ 643 | div#changelog > div.section > ul > li > p:only-child { 644 | margin-bottom: 0; 645 | } 646 | 647 | /* Hide fugly table cell borders in ..bibliography:: directive output */ 648 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th { 649 | border: none; 650 | /* Below needed in some edge cases; if not applied, bottom shadows appear */ 651 | -moz-box-shadow: none; 652 | -webkit-box-shadow: none; 653 | box-shadow: none; 654 | } 655 | 656 | 657 | /* relbar */ 658 | 659 | .related { 660 | line-height: 30px; 661 | width: 100%; 662 | font-size: 0.9rem; 663 | } 664 | 665 | .related.top { 666 | border-bottom: 1px solid #EEE; 667 | margin-bottom: 20px; 668 | } 669 | 670 | .related.bottom { 671 | border-top: 1px solid #EEE; 672 | } 673 | 674 | .related ul { 675 | padding: 0; 676 | margin: 0; 677 | list-style: none; 678 | } 679 | 680 | .related li { 681 | display: inline; 682 | } 683 | 684 | nav#rellinks { 685 | float: right; 686 | } 687 | 688 | nav#rellinks li+li:before { 689 | content: "|"; 690 | } 691 | 692 | nav#breadcrumbs li+li:before { 693 | content: "\00BB"; 694 | } 695 | 696 | /* Hide certain items when printing */ 697 | @media print { 698 | div.related { 699 | display: none; 700 | } 701 | } -------------------------------------------------------------------------------- /doc/images/DocFigs1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/images/DocFigs1-1.png -------------------------------------------------------------------------------- /doc/images/DocFigs1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/images/DocFigs1-3.png -------------------------------------------------------------------------------- /doc/images/DocFigs1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/images/DocFigs1.png -------------------------------------------------------------------------------- /doc/images/DocFigs2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/doc/images/DocFigs2.png -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. Genes2Genes documentation master file, created by 2 | sphinx-quickstart on Wed Aug 16 09:01:25 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Genes2Genes 7 | ======================================= 8 | 9 | **A framework for single-cell pseudotime trajectory alignment** 10 | 11 | .. contents:: Contents 12 | 13 | Genes2Genes (G2G) is a new Python framework for aligning single-cell pseudotime trajectories of gene expression between any reference and query for a pairwise comparison such as: 14 | 15 | * Organoid vs. Reference tissue 16 | * Control vs. Treatment 17 | * Healthy vs. Disease 18 | 19 | Trajectory alignment: What & Why? 20 | ########### 21 | 22 | A single-cell trajectory describes the transcriptomic state of cells along some axis of progression (such as time), due to undergoing some dynamic process (e.g. cell differentiation, treatment response, or disease infection). Given an scRNA-seq profile, there are various tools available today to infer such trajectory by estimating a pseudo ordering of the cells along an axis, commonly referred to as 'pseudotime'. The pseudotime axis of a trajectory can be descritized to represent it as a sequence of discrete time points. Given two such discrete pseudotime sequences of two trajectories, a pairwise alignment between them defines a non-linear mapping between their time points. This mapping could have 1-to-1 matches as well as 1-to-many/many-to-1 matches (a.k.a warps) between the time points, while unmapping the time points which have significantly different transcriptomic states. Below is an example visualization of two cell differentiation trajectories. 23 | 24 | 25 | .. image:: images/DocFigs1.png 26 | :width: 600 27 | :alt: What is trajectory alignment? 28 | :align: center 29 | 30 | For two trajectories representing single lineages as above, G2G generates an **optimal pairwise trajectory alignment** that captures the matches and mismatches between their time points in sequential order, allowing a user to quantify the degree of similarity between them. 31 | 32 | .. image:: images/DocFigs1-1.png 33 | :width: 600 34 | :alt: Example mapping 35 | :align: center 36 | | 37 | G2G defines 5 different states of alignment between any two **R** and **Q** time points, corresponding to all possible match and mismatch states. They are: 1-to-1 match (``M``), 1-to-many match (``V``), many-to-1 match (``W``), insertion (``I``) and deletion (``D``). Here, ``I`` or ``D`` refer to a mismatched time point in Q or R, respectively. These states jointly cover the alignment states defined in classical dynamic time warping and biological sequence alignment. 38 | 39 | .. image:: images/DocFigs1-3.png 40 | :width: 600 41 | :alt: 5 states of alignment 42 | :align: center 43 | | 44 | Accordingly, we can describe any trajectory alignment as a 5-state alignment string. For example, the 5-state alignment string of the above illustrated trajectory alignment is: 45 | 46 | .. code-block:: 47 | 48 | IIIMMMWWWIIIDDDMMMIIIIDDDD 49 | 50 | This G2G alignment string enables us to identify the time regions of match and mismatch along the trajectories. For instance, we can interpret the above illustrated alignment as follow -- *R and Q trajectories have mid and late mismatches, with the early stage of Q being mismatched, yet starting to match to the early stage of R at the middle of Q's trajectory. Overall, there are 9 R and Q pseudotime pairs getting matched (with 34.62% alignment similarity)*. 51 | 52 | 53 | Outputs from Genes2Genes 54 | ########### 55 | 56 | Given an scRNA-seq dataset with their pseudotime estimates and a specified set of genes (e.g. all transcription factors, highly variable genes, biological/signaling pathway genes), G2G generates fully-descriptive alignments for each gene (i.e. **gene-level alignment**), as well as an average (aggregate) alignment (i.e. **cell-level alignment**) across all genes. 57 | 58 | Below is an example gene-level alignment of the gene *JUNB* in T cell differentiation between a pan-fetal reference and an artificial thymic organoid system: 59 | 60 | .. image:: images/DocFigs2.png 61 | :width: 600 62 | :alt: Example gene-level alignment? 63 | :align: center 64 | | 65 | .. code-block:: 66 | 67 | IIIIIDMMMMMMMMIDDDDD 68 | 69 | Each gene-level alignment carries its 5-state string, an alignment similarity percentage statistic, and the optimal alignment cost (in *nits* -- the unit measure of information). For the above gene, the aligment similarity is 40%, and the total cost of alignment is 53.47 nits. When the degree of difference in gene expression between the reference and query is high, the alignment cost will also be high. 70 | 71 | G2G uses the inferred gene-level alignments to inform: 72 | 73 | #. **The degree of similarity between the two profiles** as an average percentage of alignment similarity across all the genes tested, 74 | #. **An aggregate cell-level alignment across all genes** to inform the average states of match and mismatch between the two profiles (again represented by a 5-state string), 75 | #. **A ranked list of genes across time (from the most distant to most similar)** based on their alignment similarity percentage statistic, 76 | #. **The diversity of different alignment patterns in genes**, by clustering gene-level alignments to identify different matching and mismatching patterns along time, 77 | 78 | between the two single-cell reference and query profiles in comparison. 79 | 80 | For further downstream analysis, G2G provides a wrapper function to check gene-set overrepresentation analysis of the identified gene-clusters and the list of the top distant (differentially-expressed) genes across time, using `GSEApy `_ Enrichr interface. The user is also able to compute an average alignment across any gene subset of their interest. 81 | 82 | **Note**: G2G has been developed only for single-lineage trajectory comparison. In the case of a trajectory with multiple branches, we recommend separating out the singe-lineage branches before any pairwise comparison. 83 | 84 | Our approach to trajectory alignment 85 | ########### 86 | 87 | We employ a **dynamic programming** (DP) based algorithm that can capture both matches and mismatches in gene expression in a unified way. This combines the classical **Gotoh's algorithm** for biological sequence alignment with **dynamic time warping**. Our DP algorithm uses a **Bayesian information-theoretic scoring scheme** based on the **minimum message length** criterion to generate an optimal alignment between two gene trajectories. This scheme evaluates the distributional similarity of gene expression between R and Q for each pair of time points, in terms of both their mean and variance of expression modelled as Gaussian distributions. 88 | For more details on the methods, please see our `manuscript `_. 89 | 90 | Getting started 91 | =========== 92 | 93 | For now, G2G needs to be installed from GitHub: 94 | 95 | .. code-block:: shell 96 | 97 | pip install git+https://github.com/Teichlab/Genes2Genes.git 98 | 99 | The package will be made available soon on PyPi. 100 | 101 | **Input to Gene2Genes** 102 | 103 | G2G takes reference and query input data as ``anndata`` objects, where each ``adata`` object has: 104 | 105 | * log1p normalised gene expression stored at ``adata.X`` 106 | * pseudotime estimates of the cells stored as ``adata.obs['time']`` 107 | 108 | The user can estimate pseudotime of the cells in their datasets using any suitable method available (such as `Diffusion pseudotime `_, `Palantir `_, `GPLVM `_, `Monocle `_ etc.). 109 | For better visualisation and interpretation of the alignment results, we recommend the data to be annotated with their cell types (manually and/or using an automatic annotation tool such as `CellTypist `_). 110 | 111 | Please refer to our `Tutorial `_ for an example analysis between a reference and query dataset from literature. 112 | 113 | Citing Genes2Genes 114 | =========== 115 | Our manuscript is currently available as a `preprint `_ at bioRxiv: 116 | 117 | *Sumanaweera, D., Suo, C., Cujba, A.M., Muraro, D., Dann, E., Polanski, K., Steemers, A.S., Lee, W., Oliver, A.J., Park, J.E. and Meyer, K.B., 2023.* **Gene-level alignment of single cell trajectories**. *bioRxiv, pp.2023-03.* 118 | 119 | This publication is part of the `Human Cell Atlas `_ 120 | 121 | Funding Acknowledgement 122 | =========== 123 | Marie Skłodowska-Curie grant agreement No: 101026506 (Marie Curie Individual Fellowship) under the European Union’s Horizon 2020 research and innovation programme; Wellcome Trust Ph.D. Fellowship for Clinicians; Wellcome Trust (WT206194); ERC Consolidator Grant (646794); Wellcome Sanger Institute’s Translation Committee Fund. 124 | 125 | 126 | .. toctree:: 127 | :hidden: 128 | 129 | self 130 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /genes2genes/AlignmentDistMan.py: -------------------------------------------------------------------------------- 1 | import regex 2 | import numpy as np 3 | from tqdm import tqdm 4 | import pandas as pd 5 | from tabulate import tabulate 6 | from scipy.spatial import distance 7 | 8 | """ 9 | This script defines complementary classes and functions for alignment result analysis 10 | """ 11 | 12 | class _TempFSMObj: 13 | 14 | def __init__(self, al_str, gene): 15 | self.al_str = al_str 16 | self.fsm, self.counts = self._get_transition_probs(al_str) 17 | self.al_length = len(al_str) 18 | self.gene = gene 19 | 20 | def _get_transition_probs(self, al_str): 21 | transition_counts = {'MM':0,'MI':0, 'MD':0,'MW':0,'MV':0, 'II':0, 'IM':0,'ID':0, 'IW':0, 'IV':0,'DD':0, 'DM':0,'DI':0, 'DW':0, 'DV':0, 22 | 'WM':0,'WW':0,'WV':0,'WI':0,'WV':0, 'VM':0,'VW':0,'VV':0,'VI':0,'VV':0} 23 | sum_transitions = 0 24 | for key in transition_counts.keys(): 25 | transition_counts[key] = len(regex.findall(key,al_str, overlapped=True)) + 1 # Adding pseudocount to avoid log(0) 26 | sum_transitions += transition_counts[key] 27 | 28 | transition_probs = transition_counts.copy() 29 | 30 | for key in transition_counts.keys(): 31 | transition_probs[key] = transition_counts[key]/sum_transitions 32 | return transition_probs, transition_counts 33 | 34 | def _compute_msg_len(transition_counts, fsm, al_len): 35 | 36 | msg_len = 0.0 37 | for key in transition_counts.keys(): 38 | msg_len = -np.log(fsm[key])*transition_counts[key] 39 | msg_len = msg_len/al_len 40 | return msg_len 41 | 42 | def _pairwise_alignment_dist_v2(a1,a2): 43 | 44 | x1 = _compute_msg_len(a1.counts, a1.fsm, a1.al_length) 45 | y1 = _compute_msg_len(a2.counts, a1.fsm, a2.al_length) 46 | x2 = _compute_msg_len(a1.counts, a2.fsm, a1.al_length) 47 | y2 = _compute_msg_len(a2.counts, a2.fsm, a2.al_length) 48 | 49 | return (np.abs(x1-y1) + np.abs(x2-y2))/2 50 | 51 | def _get_region_str(al_str): 52 | prev = '' 53 | i=0 54 | regions = '' 55 | for i in range(len(al_str)): 56 | if(i==0): 57 | regions += al_str[i] 58 | continue 59 | if(al_str[i-1]==al_str[i]): 60 | continue 61 | else: 62 | regions += al_str[i] 63 | continue 64 | return regions 65 | 66 | def _test_unique_index_sums(a): 67 | index_sum = 0 68 | m = {'M':0,'I':0,'D':0,'W':0,'V':0} 69 | 70 | l = 0 71 | for i in range(len(a)): 72 | if(i==len(a)-1): 73 | if(a[i-1]==a[i]): 74 | m[a[i]] += (index_sum + i)/(l+1) 75 | else: 76 | m[a[i-1]] += index_sum/l 77 | index_sum = 0 78 | m[a[i]] += (index_sum + i) 79 | break 80 | 81 | if(i==0 or a[i-1]==a[i]): 82 | index_sum += i 83 | l+=1 84 | else: 85 | m[a[i-1]] = m[a[i-1]] + (index_sum/l) 86 | index_sum = 0 87 | index_sum += i 88 | l=1 89 | return m 90 | 91 | class AlignmentDist: 92 | 93 | def __init__(self, aligner_obj): 94 | self.alignments = aligner_obj.results 95 | self.gene_list = aligner_obj.gene_list 96 | self.results_map = aligner_obj.results_map 97 | self.results = aligner_obj.results 98 | 99 | # computing pairwise polygon based distance between each pair of alignments in the set of all gene ref-query alignments 100 | def compute_polygon_area_alignment_dist(self): 101 | 102 | DistMat = [] 103 | for i in range(len(self.alignments)): 104 | DistMat.append(np.repeat(-1,len(self.alignments))) 105 | for i in tqdm(range(len(self.alignments))): 106 | for j in range(len(self.alignments)): 107 | if(DistMat[i][j] < 0): 108 | DistMat[i][j] = Utils.compute_alignment_area_diff_distance(self.alignments[i].alignment_str, self.alignments[j].alignment_str 109 | ,self.alignments[i].fwd_DP.S_len, self.alignments[i].fwd_DP.T_len ) 110 | else: 111 | DistMat[i][j] = DistMat[j][i] 112 | DistMat = pd.DataFrame(DistMat) 113 | DistMat.index = self.gene_list 114 | DistMat.columns = self.gene_list 115 | 116 | DistMat/np.max(np.asarray(DistMat).flatten()) 117 | 118 | return DistMat 119 | 120 | def compute_alignment_ensemble_distance_matrix(self, scheme): 121 | 122 | PolygonDistMat = self.compute_polygon_area_alignment_dist() 123 | if(scheme==1): 124 | return PolygonDistMat 125 | 126 | FSA_objects = [] 127 | FSA_objects_regionwise = [] 128 | 129 | for i in range(len(self.alignments)): 130 | FSA_objects.append(_TempFSMObj(self.alignments[i].alignment_str,self.alignments[i].gene ) ) 131 | region_str = _get_region_str(self.alignments[i].alignment_str) 132 | FSA_objects_regionwise.append(_TempFSMObj(region_str,self.alignments[i].gene )) 133 | self.alignments[i].unique_index_sums = list(_test_unique_index_sums(self.alignments[i].alignment_str).values()) 134 | self.alignments[i].region_str = region_str 135 | 136 | Mat = []; Mat_ui = [] 137 | for i in range(len(self.alignments)): 138 | Mat.append(np.repeat(-1.0,len(self.alignments))) 139 | Mat_ui.append(np.repeat(-1.0,len(self.alignments))) 140 | 141 | for i in range(len(self.alignments)): 142 | for j in range(len(self.alignments)): 143 | if(i==j): 144 | Mat[i][j] = 0.0; Mat_ui[i][j] = 0.0 145 | if(Mat[i][j]<0): 146 | Mat[i][j] = _pairwise_alignment_dist_v2(FSA_objects[i],FSA_objects[j]) 147 | Mat_ui[i][j] = distance.euclidean(self.alignments[i].unique_index_sums,self.alignments[j].unique_index_sums) 148 | 149 | LikelihoodDistMat = pd.DataFrame(Mat) 150 | LikelihoodDistMat.columns = self.gene_list 151 | LikelihoodDistMat.index = self.gene_list 152 | LikelihoodDistMat = (LikelihoodDistMat/np.max(np.max(LikelihoodDistMat ))) 153 | IndexSumDistMat = pd.DataFrame(Mat_ui) 154 | IndexSumDistMat.columns = self.gene_list 155 | IndexSumDistMat.index = self.gene_list 156 | IndexSumDistMat = IndexSumDistMat /np.max(np.max(IndexSumDistMat)) 157 | 158 | if(scheme==2): 159 | return LikelihoodDistMat 160 | elif(scheme==3): 161 | return IndexSumDistMat 162 | elif(scheme==0): 163 | joint_mat = PolygonDistMat + LikelihoodDistMat + IndexSumDistMat 164 | return joint_mat/3 165 | elif(scheme==4): 166 | joint_mat = PolygonDistMat + LikelihoodDistMat 167 | return joint_mat/2 168 | elif(scheme==5): 169 | joint_mat = LikelihoodDistMat + IndexSumDistMat 170 | return joint_mat/2 171 | elif(scheme==6): 172 | joint_mat = PolygonDistMat + IndexSumDistMat 173 | return joint_mat/2 174 | 175 | def order_genes_by_alignments(self): 176 | 177 | indices = [] 178 | genes = [] 179 | gene_strs = [] 180 | first_lengths= [] 181 | 182 | for a in self.results: 183 | gene_strs.append(a.alignment_str) 184 | genes.append(a.gene_pair) 185 | w_index = a.alignment_str.find('W') 186 | m_index = a.alignment_str.find('M') 187 | v_index = a.alignment_str.find('V') 188 | if(w_index<0): 189 | w_index = np.inf 190 | if(m_index<0): 191 | m_index = np.inf 192 | if(v_index<0): 193 | v_index = np.inf 194 | 195 | if(m_index<0): 196 | if(w_index >=0 and (w_index=0 and (v_index= interpolation_points[i-1]; range_length = range_length_corner 67 | else: 68 | logic = np.logical_and(cell_pseudotimes <= interpolation_points[i+1], cell_pseudotimes >= interpolation_points[i-1]) 69 | range_length = range_length_mid 70 | 71 | density_stat = np.count_nonzero(logic) 72 | density_stat = density_stat/range_length 73 | cell_density_estimates.append(density_stat) 74 | #print('** per unit cell density: ', cell_density_estimates) 75 | self.cell_density_estimates = cell_density_estimates 76 | cell_density_estimates = [1/x for x in cell_density_estimates] # taking reciprocal for weighing 77 | 78 | #print('reciprocals: ', cell_density_estimates) 79 | # if this has inf values, use the max weight for them (otherwise it becomes inf resulting same weights 1.0 for all cells) 80 | arr = cell_density_estimates 81 | if(np.any(np.isinf(arr))): 82 | max_w = max(np.asarray(arr)[np.isfinite(arr)] ) 83 | cell_density_estimates = np.where(np.isinf(arr), max_w, arr) 84 | #print('** adaptive weights -- ', cell_density_estimates) 85 | 86 | return cell_density_estimates 87 | 88 | def compute_adaptive_window_denominator(self): # for each interpolation time point 89 | 90 | cell_density_adaptive_weights = self.reciprocal_cell_density_estimates 91 | 92 | # using min-max to stretch the range (for highly adapted window sizes having high window sizes) 93 | cell_density_adaptive_weights =np.asarray(cell_density_adaptive_weights) 94 | scaler = MinMaxScaler() 95 | cell_density_adaptive_weights = scaler.fit_transform(cell_density_adaptive_weights.reshape(-1, 1)).flatten() 96 | cell_density_adaptive_weights = cell_density_adaptive_weights * self.k 97 | 98 | # ======= enforcing the same window_size = kernel_WINDOW_SIZE for the interpolation with the least weighted kernel window size 99 | adaptive_window_sizes = [] 100 | for cd in cell_density_adaptive_weights: 101 | adaptive_window_sizes.append(cd*self.kernel_WINDOW_SIZE) #weighing stadard window size 102 | 103 | # find the interpolation point for which the window_size weighted to be lowest -- furthest to kernel_WINDOW_SIZE 104 | temp = list(np.abs(adaptive_window_sizes - np.repeat(self.kernel_WINDOW_SIZE,self.n_bins))) 105 | least_affected_interpolation_point = temp.index(max(temp)) 106 | residue = np.abs(self.kernel_WINDOW_SIZE - adaptive_window_sizes[least_affected_interpolation_point]) 107 | if(self.k>1): # linear scaling to stretch the range of window size from 0.1 base line. 108 | adaptive_window_sizes = adaptive_window_sizes + (residue/(self.k-1)) 109 | else: 110 | adaptive_window_sizes = adaptive_window_sizes + residue 111 | 112 | # compute adaptive window size based denominator of Gaussian kernel for each cell for each interpolation time point 113 | W = [] 114 | for adw in adaptive_window_sizes: 115 | adaptive_W_size = adw**2 116 | W.append(adaptive_W_size) 117 | self.adaptive_window_sizes = adaptive_window_sizes 118 | 119 | return W 120 | 121 | # compute Gaussian weights for each interpolation time point and cell 122 | def compute_Weight_matrix(self): 123 | if(self.adaptive_kernel): 124 | adaptive_win_denoms_mat = np.asarray([np.repeat(a, len(self.cell_pseudotimes)) for a in self.adaptive_win_denoms]) 125 | W_matrix = pd.DataFrame(np.exp(-np.divide(np.array(self.abs_timediff_mat**2), adaptive_win_denoms_mat))) 126 | else: 127 | W_matrix = pd.DataFrame(np.exp(-np.array(self.abs_timediff_mat**2)/self.kernel_WINDOW_SIZE**2)) 128 | W_matrix.columns = self.adata.obs_names 129 | self._real_intpl = self.interpolation_points 130 | #self.interpolation_points = [np.round(i,2) for i in self.interpolation_points] 131 | W_matrix.index = self.interpolation_points 132 | #sb.heatmap(W_matrix) 133 | return W_matrix 134 | 135 | def get_effective_cell_pseudotime_range(self, i, effective_weight_threshold): 136 | effective_weights = self.cell_weight_mat.loc[self.interpolation_points[i]] 137 | cell_names = np.asarray(effective_weights.index) 138 | effective_weights = np.asarray(effective_weights) 139 | cell_ids = np.where(effective_weights>effective_weight_threshold)[0] 140 | effective_cell_names = cell_names[cell_ids] 141 | effective_cell_pseudotimes = self.cell_pseudotimes[cell_ids] 142 | return effective_cell_pseudotimes 143 | 144 | # plotting highly effective cell_contribution regions for given interpolation points based on adaptive weighted gaussian kernel 145 | def plot_effective_regions_for_interpolation_points(self, intpointsIdx2plots, effective_weight_threshold=0.5, plot=True): 146 | 147 | cmap = sb.color_palette("viridis", as_cmap=True) 148 | self.n_effective_cells = [] 149 | for i in intpointsIdx2plots: 150 | x = self.get_effective_cell_pseudotime_range(i, effective_weight_threshold= effective_weight_threshold) 151 | self.n_effective_cells.append(len(x)) 152 | if(plot): 153 | sb.kdeplot(x, fill=True, color=cmap(i/self.n_bins), clip=(0.0,1.0)) 154 | 155 | 156 | """ 157 | The below functions define interpolation functions used by the above Interpolator object 158 | (defined outside class for time efficiency) 159 | """ 160 | # ====================== interpolation process of genes 161 | def compute_stat(row, x, cell_densities, user_given_std): 162 | idx = row.name 163 | if(user_given_std[idx] < 0): 164 | cell_weights_sum = np.sum(row) 165 | 166 | # estimate weighted mean 167 | weighted_mean = np.dot(row, x)/cell_weights_sum 168 | #print(weighted_mean) 169 | 170 | # estimate weighted variance 171 | real_mean = np.mean(x); n = len(row) 172 | weighted_sum_std = np.dot(row, (x - real_mean) ** 2 ) 173 | weighted_std = np.sqrt(weighted_sum_std/(cell_weights_sum * (n-1)/n)) 174 | weighted_std = weighted_std * cell_densities[idx] # weighting according to cell density 175 | else: 176 | weighted_mean = 0.0 177 | weighted_std = user_given_std[idx] # 178 | 179 | D,_,_ = MyFunctions.generate_random_dataset(50, weighted_mean, weighted_std) 180 | return np.asarray([weighted_mean, weighted_std, D], dtype=list) 181 | 182 | #row = list(trajInterpolator.cell_weight_mat.loc[intpl_i]) 183 | def interpolate_gene_v2(i, trajInterpolator, user_given_std): 184 | torch.manual_seed(1) 185 | GENE = trajInterpolator.gene_list[i] 186 | #print(GENE) 187 | x = Utils.csr_mat_col_densify(trajInterpolator.mat ,i) 188 | N_cells= len(trajInterpolator.cell_pseudotimes) 189 | 190 | trajInterpolator.cell_weight_mat.index = range(0,len(trajInterpolator.cell_weight_mat)) 191 | cell_densities = list(trajInterpolator.cell_weight_mat.apply(np.sum, axis=1)/N_cells) 192 | 193 | results = trajInterpolator.cell_weight_mat.apply(compute_stat, axis=1, args = ([x,cell_densities, user_given_std]), result_type='expand') 194 | results = pd.DataFrame(results) 195 | 196 | return SummaryTimeSeries(GENE, results[0], results[1], results[2], trajInterpolator.interpolation_points) 197 | 198 | class SummaryTimeSeries: 199 | """ 200 | This class defines an interpolated time series object that carries the interpolated result of a gene expression time series 201 | """ 202 | 203 | def __init__(self, gene_name, mean_trend, std_trend, intpl_gex, time_points): 204 | self.gene_name = gene_name 205 | self.mean_trend = np.asarray([np.mean(data_bin) for data_bin in intpl_gex]) # interpolated dist mean 206 | self.std_trend = np.asarray([np.std(data_bin) for data_bin in intpl_gex]) # interpolated dist std 207 | self.data_bins = list(intpl_gex) 208 | self.intpl_means = list(mean_trend) # actual weighted means 209 | self.intpl_stds = list(std_trend) # actual weighted stds 210 | self.time_points = np.asarray(time_points) 211 | 212 | self.Y = np.asarray([np.asarray(x) for x in self.data_bins]).flatten() 213 | self.X = np.asarray([np.repeat(t,50) for t in self.time_points]).flatten() 214 | 215 | def plot_mean_trend(self, color='midnightblue'): 216 | sb.lineplot(x= self.time_points, y=self.mean_trend, color=color, linewidth=4) 217 | 218 | def plot_std_trend(self, color='midnightblue'): 219 | sb.lineplot(x= self.time_points, y=self.std_trend, color=color, linewidth=4) 220 | 221 | -------------------------------------------------------------------------------- /genes2genes/Utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csr_matrix 3 | from . import MyFunctions 4 | 5 | # UTIL FUNCTIONS 6 | def csr_mat_col_densify(csr_matrix, j): 7 | start_ptr = csr_matrix.indptr[j] 8 | end_ptr = csr_matrix.indptr[j + 1] 9 | data = csr_matrix.data[start_ptr:end_ptr] 10 | dense_column = np.zeros(csr_matrix.shape[1]) 11 | dense_column[csr_matrix.indices[start_ptr:end_ptr]] = data 12 | return dense_column 13 | 14 | 15 | def minmax_normalise(arr): 16 | 17 | norm_arr = [] 18 | arr = np.asarray(arr) 19 | arr_max = np.max(arr) 20 | arr_min = np.min(arr) 21 | for i in range(len(arr)): 22 | norm_arr.append((arr[i] - arr_min )/(arr_max - arr_min )) 23 | return norm_arr 24 | 25 | 26 | # computes distributional distance under the MML framework 27 | def compute_mml_dist(ref_adata_subset,query_adata_subset, gene): 28 | 29 | ref_data = np.asarray(ref_adata_subset[:,gene].X.todense()).flatten() 30 | query_data = np.asarray(query_adata_subset[:,gene].X.todense()).flatten() 31 | μ_S = np.mean(ref_data) 32 | μ_T = np.mean(query_data) 33 | σ_S =np.std(ref_data) 34 | σ_T =np.std(query_data) 35 | #print(μ_S,μ_T) 36 | if(not np.any(ref_data)): 37 | σ_S = 0.1 38 | if(not np.any(query_data)): 39 | σ_T = 0.1 40 | 41 | I_ref_model, I_refdata_g_ref_model = MyFunctions.run_dist_compute_v3(ref_data, μ_S, σ_S) 42 | I_query_model, I_querydata_g_query_model = MyFunctions.run_dist_compute_v3(query_data, μ_T, σ_T) 43 | I_ref_model, I_querydata_g_ref_model = MyFunctions.run_dist_compute_v3(query_data, μ_S, σ_S) 44 | I_query_model, I_refdata_g_query_model = MyFunctions.run_dist_compute_v3(ref_data, μ_T, σ_T) 45 | 46 | match_encoding_len1 = I_ref_model + I_querydata_g_ref_model + I_refdata_g_ref_model 47 | match_encoding_len1 = match_encoding_len1/(len(query_data)+len(ref_data)) 48 | match_encoding_len2 = I_query_model + I_refdata_g_query_model + I_querydata_g_query_model 49 | match_encoding_len2 = match_encoding_len2/(len(query_data)+len(ref_data)) 50 | match_encoding_len = (match_encoding_len1 + match_encoding_len2 )/2.0 51 | 52 | null = (I_ref_model + I_refdata_g_ref_model + I_query_model + I_querydata_g_query_model)/(len(query_data)+len(ref_data)) 53 | match_compression = match_encoding_len - null 54 | 55 | return match_compression 56 | 57 | 58 | def sample_state(x): 59 | x = np.cumsum(x) 60 | rand_num = np.random.rand(1) 61 | # print(rand_num) 62 | if(rand_num<=x[0]): 63 | return 0#'M' 64 | elif(rand_num>x[0] and rand_num<=x[1]): 65 | return 1#'W' 66 | elif(rand_num>x[1] and rand_num<=x[2]): 67 | return 2#'V' 68 | elif(rand_num>x[2] and rand_num<=x[3]): 69 | return 3#'D' 70 | elif(rand_num>x[3] and rand_num<=x[4]): 71 | return 4#'I' 72 | 73 | 74 | def compute_alignment_area_diff_distance(A1, A2, S_len, T_len): 75 | 76 | pi = np.arange(1, S_len+T_len+1) # skew diagonal indices 77 | A1_ = "" 78 | for c in A1: 79 | A1_ = A1_ + c 80 | if(c=='M'): 81 | A1_ = A1_ + 'X' 82 | A2_ = "" 83 | for c in A2: 84 | A2_ = A2_ + c 85 | if(c=='M'): 86 | A2_ = A2_ + 'X' 87 | 88 | pi_1_k = 0 89 | pi_2_k = 0 90 | #print(0, pi_1_k , pi_2_k ) 91 | A1_al_index = 0 92 | A2_al_index = 0 93 | absolute_dist_sum = 0.0 94 | for k in pi: 95 | #print('k=',k, A1_al_index, A2_al_index) 96 | A1_state = A1_[A1_al_index] 97 | A2_state = A2_[A2_al_index] 98 | if(A1_state=='I' or A1_state=='V'): 99 | pi_1_k = pi_1_k - 1 100 | elif(A1_state=='D' or A1_state=='W'): 101 | pi_1_k = pi_1_k + 1 102 | if(A2_state=='I' or A2_state=='V'): 103 | pi_2_k = pi_2_k - 1 104 | elif(A2_state=='D' or A2_state=='W'): 105 | pi_2_k = pi_2_k + 1 106 | 107 | absolute_dist_sum = absolute_dist_sum + np.abs(pi_1_k - pi_2_k) 108 | #print('-----') 109 | A1_al_index = A1_al_index + 1 110 | A2_al_index = A2_al_index + 1 111 | 112 | return absolute_dist_sum 113 | 114 | def compute_chattergi_coefficient(y1,y2): 115 | df = pd.DataFrame({'S':y1, 'T':y2}) 116 | df['rankS'] = df['S'].rank() 117 | df['rankT'] = df['T'].rank() 118 | # sort df by the S variable first 119 | df = df.sort_values(by='rankS') 120 | return 1 - ((3.0 * df['rankT'].diff().abs().sum())/((len(df)**2)-1)) 121 | 122 | 123 | def plot_different_alignments(paths, S_len, T_len, ax, mat=[]): # pass alignment path coordinates 124 | mat=[] 125 | # if(len(mat)==0): 126 | for i in range(T_len+1): 127 | mat.append(np.repeat(0,S_len+1)) 128 | sb.heatmap(mat, square=True, cmap='viridis', ax=ax, vmin=0, vmax=0, cbar=False,xticklabels=False,yticklabels=False) 129 | path_color = "orange" 130 | 131 | for path in paths: 132 | path_x = [p[0]+0.5 for p in path] 133 | path_y = [p[1]+0.5 for p in path] 134 | ax.plot(path_y, path_x, color=path_color, linewidth=3, alpha=0.5, linestyle='dashed') # path plot 135 | plt.xlabel("S",fontweight='bold') 136 | plt.ylabel("T",fontweight='bold') 137 | 138 | 139 | def check_alignment_clusters(n_clusters , cluster_ids, alignments, n_cols = 5, figsize= (10,6)): 140 | 141 | clusters = [] 142 | S_len = alignments[0].fwd_DP.S_len 143 | T_len = alignments[0].fwd_DP.T_len 144 | 145 | unique_cluster_ids = np.unique(cluster_ids) 146 | n_rows = int(np.ceil(n_clusters/n_cols)) 147 | 148 | 149 | fig, axs = plt.subplots(n_rows,n_cols, figsize = (20,n_rows*3)) # custom -- only for 20 clusters -- TODO change later 150 | axs = axs.flatten() 151 | i = 0 152 | k=1 153 | for cluster_id in range(n_clusters): 154 | paths = [] 155 | cluster_genes = [] 156 | cluster_alignments = np.asarray(alignments)[cluster_ids == unique_cluster_ids[cluster_id]] 157 | for a in cluster_alignments: 158 | paths.append(a.fwd_DP.alignment_path) 159 | #print(a.gene) 160 | cluster_genes.append(a.gene);# cluster_genes.append(a.gene) 161 | clusters.append(list(np.unique(cluster_genes)) ) 162 | 163 | plot_different_alignments(paths, S_len, T_len, axs[cluster_id]) 164 | axs[cluster_id].set_title('Cluster-'+str(i) + ' | '+str(len(cluster_alignments))) 165 | 166 | i=i+1 167 | k=k+1 168 | 169 | fig.tight_layout() 170 | n = n_cols * n_rows 171 | i = 1 172 | while(k<=n): 173 | axs.flat[-1*i].set_visible(False) 174 | k = k+1 175 | i=i+1 176 | 177 | return clusters 178 | 179 | 180 | # input: log1p gene expression vectors 181 | def compute_KLDivBasedDist(x,y): 182 | 183 | # convert to probabilities 184 | x = x.numpy() 185 | y = y.numpy() 186 | # convering backto counts+1 187 | x = np.exp(x) 188 | y = np.exp(y) 189 | x = x/np.sum(x) 190 | y = y/np.sum(y) 191 | 192 | sum_term = 0.0 193 | for i in range(len(x)): 194 | sum_term += x[i]*(np.log(x[i]) - np.log(y[i])) 195 | 196 | return sum_term 197 | 198 | -------------------------------------------------------------------------------- /genes2genes/__init__.py: -------------------------------------------------------------------------------- 1 | """A tool for aligning gene expression trajectories of single-cell reference and query systems""" 2 | __version__ = "0.2.0" 3 | 4 | from . import AlignmentDistMan 5 | from . import ClusterUtils 6 | from . import Main 7 | from . import MyFunctions 8 | from . import OrgAlign 9 | from . import PathwayAnalyser 10 | from . import TimeSeriesPreprocessor 11 | from . import Utils 12 | from . import VisualUtils -------------------------------------------------------------------------------- /images/G2G_framework_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/images/G2G_framework_overview.png -------------------------------------------------------------------------------- /images/G2G_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/images/G2G_logo.png -------------------------------------------------------------------------------- /images/G2G_logo_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/images/G2G_logo_new.png -------------------------------------------------------------------------------- /images/cell_numbers_vs_approx_time_PAM_LPS_G2G_alignment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/images/cell_numbers_vs_approx_time_PAM_LPS_G2G_alignment.png -------------------------------------------------------------------------------- /images/n_interpolation_points_vs_time_PAM_LPS_G2G_alignment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/images/n_interpolation_points_vs_time_PAM_LPS_G2G_alignment.png -------------------------------------------------------------------------------- /notebooks/data/adata_lps_local.h5ad: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/notebooks/data/adata_lps_local.h5ad -------------------------------------------------------------------------------- /notebooks/data/adata_pam_local.h5ad: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teichlab/Genes2Genes/e3d865cbb5b3d4a5b25b885b22ffb164d3c64768/notebooks/data/adata_pam_local.h5ad -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "genes2genes" 7 | authors = [{name = "Dinithi Sumanaweera", email = "ds40@sanger.ac.uk"}] 8 | readme = "README.md" 9 | license = {file = "LICENSE"} 10 | classifiers = [ 11 | "License :: OSI Approved :: MIT License", 12 | "Intended Audience :: Science/Research", 13 | "Topic :: Scientific/Engineering :: Bio-Informatics", 14 | "Development Status :: 5 - Production/Stable", 15 | ] 16 | dynamic = ["version", "description"] 17 | keywords = ["single cell", "trajectory alignment", "dynamic programming"] 18 | dependencies = [ 19 | "anndata", 20 | "regex", 21 | "tabulate", 22 | "gseapy", 23 | "blitzgsea", 24 | "torch", 25 | "scipy", 26 | "scikit-learn", 27 | "tqdm", 28 | "matplotlib", 29 | "numpy<2", 30 | "pandas>=2.0.3", 31 | "seaborn>=0.12.2", 32 | "leven", 33 | 34 | ] 35 | requires-python = ">=3.8" 36 | 37 | [project.urls] 38 | Home = "https://teichlab.github.io/Genes2Genes" 39 | Repository = "https://github.com/Teichlab/Genes2Genes" 40 | --------------------------------------------------------------------------------