├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── conf.py ├── content │ ├── describe_figure.rst │ ├── examples.rst │ ├── images │ │ ├── example_hic.png │ │ ├── example_symmetrical.png │ │ ├── figeno.png │ │ ├── figure_alignments.png │ │ ├── figure_ase.png │ │ ├── figure_basemod.png │ │ ├── figure_bigwig.png │ │ ├── figure_chr.png │ │ ├── figure_foldback.png │ │ ├── figure_genes.png │ │ ├── figure_hic.png │ │ ├── figure_linkSR.png │ │ ├── figure_multiple_asm.png │ │ ├── figure_multiple_bigwigs.png │ │ ├── template_asm.png │ │ ├── template_bigwig.png │ │ ├── template_hic.png │ │ ├── template_wgs_chr.png │ │ └── template_wgs_circos.png │ ├── installation.rst │ └── usage.rst ├── index.rst ├── make.bat └── requirements.txt ├── figeno ├── __init__.py ├── ase.py ├── bam.py ├── cli │ ├── __init__.py │ ├── cli.py │ ├── gui.py │ ├── init.py │ └── make.py ├── data │ ├── __init__.py │ ├── hg19_cytobands.tsv │ ├── hg19_genes.txt.gz │ ├── hg38_cytobands.tsv │ ├── hg38_genes.txt.gz │ ├── mm10_cytobands.tsv │ └── mm10_genes.txt.gz ├── figeno.py ├── genes.py ├── gui │ ├── __init__.py │ ├── gui_browser.py │ ├── gui_server.py │ ├── gui_webview.py │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── figeno_icon.png │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── Basemod.jsx │ │ ├── ColorButton.jsx │ │ ├── DndContainer.jsx │ │ ├── DndItem.jsx │ │ ├── FileDialog.jsx │ │ ├── GeneSelector.jsx │ │ ├── GeneralContainer.jsx │ │ ├── Highlight.jsx │ │ ├── HighlightsContainer.jsx │ │ ├── LoadingScreen.jsx │ │ ├── OutputContainer.jsx │ │ ├── Region.jsx │ │ ├── RegionsContainer.jsx │ │ ├── TemplatePanel.jsx │ │ ├── Track.jsx │ │ ├── TrackTypePanel.jsx │ │ ├── TracksContainer.jsx │ │ ├── images │ │ ├── template_asm.png │ │ ├── template_bigwig.png │ │ ├── template_hic.png │ │ ├── template_wgs_chr.png │ │ └── template_wgs_circos.png │ │ ├── index.css │ │ ├── index.js │ │ ├── style.css │ │ └── useClickOutside.js ├── track_alignments.py ├── track_ase.py ├── track_basemodfreq.py ├── track_bed.py ├── track_bigwig.py ├── track_chr.py ├── track_copynumber.py ├── track_coverage.py ├── track_genes.py ├── track_hic.py ├── track_sv.py └── utils.py ├── pyinstaller ├── figeno.spec ├── figeno_icon.ico └── main.py ├── pyproject.toml ├── test_data ├── GDM1_config.json ├── GDM1_figure.svg ├── GDM1_splitread_config.json ├── GDM1_splitread_figure.svg ├── GDM1_subset.bam ├── GDM1_subset.bam.bai ├── LNCaP_ENCFF282KWR_subset.bigwig ├── LNCaP_config.json ├── LNCaP_figure.svg ├── LNCaP_subset_hg38.cool ├── README.md ├── THP1_CNVs ├── THP1_SV.vcf ├── THP1_circos_config.json ├── THP1_circos_figure.svg ├── THP1_ratio.txt ├── THP1_symmetrical_config.json ├── THP1_symmetrical_figure.png ├── delly.cov.gz ├── delly_CNAs.bed └── purple_cn.tsv └── tests └── test_figeno.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Figeno tests 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | python-version: ["3.8", "3.10","3.12"] 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v3 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | python -m pip install flake8 pytest . 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with pytest 39 | run: | 40 | pytest 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | **/__pycache__ 3 | 4 | /build 5 | /dist 6 | 7 | docs/_build 8 | 9 | figeno/gui/node_modules 10 | figeno/gui/build 11 | figeno/gui/build.zip 12 | test_data/*2.svg 13 | test_data/*2.png 14 | test_data/*svg.png 15 | 16 | pyinstaller/build 17 | pyinstaller/dist 18 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | # formats: 27 | # - pdf 28 | # - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | python: 34 | install: 35 | - requirements: docs/requirements.txt 36 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include figeno/data/*genes.txt.gz 2 | include figeno/data/*cytobands.tsv 3 | recursive-include figeno/gui/build * 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # figeno 2 | 3 | ``` 4 | FIGENO is a 5 | FIGure 6 | GENerator 7 | for GENOmics 8 | ``` 9 | With figeno, you can plot various types of sequencing data along genomic coordinates. Video overview: https://www.youtube.com/watch?v=h1cBeXoSYTA. 10 | 11 | ![figeno](docs/content/images/figeno.png) 12 | Example figures generated with figeno. Left: allele-specific methylation with nanopore data. Right: HiC data across a structural rearrangement. See more examples [here](https://figeno.readthedocs.io/en/latest/content/examples.html). 13 | 14 | ## Features 15 | - Large collection of tracks (bigwig, HiC, alignments with base modifications, WGS with copy numbers and SV…) 16 | - Multi-region figures with interactions across regions 17 | - Graphical user interface, command line interface and python API 18 | - Output figures in vector graphics (svg, pdf) or bitmap (png) 19 | 20 | ## Quick start 21 | ### Linux, MacOS 22 | In an environment with python>=3.7: 23 | ``` 24 | pip install figeno 25 | figeno gui 26 | ``` 27 | This will install figeno and run the graphical user interface (GUI). From the GUI, you can configure the figure and generate it, as well as save the JSON config file which fully defines the figure. The GUI is optional and you can instead use the command line interface: use [figeno init](https://figeno.readthedocs.io/en/latest/content/usage.html#figeno-init) to initialize a config file, edit the config file manually, and generate the figure with [figeno make](https://figeno.readthedocs.io/en/latest/content/usage.html#figeno-make). 28 | 29 | ### Windows 30 | Download figeno_windows.zip from https://github.com/CompEpigen/figeno/releases/latest, unzip it and launch the graphical user interface by executing `figeno.exe`. 31 | 32 | ## Documentation 33 | For more information on how to use figeno, please read the documentation at 34 | https://figeno.readthedocs.io/en/latest/. 35 | 36 | ## Test data 37 | Example input files to test figeno are provided in [test_data](https://github.com/CompEpigen/figeno/tree/main/test_data). 38 | 39 | ## Feedback 40 | If you encounter a bug or would like to have a new feature added, please do not hesitate to [raise an issue](https://github.com/CompEpigen/figeno/issues/new) or to [contact me directly](https://www.dkfz.de/en/CanEpi/staff/kontakt/Sollier_Etienne.php). You can also provide feedback by completing this [anonymous form](https://forms.gle/xb9Ygk6zsJCJUQYm9), which would help me improve figeno! 41 | 42 | ## Citation 43 | If you use figeno in your research, please consider citing: 44 | 45 | Etienne Sollier, Jessica Heilmann, Clarissa Gerhauser, Michael Scherer, Christoph Plass, Pavlo Lutsik. Figeno: multi-region genomic figures with long-read support, Bioinformatics 2024. [https://doi.org/10.1093/bioinformatics/btae354](https://doi.org/10.1093/bioinformatics/btae354) 46 | 47 | -------------------------------------------------------------------------------- /docs/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 ?= 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 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # For the full list of built-in configuration values, see the documentation: 6 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 7 | 8 | # -- Project information ----------------------------------------------------- 9 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 10 | 11 | #MOCK_MODULES = ['pyBigWig', 'pysam', 'numpy', 'pandas', 'vcfpy','matplotlib','cooler','Flask','importlib-resources'] 12 | #for mod_name in MOCK_MODULES: 13 | # sys.modules[mod_name] = mock.Mock() 14 | 15 | project = 'figeno' 16 | copyright = '2024, Etienne Sollier' 17 | author = 'Etienne Sollier' 18 | 19 | # -- General configuration --------------------------------------------------- 20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 21 | 22 | extensions = ['sphinx_rtd_theme','sphinx.ext.autosectionlabel','sphinx_togglebutton'] 23 | 24 | templates_path = ['_templates'] 25 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 26 | 27 | 28 | togglebutton_hint = "Show config file." 29 | togglebutton_hint_hide = "Hide config file." 30 | 31 | 32 | # -- Options for HTML output ------------------------------------------------- 33 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 34 | 35 | html_static_path = ['_static'] 36 | 37 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 38 | html_theme = 'sphinx_rtd_theme' 39 | 40 | if not on_rtd: # only import and set the theme if we're building docs locally 41 | import sphinx_rtd_theme 42 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 43 | -------------------------------------------------------------------------------- /docs/content/images/example_hic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/example_hic.png -------------------------------------------------------------------------------- /docs/content/images/example_symmetrical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/example_symmetrical.png -------------------------------------------------------------------------------- /docs/content/images/figeno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figeno.png -------------------------------------------------------------------------------- /docs/content/images/figure_alignments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_alignments.png -------------------------------------------------------------------------------- /docs/content/images/figure_ase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_ase.png -------------------------------------------------------------------------------- /docs/content/images/figure_basemod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_basemod.png -------------------------------------------------------------------------------- /docs/content/images/figure_bigwig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_bigwig.png -------------------------------------------------------------------------------- /docs/content/images/figure_chr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_chr.png -------------------------------------------------------------------------------- /docs/content/images/figure_foldback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_foldback.png -------------------------------------------------------------------------------- /docs/content/images/figure_genes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_genes.png -------------------------------------------------------------------------------- /docs/content/images/figure_hic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_hic.png -------------------------------------------------------------------------------- /docs/content/images/figure_linkSR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_linkSR.png -------------------------------------------------------------------------------- /docs/content/images/figure_multiple_asm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_multiple_asm.png -------------------------------------------------------------------------------- /docs/content/images/figure_multiple_bigwigs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/figure_multiple_bigwigs.png -------------------------------------------------------------------------------- /docs/content/images/template_asm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/template_asm.png -------------------------------------------------------------------------------- /docs/content/images/template_bigwig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/template_bigwig.png -------------------------------------------------------------------------------- /docs/content/images/template_hic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/template_hic.png -------------------------------------------------------------------------------- /docs/content/images/template_wgs_chr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/template_wgs_chr.png -------------------------------------------------------------------------------- /docs/content/images/template_wgs_circos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/docs/content/images/template_wgs_circos.png -------------------------------------------------------------------------------- /docs/content/installation.rst: -------------------------------------------------------------------------------- 1 | 2 | Installation 3 | ================================== 4 | 5 | 6 | Linux, MacOS 7 | ^^^^^^^^^^^^ 8 | 9 | In an environment with python>=3.7: 10 | 11 | .. code:: bash 12 | 13 | pip install figeno 14 | 15 | .. warning:: 16 | 17 | If pip fails to install pysam, try installing it through conda: ``conda install pysam -c bioconda`` 18 | 19 | 20 | Windows 21 | ^^^^^^^ 22 | 23 | Figeno has some dependencies which do not officially support Windows (pysam, pybigwig), so it is difficult to install as a python package on Windows. To make it easier for windows users to use figeno, we provide windows binaries for figeno. Download figeno_windows.zip from https://github.com/CompEpigen/figeno/releases/latest, extract it, and start the figeno GUI by double-clicking on figene.exe. 24 | 25 | 26 | Building from source 27 | ^^^^^^^^^^^^^^^^^^^^ 28 | 29 | It is easier to install figeno from pip or with the windows binaries. If you want to build figeno from the source code and want to use the GUI, you will first need to build the react app, which requires `nodejs `_ to be installed (giving you access to the npm command), and then install the python package. 30 | 31 | .. code:: bash 32 | 33 | cd figeno/gui 34 | npm install 35 | npm run build 36 | cd ../.. 37 | pip install . 38 | 39 | -------------------------------------------------------------------------------- /docs/content/usage.rst: -------------------------------------------------------------------------------- 1 | 2 | Usage 3 | ================================== 4 | 5 | Basics 6 | ------- 7 | 8 | Figeno generates a figure based on a config file in JSON format. The config file describes where the output file will be stored, which regions will be shown, which tracks will be displayed... (see :ref:`Describing the figure` and `examples of config files `_). 9 | 10 | The easiest way to create the config file is to use the graphical user interface (GUI), either by running ``figeno gui`` if you installed figeno as a python package, or by executing the executable if you downloaded the binaries. From the GUI, you can directly generate the figure, but also save the config file, in case you want to remake the figures later with slightly different parameters. 11 | 12 | Alternatively, you can write the config file manually in a text editor, and then run ``figeno make /path/to/config.json`` to generate the figure. You can also initialize a config file which already contains some tracks using ``figeno init``. 13 | 14 | 15 | Command line interace (CLI) 16 | --------------------------- 17 | 18 | figeno init 19 | ^^^^^^^^^^^ 20 | 21 | Initialize a config file, which will then have to be completed manually. Predefined templates are provided, or you can also provide a list of tracks and regions. 22 | 23 | .. code:: bash 24 | 25 | figeno init [-o config.json] [--template TEMPLATE] [--tracks TRACKS] [--regions REGIONS] [--highlights HIGHLIGHTS] 26 | 27 | Parameters: 28 | 29 | * ``-o``, ``--output``: Path where the configuration file will be stored (default: config.json) 30 | * ``--template``: Used to initialize the track with preset tracks and some options (see :ref:`Templates`). Possible values: 31 | 32 | * bigwig. Will have the following tracks: bigwig, genes, chr_axis. 33 | * hic. Will have the following tracks: hic, genes, chr_axis. 34 | * asm. Will have the following tracks: alignments (with split by haplotype and color by basemod), basemod_freq, genes, chr_axis. 35 | * wgs_chr. Will have the following tracks: sv, copynumber, chr_axis. Will also set margin_above to 0 and unit to Mb for chr_axis. 36 | * wgs_circos. Will have the following tracks: sv, copynumber, chr_axis. Will also set margin_above to 0, max_cn to 3.9 and disable the vertical grid for the copynumber track, set layout to circular and set the regions to all chromosomes. 37 | 38 | * ``--tracks``: comma-separated list of tracks, eg: bigwig,genes,chr_axis. 39 | 40 | * ``--regions``: comma-separated list of regions, eg: 7:156790000-156820000,12:11900000-12100000 41 | 42 | * ``--highlights``: comma-separated list of highlights (same format as regions). 43 | 44 | 45 | figeno make 46 | ^^^^^^^^^^^ 47 | 48 | Generate a figure described by a config file. 49 | 50 | .. code:: bash 51 | 52 | figeno make config.json 53 | 54 | Parameters: 55 | 56 | * config file in json format. 57 | 58 | 59 | figeno gui 60 | ^^^^^^^^^^^ 61 | 62 | Start the graphical user interface. 63 | 64 | .. code:: bash 65 | 66 | figeno gui [--webview] [-p PORT] [--debug] 67 | 68 | Parameters: 69 | 70 | * ``-w``, ``--webview``. If set, will use pywebview to render the GUI. Otherwise, the GUI can be viewed in the browser (at localhost:5000 by default). 71 | 72 | * ``-p``, ``--port``. Port for the local server, in case the browser mode is used (default: 5000). 73 | 74 | * ``--debug``: If set, will print more information to the terminal. 75 | 76 | .. warning:: 77 | The webview mode works for linux, windows and mac, but for linux you will need to install additional dependencies (see https://pywebview.flowrl.com/guide/installation.html#linux). 78 | 79 | 80 | Graphical user interface (GUI) 81 | ------------------------------ 82 | 83 | The GUI can be started with ``figeno gui`` from the command line, or by launching the executable for windows. It can be used to easily edit a config file. Required parameters which have not been filled in yet are highlighted in orange. Once you have finished describing the figure, you can click on "Generate figure" to generate it. You can also save the config file to a json file by clicking on "Save config" if you want to edit it later, in which case you can then load it again with "Load config". You can also combine the CLI and the GUI, for example by creating a config file with the GUI, saving it, and then using ``figeno make`` to generate the figure. 84 | 85 | Python API 86 | ----------- 87 | 88 | You can also import figeno as a python module, and give ``figeno_make`` the config file as a python dictionary. See an example below with four tracks (the full paths to the bigwig and bed files need to be provided). You can add or remove tracks and regions, and use any option listed in :ref:`Describing the figure`. 89 | 90 | 91 | .. code:: python 92 | 93 | from figeno import figeno_make 94 | 95 | config={"general":{"reference":"hg19","layout":"horizontal"}} 96 | config["output"] = {"file":"figure.svg","dpi":200,"width":180} 97 | config["regions"] = [{"chr":"17","start":7534342,"end":7628246}] 98 | config["tracks"] = [ 99 | {"type":"bigwig","file":"/path/to/H3K27ac.bigWig","color":"#e74c3c","label":"H3K27ac"}, 100 | {"type":"bed","file":"/path/to/CGI.bed","color":"#34495e","label":"CpG islands"}, 101 | {"type":"genes"}, 102 | {"type":"chr_axis"} 103 | ] 104 | figeno_make(config) 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Welcome to figeno's documentation! 3 | ================================== 4 | 5 | 6 | 7 | Figeno is a tool for generating **FI**\ gures for **GENO**\ mics. 8 | 9 | 10 | 11 | .. raw:: html 12 | 13 |
14 | 15 |
16 | 17 | Features 18 | --------- 19 | 20 | * Large collection of tracks (bigwig, HiC, alignments, copy number, SV...) 21 | * Multi-region figures 22 | * Graphical user interface 23 | * Highlight regions of interest 24 | * Output figures in svg, pdf, or png 25 | 26 | Table of contents 27 | ----------------- 28 | 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | 33 | content/installation 34 | content/usage 35 | content/describe_figure 36 | content/examples 37 | 38 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 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 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx >=2.1 2 | sphinx_rtd_theme==2.0.0 3 | sphinx-togglebutton>=0.3.2 4 | docutils==0.16 5 | -------------------------------------------------------------------------------- /figeno/__init__.py: -------------------------------------------------------------------------------- 1 | from figeno.figeno import figeno_make -------------------------------------------------------------------------------- /figeno/ase.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import vcfpy 4 | from figeno.utils import KnownException 5 | 6 | 7 | def read_SNPs_RNA(ase_file,gene=None,chr=None,start=None,end=None,exons=None,min_depth=0): 8 | """ 9 | Reads a tsv file generated by fast_ase or ASEReadCounter. 10 | Return a dataframe whose rows are SNPs, indicating the read counts of each allele in the RNAseq data. 11 | """ 12 | if gene is None and (chr is None or start is None or end is None): 13 | raise Exception("Must provide either a gene or coordinates (chr start end)") 14 | if gene is not None: 15 | chr = gene.chr 16 | start = gene.start 17 | end = gene.end 18 | try: 19 | df = pd.read_csv(ase_file,sep="\t",dtype={"contig":str}) 20 | except: 21 | raise KnownException("Failed to open the ase file: "+str(ase_file)) 22 | if not "contig" in df.columns: raise KnownException("The input file for the ase track needs a contig column.") 23 | if not "position" in df.columns: raise KnownException("The input file for the ase track needs a position column.") 24 | if not "refCount" in df.columns: raise KnownException("The input file for the ase track needs a refCount column.") 25 | if not "altCount" in df.columns: raise KnownException("The input file for the ase track needs a altCount column.") 26 | if not "variantID" in df.columns: raise KnownException("The input file for the ase track needs a variantID column.") 27 | df["contig"] = [x.lstrip("chr") for x in df["contig"]] 28 | df = df.loc[df["contig"]==chr] 29 | df = df.loc[(df["position"]>=start) & (df["position"]<=end)] 30 | df = df.loc[(df["refCount"]+df["altCount"]>=min_depth)] 31 | variables_selected=["contig","position","variantID","refAllele","altAllele","refCount","altCount"] 32 | if "refCount_DNA" in df.columns: variables_selected+=["refCount_DNA","altCount_DNA"] 33 | df = df[variables_selected] 34 | df["VAF"] = [min(df.loc[i,"refCount"],df.loc[i,"altCount"]) / (df.loc[i,"refCount"]+df.loc[i,"altCount"]) for i in df.index] 35 | if "refCount_DNA" in df.columns: 36 | df["VAF_DNA"] = [min(df.loc[i,"refCount_DNA"],df.loc[i,"altCount_DNA"]) / (df.loc[i,"refCount_DNA"]+df.loc[i,"altCount_DNA"]) for i in df.index] 37 | in_exons=[] 38 | if exons is not None: 39 | for i in df.index: 40 | exonic=False 41 | for exon in exons: 42 | if exon[0]<=df.loc[i,"position"] and df.loc[i,"position"] <=exon[1]: 43 | exonic=True 44 | in_exons.append(exonic) 45 | df["exonic"] = in_exons 46 | return df 47 | 48 | def read_SNPs_DNA(vcf_file,df_ase): 49 | variants_RNA=[] 50 | for i in df_ase.index: 51 | variants_RNA.append(df_ase.loc[i,"contig"]+":"+str(df_ase.loc[i,"position"])) 52 | positions=[] 53 | refCount_DNA=[] 54 | altCount_DNA=[] 55 | VAF_DNA=[] 56 | depth_DNA=[] 57 | reader = vcfpy.Reader.from_path(vcf_file) 58 | prefix="" 59 | for record in reader: 60 | if record.CHROM.startswith("chr"): prefix = "chr" 61 | break 62 | reader = vcfpy.Reader.from_path(vcf_file) 63 | for record in reader.fetch(prefix+df_ase.loc[df_ase.index[0],"contig"], np.min(df_ase["position"])-5, np.max(df_ase["position"])+5): 64 | if record.CHROM+":"+str(record.POS) in variants_RNA: 65 | DP = record.calls[0].data["DP"] 66 | AF = min(record.calls[0].data["AD"][0],record.calls[0].data["AD"][1]) / DP 67 | refCount_DNA.append(record.calls[0].data["AD"][0]) 68 | altCount_DNA.append(record.calls[0].data["AD"][1]) 69 | VAF_DNA.append(AF) 70 | depth_DNA.append(DP) 71 | positions.append(record.POS) 72 | df_ase = df_ase.copy(deep=True) 73 | if len(VAF_DNA)==df_ase.shape[0]: 74 | df_ase["VAF_DNA"] = VAF_DNA 75 | df_ase["depth_DNA"] = depth_DNA 76 | df_ase["refCount_DNA"] = refCount_DNA 77 | df_ase["altCount_DNA"] = altCount_DNA 78 | else: 79 | print("Some SNPs in the RNAseq were not found in the DNAseq") 80 | return df_ase -------------------------------------------------------------------------------- /figeno/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/cli/__init__.py -------------------------------------------------------------------------------- /figeno/cli/cli.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser 2 | 3 | from figeno.cli import gui, init,make 4 | 5 | __version__ = "1.7.0" 6 | 7 | def main(): 8 | parser = ArgumentParser("figeno",formatter_class=ArgumentDefaultsHelpFormatter) 9 | parser.add_argument('-v','--version',action='version',version='%(prog)s {}'.format(__version__)) 10 | 11 | subparsers = parser.add_subparsers(title="subcommands",description="valid commands",help="additional help",dest="command") 12 | subparsers.required = True 13 | 14 | for module in ["init","gui","make"]: 15 | mod = globals()[module] 16 | p = subparsers.add_parser(module,parents=[mod.argparser()]) 17 | p.set_defaults(func=mod.main) 18 | 19 | args = parser.parse_args() 20 | args.func(args) -------------------------------------------------------------------------------- /figeno/cli/gui.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 2 | 3 | def main(args): 4 | if args.server: 5 | from figeno.gui import gui_server 6 | gui_server.main(args) 7 | elif not args.webview: 8 | from figeno.gui import gui_browser 9 | gui_browser.main(args) 10 | else: 11 | from figeno.gui import gui_webview 12 | gui_webview.main(args) 13 | 14 | def argparser(): 15 | parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter,add_help=False) 16 | parser.add_argument('-w','--webview', dest="webview",action='store_true',help="If set, start the GUI in webview (using pywebview) instead of in the browser.") 17 | parser.add_argument('-s','--server', dest="server",action='store_true',help="If set, start the GUI in server mode, with filedialogs in the browser.") 18 | parser.add_argument("-p","--port",type=int,default=5000, help="Port, only used in browser mode.") 19 | parser.add_argument("--host",type=str,default="", help="Hostname, only used in server mode..") 20 | parser.add_argument('--debug', action='store_true',help="If set, will provide more debugging information.") 21 | parser.set_defaults(debug=False) 22 | parser.set_defaults(webview=False) 23 | parser.set_defaults(server=False) 24 | return parser -------------------------------------------------------------------------------- /figeno/cli/init.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 2 | import json 3 | import os 4 | 5 | def main(args): 6 | config={"general":get_general(args), 7 | "output": get_output(args), 8 | "regions":get_regions(args), 9 | "highlights":get_highlights(args), 10 | "tracks":get_tracks(args)} 11 | 12 | with open(args.output,"w") as fp: 13 | json.dump(config,fp,indent= "\t") 14 | 15 | if os.path.dirname(args.output)=="": 16 | config_path=os.path.join(os.getcwd(),args.output) 17 | else: 18 | config_path=args.output 19 | 20 | print("A config file was initialized at: "+config_path+". This config file still needs to be manually edited (in particular add the paths to the data files and to the output file), before running figeno make "+config_path) 21 | 22 | 23 | def get_general(args): 24 | if args.template is not None: 25 | if args.template=="wgs_circos": 26 | return {"layout": "circular","reference": "hg19"} 27 | return {"layout": "horizontal","reference": "hg19"} 28 | def get_output(args): 29 | return {"file": "","dpi": "400","width": "180"} 30 | 31 | def get_regions(args): 32 | regions=[] 33 | if args.template is not None and args.template=="wgs_circos": 34 | colors=["#98671F","#65661B","#969833","#CE151D","#FF1A25","#FF0BC8","#FFCBCC","#FF9931","#FFCC3A","#FCFF44","#C4FF40","#00FF3B", 35 | "#2F7F1E","#2800C6","#6A96FA","#98CAFC","#00FEFD","#C9FFFE","#9D00C6","#D232FA","#956DB5","#5D5D5D","#989898","#CBCBCB"] 36 | chromosomes = [str(x) for x in range(1,23)] + ["X","Y"] 37 | for i in range(len(colors)): 38 | regions.append({"chr":chromosomes[i],"start":"","end":"","color":colors[i]}) 39 | 40 | if args.regions is not None: 41 | reg_split = args.regions.split(",") 42 | for reg in reg_split: 43 | d_reg={"chr":"","start":"","end":"","color":"#f4a460"} 44 | if ":" in reg: 45 | chr,positions = reg.split(":") 46 | d_reg["chr"]=chr 47 | if "-" in positions: 48 | start,end = positions.split("-") 49 | d_reg["start"]=start 50 | d_reg["end"]=end 51 | else: 52 | d_reg["chr"]=reg 53 | regions.append(d_reg) 54 | 55 | if len(regions)==0: regions.append({"chr":"","start":"","end":"","color":"#f4a460"}) 56 | return regions 57 | 58 | def get_highlights(args): 59 | highlights=[] 60 | if args.highlights is not None: 61 | hl_split = args.highlights.split(",") 62 | for hl in hl_split: 63 | d_hl={"chr":"","start":"","end":"","color":"#eba434","opacity":0.3} 64 | if ":" in hl: 65 | chr,positions = hl.split(":") 66 | d_hl["chr"]=chr 67 | if "-" in positions: 68 | start,end = positions.split("-") 69 | d_hl["start"]=start 70 | d_hl["end"]=end 71 | else: 72 | d_hl["chr"]=hl 73 | if highlights: 74 | d_hl["color"] = "#eba434" 75 | d_hl["opacity"] = 0.3 76 | highlights.append(d_hl) 77 | return highlights 78 | 79 | def get_tracks(args): 80 | tracks=[] 81 | if args.tracks is not None: 82 | for track_type in args.tracks.split(","): 83 | tracks.append(get_track(track_type)) 84 | if args.template is not None: 85 | if args.template=="bigwig": 86 | tracks+=[get_track("bigwig"),get_track("genes"),get_track("chr_axis")] 87 | elif args.template=="hic": 88 | tracks+=[get_track("hic"),get_track("genes"),get_track("chr_axis")] 89 | elif args.template=="asm": 90 | alignments_track = get_track("alignments") 91 | alignments_track["group_by"]="haplotype" 92 | alignments_track["color_by"]="basemod" 93 | basemod_freq_track = get_track("basemod_freq") 94 | basemod_freq_track["bams"] = [{"file": "","base": "C","mod": "m","min_coverage": 6,"linewidth": 3,"opacity": 1, 95 | "fix_hardclip": False,"split_by_haplotype": True,"colors": ["#27ae60","#e67e22"]}] 96 | tracks+=[alignments_track,basemod_freq_track,get_track("genes"),get_track("chr_axis")] 97 | elif args.template=="wgs_chr": 98 | sv_track=get_track("sv") 99 | copynumber_track=get_track("copynumber") 100 | copynumber_track["margin_above"]=0 101 | chr_track=get_track("chr_axis") 102 | chr_track["margin_above"]=0 103 | tracks+=[sv_track,copynumber_track,chr_track] 104 | elif args.template=="wgs_circos": 105 | sv_track=get_track("sv") 106 | copynumber_track=get_track("copynumber") 107 | copynumber_track["margin_above"]=0 108 | copynumber_track["max_cn"]=3.9 109 | copynumber_track["grid_major"]=False 110 | copynumber_track["grid_minor"]=False 111 | chr_track=get_track("chr_axis") 112 | chr_track["margin_above"]=0 113 | chr_track["unit"]="Mb" 114 | tracks+=[sv_track,copynumber_track,chr_track] 115 | else: 116 | raise Exception("Unrecognized template: "+str(args.template)+". Please choose from: bigwig, hic, asm, wgs_chr, wgs_circos") 117 | return tracks 118 | 119 | 120 | def get_track(type): 121 | if type=="chr_axis": 122 | return { 123 | "type": "chr_axis", 124 | "height": 10, 125 | "margin_above": 1.5, 126 | "bounding_box": False, 127 | "fontscale": 1, 128 | "label": "", 129 | "label_rotate": False, 130 | "style": "default", 131 | "unit": "kb", 132 | "ticklabels_pos": "below", 133 | "ticks_interval": "auto" 134 | } 135 | if type=="genes": 136 | return { 137 | "type": "genes", 138 | "height": 10, 139 | "margin_above": 1.5, 140 | "bounding_box": False, 141 | "fontscale": 1, 142 | "label": "", 143 | "label_rotate": False, 144 | "style": "default", 145 | "collapsed": True, 146 | "only_protein_coding": True, 147 | "exon_color": "#2980b9", 148 | "genes": "auto" 149 | } 150 | if type=="bed": 151 | return { 152 | "type": "bed", 153 | "file": "", 154 | "color": "#444444", 155 | "label": "", 156 | "label_rotate": False, 157 | "height": 10, 158 | "margin_above": 1.5, 159 | "bounding_box": False, 160 | "fontscale": 1 161 | } 162 | if type=="bigwig": 163 | return { 164 | "type": "bigwig", 165 | "file": "", 166 | "color": "#2980b9", 167 | "n_bins": 500, 168 | "scale": "auto", 169 | "scale_pos": "corner", 170 | "upside_down": False, 171 | "height": 10, 172 | "margin_above": 1.5, 173 | "bounding_box": False, 174 | "fontscale": 1, 175 | "label": "", 176 | "label_rotate": False 177 | } 178 | if type=="coverage": 179 | return { 180 | "type": "coverage", 181 | "file": "", 182 | "color": "#888888", 183 | "n_bins": 500, 184 | "scale": "auto", 185 | "scale_pos": "corner", 186 | "upside_down": False, 187 | "height": 10, 188 | "margin_above": 1.5, 189 | "bounding_box": False, 190 | "fontscale": 1, 191 | "label": "", 192 | "label_rotate": False 193 | } 194 | if type=="alignments": 195 | return { 196 | "type": "alignments", 197 | "file": "", 198 | "height": 50, 199 | "margin_above": 1.5, 200 | "bounding_box": False, 201 | "fontscale": 1, 202 | "label": "", 203 | "label_rotate": False, 204 | "hgap_bp": 30, 205 | "vgap_frac": 0.3, 206 | "read_color": "#cccccc", 207 | "splitread_color": "#999999", 208 | "link_splitreads": False, 209 | "min_splitreads_breakpoints": 2, 210 | "group_by": "none", 211 | "show_unphased": True, 212 | "exchange_haplotypes": False, 213 | "show_haplotype_colors": True, 214 | "haplotype_colors": [ 215 | "#27ae60", 216 | "#e67e22", 217 | "#808080" 218 | ], 219 | "haplotype_labels": [ 220 | "HP1", 221 | "HP2", 222 | "Unphased" 223 | ], 224 | "color_by": "none", 225 | "color_unmodified": "#0f57e5", 226 | "basemods": [ 227 | [ 228 | "C", 229 | "m", 230 | "#f40202" 231 | ] 232 | ], 233 | "fix_hardclip_basemod": False 234 | } 235 | if type=="basemod_freq": 236 | return { 237 | "type": "basemod_freq", 238 | "style":"lines", 239 | "smooth":4, 240 | "gap_frac":0.1, 241 | "bams":[], 242 | "bedmethyls":[], 243 | "height": 20, 244 | "margin_above": 1.5, 245 | "bounding_box": True, 246 | "fontscale": 1, 247 | "label": "Methylation freq", 248 | "label_rotate": True 249 | } 250 | if type=="hic": 251 | return { 252 | "type": "hic", 253 | "file": "", 254 | "height": 50, 255 | "margin_above": 1.5, 256 | "bounding_box": True, 257 | "fontscale": 1, 258 | "label": "", 259 | "label_rotate": False, 260 | "color_map": "red", 261 | "pixel_border": False, 262 | "upside_down": False, 263 | "max_dist": 700, 264 | "extend": True, 265 | "interactions_across_regions": True, 266 | "double_interactions_across_regions": True 267 | } 268 | if type=="sv": 269 | return { 270 | "type": "sv", 271 | "file": "", 272 | "height": 15, 273 | "margin_above": 1.5, 274 | "bounding_box": True, 275 | "fontscale": 1, 276 | "label": "", 277 | "label_rotate": False, 278 | "lw": "0.5", 279 | "color_del": "#4a69bd", 280 | "color_dup": "#e55039", 281 | "color_t2t": "#8e44ad", 282 | "color_h2h": "#8e44ad", 283 | "color_trans": "#27ae60" 284 | } 285 | if type=="copynumber": 286 | return { 287 | "type": "copynumber", 288 | "freec_ratios": "", 289 | "freec_CNAs": "", 290 | "purple_cn": "", 291 | "height": 30, 292 | "margin_above": 1.5, 293 | "bounding_box": True, 294 | "fontscale": 1, 295 | "label": "", 296 | "label_rotate": False, 297 | "genes": "", 298 | "min_cn": "", 299 | "max_cn": "", 300 | "grid": True, 301 | "grid_major": True, 302 | "grid_minor": True, 303 | "grid_cn": True, 304 | "color_normal": "#000000", 305 | "color_loss": "#4a69bd", 306 | "color_gain": "#e55039", 307 | "color_cnloh": "#f6b93b" 308 | } 309 | raise Exception("Unrecognized track type: "+str(type)) 310 | 311 | 312 | 313 | 314 | 315 | 316 | def argparser(): 317 | parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter,add_help=False) 318 | parser.add_argument("--template",type=str,help="Template from the config file. Choose from: bigwig, hic, asm, wgs_chr, wgs_circos") 319 | parser.add_argument("--tracks",type=str, help="Comma-separated list of tracks, eg: chr_axis,genes,bigwig .") 320 | parser.add_argument("--regions",type=str, help="Comma-separated list of regions, eg: 3:128000000-129000000,7:142000000-142500000 .") 321 | parser.add_argument("--highlights",type=str, help="Comma-separated list of highlighted regions, eg: 3:128000000-129000000,7:142000000-142500000 .") 322 | parser.add_argument("-o","--output",type=str,default="config.json",help="Name of the config file that will be generated.") 323 | return parser -------------------------------------------------------------------------------- /figeno/cli/make.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 2 | import traceback 3 | from figeno import figeno_make 4 | from figeno.utils import KnownException 5 | 6 | def main(args): 7 | try: 8 | warnings=[] 9 | figeno_make(config_file=args.config,warnings=warnings) 10 | warning="\n".join(warnings) 11 | if warning!="": 12 | print("The figure was successfully generated, but with the following warnings:") 13 | print(warning) 14 | 15 | except KnownException as e: 16 | print("An error occured:") 17 | print(str(e)) 18 | except Exception as e: 19 | print("An error occured.") 20 | print(traceback.format_exc()) 21 | 22 | 23 | 24 | def argparser(): 25 | parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter,add_help=False) 26 | parser.add_argument("config",type=str, help="Path to the config file (json).") 27 | return parser -------------------------------------------------------------------------------- /figeno/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/data/__init__.py -------------------------------------------------------------------------------- /figeno/data/hg19_genes.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/data/hg19_genes.txt.gz -------------------------------------------------------------------------------- /figeno/data/hg38_genes.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/data/hg38_genes.txt.gz -------------------------------------------------------------------------------- /figeno/data/mm10_genes.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/data/mm10_genes.txt.gz -------------------------------------------------------------------------------- /figeno/gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/gui/__init__.py -------------------------------------------------------------------------------- /figeno/gui/gui_browser.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import webbrowser 3 | import os 4 | import sys 5 | import subprocess 6 | import logging 7 | from flask import Flask, jsonify, request 8 | import json 9 | import filedialpy 10 | from figeno import figeno_make 11 | from figeno.genes import find_genecoord_wrapper 12 | from figeno.utils import KnownException 13 | 14 | 15 | cli = sys.modules['flask.cli'] 16 | cli.show_server_banner = lambda *x: None 17 | 18 | if not os.path.isdir(os.path.dirname(__file__)+"/build"): 19 | print("Error: the React app for the gui has not been built, probably because you installed directly from the source code of the GitHub repository. "\ 20 | "The easiest way to install figeno is to install it from PyPI by running: 'pip install figeno'. "\ 21 | "If you want to build the gui from source, you must first go to the figeno/gui subdirectory, then run 'npm install' and 'npm run build' "\ 22 | "(this requires nodejs to be installed). You can then go back to the root directory and run 'pip install .'. ") 23 | exit() 24 | app = Flask(__name__, static_folder='./build', static_url_path='/') 25 | 26 | if sys.platform=="win32": 27 | last_dir=os.path.expanduser("~") 28 | else: 29 | last_dir=os.getcwd() 30 | config_dir=last_dir 31 | config_file="config.json" 32 | 33 | 34 | #@app.route('/browse/', defaults={'path': ''}) 35 | @app.route('/browse', methods=["POST"]) 36 | def browse(): 37 | global last_dir 38 | data=request.get_json() 39 | if data["dialog_type"]=="open_file": 40 | start_dir = last_dir 41 | if len(data["path"])>0 and os.path.exists(os.path.dirname(data["path"])): 42 | start_dir = os.path.dirname(data["path"]) 43 | t=filedialpy.openFile(initial_dir=start_dir, title="Select file") 44 | if len(t)>0: last_dir= os.path.dirname(t) 45 | return jsonify({"path":t}) 46 | elif data["dialog_type"]=="open_files": 47 | t=filedialpy.openFiles(initial_dir=last_dir,title="Select files") 48 | if len(t)>0 and len(t[0])>0: last_dir= os.path.dirname(t[0]) 49 | return jsonify({"files":t}) 50 | elif data["dialog_type"]=="save_file": 51 | start_dir = last_dir 52 | if len(data["path"])>0 and os.path.exists(os.path.dirname(data["path"])): 53 | start_dir=os.path.dirname(data["path"]) 54 | save_filename="figure.svg" 55 | if len(data["path"])>0: 56 | filename = os.path.basename(data["path"]) 57 | if len(filename)>0 and (filename.endswith(".svg") or filename.endswith(".pdf") or filename.endswith(".ps") or filename.endswith(".eps") or filename.endswith(".png")): 58 | save_filename=filename 59 | t=filedialpy.saveFile(initial_dir=start_dir,initial_file=save_filename,title="Select output path for the figure", filter="*.svg *.pdf *.png *.eps *.ps") 60 | if len(t)>0:last_dir= os.path.dirname(t) 61 | return jsonify({"path":t}) 62 | elif data["dialog_type"]=="load_config": 63 | filename=filedialpy.openFile(initial_dir=config_dir,filter="*.json",title="Select config file to load") 64 | return jsonify({"path":filename}) 65 | elif data["dialog_type"]=="save_config": 66 | filename=filedialpy.saveFile(initial_dir=config_dir,initial_file=config_file,title="Select path to save the config file",filter="*.json") 67 | return jsonify({"path":filename}) 68 | 69 | @app.route('/save_config', methods = ['POST']) 70 | def save_config(): 71 | global config_dir 72 | global config_file 73 | if request.is_json: 74 | data = request.get_json() 75 | filename=data["path"] 76 | if len(filename)>0: 77 | config_dir=os.path.dirname(filename) 78 | config_file=os.path.basename(filename) 79 | with open(filename,"w") as fp: 80 | json.dump(data["config"],fp,indent= "\t") 81 | return jsonify({"path":filename}) 82 | else: return {} 83 | 84 | @app.route('/load_config', methods=['POST']) 85 | def load_config(): 86 | global config_dir 87 | global config_file 88 | data=request.get_json() 89 | if len(data["path"])>0: 90 | config_dir=os.path.dirname(data["path"]) 91 | config_file=os.path.basename(data["path"]) 92 | with open(data["path"],"r") as fp: 93 | config = json.load(fp) 94 | return jsonify(config) 95 | else: 96 | return {} 97 | 98 | @app.route('/run', methods = ['POST']) 99 | def run(): 100 | if request.is_json: 101 | data = request.get_json() 102 | try: 103 | warnings=[] 104 | figeno_make(data,warnings=warnings) 105 | warning="\n\n".join(warnings) 106 | #if warning!="": print(warning) 107 | return jsonify({"status":"success","message":data["output"]["file"],"warning":warning}) 108 | except KnownException as e: 109 | print(str(e)) 110 | return jsonify({"status":"known_error","message":str(e)}) 111 | except Exception as e: 112 | print(traceback.format_exc()) 113 | return jsonify({"status":"unknown_error","message":traceback.format_exc()}) 114 | 115 | @app.route('/open_image', methods = ['POST']) 116 | def open_image(): 117 | if request.is_json: 118 | data = request.get_json() 119 | if sys.platform == "win32": 120 | os.startfile(data["file"]) 121 | else: 122 | opener = "open" if sys.platform == "darwin" else "xdg-open" 123 | subprocess.call([opener,data["file"]]) 124 | return {} 125 | 126 | @app.route('/open_dir', methods = ['POST']) 127 | def open_dir(): 128 | if request.is_json: 129 | data = request.get_json() 130 | if sys.platform == "win32": 131 | os.startfile(os.path.dirname(data["file"])) 132 | else: 133 | opener = "open" if sys.platform == "darwin" else "xdg-open" 134 | subprocess.call([opener,os.path.dirname(data["file"])]) 135 | return {} 136 | 137 | @app.route('/find_gene', methods = ['POST']) 138 | def find_gene(): 139 | if request.is_json: 140 | data = request.get_json() 141 | try: 142 | chr,start,end=find_genecoord_wrapper(data["gene_name"],data["reference"],data["genes_file"]) 143 | if chr=="": return jsonify({"status":"known_error","message":"Could not find gene: "+data["gene_name"]}) 144 | else: return jsonify({"status":"success","chr":chr,"start":start,"end":end}) 145 | except KnownException as e: 146 | print(str(e)) 147 | return jsonify({"status":"known_error","message":str(e)}) 148 | except Exception as e: 149 | print(traceback.format_exc()) 150 | return jsonify({"status":"unknown_error","message":traceback.format_exc()}) 151 | 152 | @app.route('/get_all_chromosomes', methods = ['POST']) 153 | def get_all_chromosomes(): 154 | if request.is_json: 155 | data = request.get_json() 156 | try: 157 | chromosomes=[] 158 | if "cytobands_file" in data and data["cytobands_file"]!="": 159 | if not os.path.isfile(data["cytobands_file"]): raise KnownException("The cytobands file could not be found: "+str(data["cytobands_file"]+". Will use the human chromosomes by default.")) 160 | with open(data["cytobands_file"],"r") as infile: 161 | for line in infile: 162 | if line.startswith("#"): continue 163 | linesplit = line.rstrip("\n").split("\t") 164 | chr = linesplit[0].lstrip("chr") 165 | if not chr in chromosomes: chromosomes.append(chr) 166 | return jsonify({"status":"success","chromosomes":chromosomes}) 167 | else: 168 | return jsonify({"status":"known_error","message":"No cytobands (or .fai) file was provided, so by default all human chromosomes were added. Please provide a cytobands (or .fai) file if you want to add the chromosomes present in your reference.","chromosomes":[]}) 169 | except KnownException as e: 170 | print(str(e)) 171 | return jsonify({"status":"known_error","message":str(e),"chromosomes":[]}) 172 | except Exception as e: 173 | print(traceback.format_exc()) 174 | return jsonify({"status":"unknown_error","message":traceback.format_exc(),"chromosomes":[]}) 175 | 176 | 177 | 178 | @app.route('/') 179 | def ind(): 180 | return app.send_static_file('index.html') 181 | 182 | def main(args=None): 183 | debug=True 184 | if args is not None: debug=args.debug 185 | if not debug: 186 | logging.getLogger('werkzeug').disabled = True 187 | port=5000 188 | if args is not None: port = args.port 189 | if sys.platform=="darwin": web_address="http://127.0.0.1:"+str(port)+"/" 190 | else: web_address="http://localhost:"+str(port)+"/" 191 | print("Starting the figeno GUI at "+web_address) 192 | webbrowser.open_new_tab(web_address) 193 | app.run(debug=debug,port=port) 194 | 195 | if __name__=="__main__": 196 | main() 197 | 198 | -------------------------------------------------------------------------------- /figeno/gui/gui_server.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import webbrowser 3 | import os 4 | import sys 5 | import logging 6 | import subprocess 7 | from flask import Flask, jsonify, request 8 | import json 9 | from figeno import figeno_make 10 | from figeno.genes import find_genecoord_wrapper 11 | from figeno.utils import KnownException 12 | 13 | app = Flask(__name__, static_folder='./build', static_url_path='/') 14 | 15 | if sys.platform=="win32": 16 | last_dir=os.path.expanduser("~") 17 | else: 18 | last_dir=os.getcwd() 19 | config_dir=last_dir 20 | config_file="config.json" 21 | 22 | 23 | #@app.route('/browse/', defaults={'path': ''}) 24 | @app.route('/browse', methods=["POST"]) 25 | def browse(): 26 | global last_dir 27 | data=request.get_json() 28 | start_dir = last_dir 29 | if data["dialog_type"] in ["save_config","load_config"]: start_dir=config_dir 30 | if len(data["path"])>0 and os.path.exists((data["path"])): 31 | if os.path.isdir(data["path"]): 32 | start_dir = data["path"] 33 | else: start_dir = os.path.dirname(data["path"]) 34 | last_dir=start_dir 35 | dirs=[] 36 | files=[] 37 | for f in os.listdir(start_dir): 38 | full_path = os.path.join(start_dir,f) 39 | if os.path.isfile(full_path): 40 | if (not data["dialog_type"] in ["load_config","save_config"]) or f.endswith(".json"): 41 | files.append(f) 42 | else: dirs.append(f) 43 | return jsonify({"current_dir":start_dir,"dirs":sorted(dirs),"files":sorted(files)}) 44 | 45 | @app.route('/save_config', methods = ['POST']) 46 | def save_config(): 47 | global config_dir 48 | global config_file 49 | if request.is_json: 50 | data = request.get_json() 51 | filename=data["path"] 52 | if len(filename)>0: 53 | config_dir=os.path.dirname(filename) 54 | config_file=os.path.basename(filename) 55 | with open(filename,"w") as fp: 56 | json.dump(data["config"],fp,indent= "\t") 57 | return jsonify({"path":filename}) 58 | else: return {} 59 | 60 | @app.route('/load_config', methods=['POST']) 61 | def load_config(): 62 | global config_dir 63 | global config_file 64 | data=request.get_json() 65 | if len(data["path"])>0: 66 | config_dir=os.path.dirname(data["path"]) 67 | config_file=os.path.basename(data["path"]) 68 | with open(data["path"],"r") as fp: 69 | config = json.load(fp) 70 | return jsonify(config) 71 | else: 72 | return {} 73 | 74 | @app.route('/run', methods = ['POST']) 75 | def run(): 76 | if request.is_json: 77 | data = request.get_json() 78 | try: 79 | warnings=[] 80 | figeno_make(data,warnings=warnings) 81 | warning="\n\n".join(warnings) 82 | #if warning!="": print(warning) 83 | return jsonify({"status":"success","message":data["output"]["file"],"warning":warning}) 84 | except KnownException as e: 85 | print(str(e)) 86 | return jsonify({"status":"known_error","message":str(e)}) 87 | except Exception as e: 88 | print(traceback.format_exc()) 89 | return jsonify({"status":"unknown_error","message":traceback.format_exc()}) 90 | 91 | @app.route('/open_image', methods = ['POST']) 92 | def open_image(): 93 | if request.is_json: 94 | data = request.get_json() 95 | if sys.platform == "win32": 96 | os.startfile(data["file"]) 97 | else: 98 | opener = "open" if sys.platform == "darwin" else "xdg-open" 99 | subprocess.call([opener,data["file"]]) 100 | #os.startfile(data["file"]) 101 | #webbrowser.open_new_tab("file:///"+data["file"]) 102 | return {} 103 | 104 | @app.route('/open_dir', methods = ['POST']) 105 | def open_dir(): 106 | if request.is_json: 107 | data = request.get_json() 108 | if sys.platform == "win32": 109 | os.startfile(os.path.dirname(data["file"])) 110 | else: 111 | opener = "open" if sys.platform == "darwin" else "xdg-open" 112 | subprocess.call([opener,os.path.dirname(data["file"])]) 113 | return {} 114 | 115 | @app.route('/find_gene', methods = ['POST']) 116 | def find_gene(): 117 | if request.is_json: 118 | data = request.get_json() 119 | try: 120 | chr,start,end=find_genecoord_wrapper(data["gene_name"],data["reference"],data["genes_file"]) 121 | if chr=="": return jsonify({"status":"known_error","message":"Could not find gene: "+data["gene_name"]}) 122 | else: return jsonify({"status":"success","chr":chr,"start":start,"end":end}) 123 | except KnownException as e: 124 | print(str(e)) 125 | return jsonify({"status":"known_error","message":str(e)}) 126 | except Exception as e: 127 | print(traceback.format_exc()) 128 | return jsonify({"status":"unknown_error","message":traceback.format_exc()}) 129 | 130 | @app.route('/get_all_chromosomes', methods = ['POST']) 131 | def get_all_chromosomes(): 132 | if request.is_json: 133 | data = request.get_json() 134 | try: 135 | chromosomes=[] 136 | if "cytobands_file" in data and data["cytobands_file"]!="": 137 | if not os.path.isfile(data["cytobands_file"]): raise KnownException("The cytobands file could not be found: "+str(data["cytobands_file"]+". Will use the human chromosomes by default.")) 138 | with open(data["cytobands_file"],"r") as infile: 139 | for line in infile: 140 | if line.startswith("#"): continue 141 | linesplit = line.rstrip("\n").split("\t") 142 | chr = linesplit[0].lstrip("chr") 143 | if not chr in chromosomes: chromosomes.append(chr) 144 | return jsonify({"status":"success","chromosomes":chromosomes}) 145 | else: 146 | return jsonify({"status":"known_error","message":"No cytobands (or .fai) file was provided, so by default all human chromosomes were added. Please provide a cytobands (or .fai) file if you want to add the chromosomes present in your reference.","chromosomes":[]}) 147 | except KnownException as e: 148 | print(str(e)) 149 | return jsonify({"status":"known_error","message":str(e),"chromosomes":[]}) 150 | except Exception as e: 151 | print(traceback.format_exc()) 152 | return jsonify({"status":"unknown_error","message":traceback.format_exc(),"chromosomes":[]}) 153 | 154 | 155 | @app.route('/') 156 | def ind(): 157 | return app.send_static_file('index.html') 158 | 159 | def main(args=None): 160 | debug=True 161 | if args is not None: debug=args.debug 162 | if not debug: 163 | logging.getLogger('werkzeug').disabled = True 164 | port=5000 165 | if args is not None: port = args.port 166 | #print("Starting server on http://localhost:"+str(port)) 167 | #webbrowser.open_new_tab('http://localhost:'+str(port)+'/') 168 | if args.host!="": 169 | print("Starting server on "+args.host+":"+str(port)) 170 | app.run(debug=debug,port=port,host=args.host) 171 | else: 172 | print("Starting server on http://localhost:"+str(port)) 173 | app.run(debug=debug,port=port) 174 | 175 | if __name__=="__main__": 176 | main() 177 | 178 | -------------------------------------------------------------------------------- /figeno/gui/gui_webview.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import logging 3 | import os 4 | import sys 5 | import subprocess 6 | from flask import Flask, jsonify, request 7 | import json 8 | 9 | from figeno import figeno_make 10 | from figeno.genes import find_genecoord_wrapper 11 | from figeno.utils import KnownException 12 | import webview 13 | if "http_proxy" in os.environ: del os.environ["http_proxy"] 14 | if "HTTP_PROXY" in os.environ: del os.environ["HTTP_PROXY"] 15 | if sys.platform=="win32": 16 | last_dir=os.path.expanduser("~") 17 | else: 18 | last_dir=os.getcwd() 19 | config_dir=last_dir 20 | config_file = "config.json" 21 | 22 | app = Flask(__name__, static_folder='./build', static_url_path='/') 23 | window = webview.create_window('figeno',app,width=webview.screens[0].width,height=webview.screens[0].height) 24 | 25 | @app.route('/browse', methods=["POST"]) 26 | def browse(): 27 | global last_dir 28 | data=request.get_json() 29 | if data["dialog_type"]=="open_file": 30 | start_dir = last_dir 31 | if len(data["path"])>0 and os.path.exists(os.path.dirname(data["path"])): 32 | start_dir = os.path.dirname(data["path"]) 33 | t = window.create_file_dialog(webview.OPEN_DIALOG, allow_multiple=False ,directory=start_dir) 34 | if t is None: t="" 35 | if not isinstance(t,str): t=t[0] 36 | if len(t)>0: last_dir= os.path.dirname(t) 37 | return jsonify({"path":t}) 38 | elif data["dialog_type"]=="open_files": 39 | t=window.create_file_dialog(webview.OPEN_DIALOG, allow_multiple=True ,directory=last_dir) 40 | if t is None: t=[] 41 | if len(t)>0 and len(t[0])>0: last_dir= os.path.dirname(t[0]) 42 | return jsonify({"files":t}) 43 | elif data["dialog_type"]=="save_file": 44 | start_dir = last_dir 45 | if len(data["path"])>0 and os.path.exists(os.path.dirname(data["path"])): 46 | start_dir=os.path.dirname(data["path"]) 47 | save_filename="figure.svg" 48 | if len(data["path"])>0: 49 | filename = os.path.basename(data["path"]) 50 | if len(filename)>0 and (filename.endswith(".svg") or filename.endswith(".pdf") or filename.endswith(".ps") or filename.endswith(".eps") or filename.endswith(".png")): 51 | save_filename=filename 52 | t=window.create_file_dialog( webview.SAVE_DIALOG, save_filename=save_filename,directory=start_dir) 53 | if t is None: t="" 54 | if not isinstance(t,str): t=t[0] 55 | if len(t)>0:last_dir= os.path.dirname(t) 56 | return jsonify({"path":t}) 57 | elif data["dialog_type"]=="load_config": 58 | filename=filename = window.create_file_dialog(webview.OPEN_DIALOG, allow_multiple=False ,directory=config_dir,file_types=('JSON files (*.json)', 'All files (*.*)')) 59 | if filename is None: filename="" 60 | if not isinstance(filename,str): filename=filename[0] 61 | return jsonify({"path":filename}) 62 | elif data["dialog_type"]=="save_config": 63 | filename= window.create_file_dialog( webview.SAVE_DIALOG, save_filename=config_file,directory=config_dir,file_types=('JSON files (*.json)', 'All files (*.*)')) 64 | if filename is None: filename="" 65 | if not isinstance(filename,str): filename=filename[0] 66 | return jsonify({"path":filename}) 67 | @app.route('/save_config', methods = ['POST']) 68 | def save_config(): 69 | global config_dir 70 | global config_file 71 | if request.is_json: 72 | data = request.get_json() 73 | filename=data["path"] 74 | if len(filename)>0: 75 | config_dir=os.path.dirname(filename) 76 | config_file=os.path.basename(filename) 77 | with open(filename,"w") as fp: 78 | json.dump(data["config"],fp,indent= "\t") 79 | return jsonify({"path":filename}) 80 | else: return {} 81 | 82 | @app.route('/load_config', methods=['POST']) 83 | def load_config(): 84 | global config_dir 85 | global config_file 86 | data=request.get_json() 87 | if len(data["path"])>0: 88 | config_dir=os.path.dirname(data["path"]) 89 | config_file=os.path.basename(data["path"]) 90 | with open(data["path"],"r") as fp: 91 | config = json.load(fp) 92 | return jsonify(config) 93 | else: 94 | return {} 95 | 96 | @app.route('/run', methods = ['POST']) 97 | def run(): 98 | if request.is_json: 99 | data = request.get_json() 100 | try: 101 | warnings=[] 102 | figeno_make(data,warnings=warnings) 103 | warning="\n\n".join(warnings) 104 | #if warning!="": print(warning) 105 | return jsonify({"status":"success","message":data["output"]["file"],"warning":warning}) 106 | except KnownException as e: 107 | print(str(e)) 108 | return jsonify({"status":"known_error","message":str(e)}) 109 | except Exception as e: 110 | print(traceback.format_exc()) 111 | return jsonify({"status":"unknown_error","message":traceback.format_exc()}) 112 | 113 | @app.route('/open_image', methods = ['POST']) 114 | def open_image(): 115 | if request.is_json: 116 | data = request.get_json() 117 | if sys.platform == "win32": 118 | os.startfile(data["file"]) 119 | else: 120 | opener = "open" if sys.platform == "darwin" else "xdg-open" 121 | subprocess.call([opener,data["file"]]) 122 | return {} 123 | 124 | @app.route('/open_dir', methods = ['POST']) 125 | def open_dir(): 126 | if request.is_json: 127 | data = request.get_json() 128 | if sys.platform == "win32": 129 | os.startfile(os.path.dirname(data["file"])) 130 | else: 131 | opener = "open" if sys.platform == "darwin" else "xdg-open" 132 | subprocess.call([opener,os.path.dirname(data["file"])]) 133 | return {} 134 | 135 | @app.route('/find_gene', methods = ['POST']) 136 | def find_gene(): 137 | if request.is_json: 138 | data = request.get_json() 139 | try: 140 | chr,start,end=find_genecoord_wrapper(data["gene_name"],data["reference"],data["genes_file"]) 141 | if chr=="": return jsonify({"status":"known_error","message":"Could not find gene: "+data["gene_name"]}) 142 | else: return jsonify({"status":"success","chr":chr,"start":start,"end":end}) 143 | except KnownException as e: 144 | print(str(e)) 145 | return jsonify({"status":"known_error","message":str(e)}) 146 | except Exception as e: 147 | print(traceback.format_exc()) 148 | return jsonify({"status":"unknown_error","message":traceback.format_exc()}) 149 | 150 | @app.route('/get_all_chromosomes', methods = ['POST']) 151 | def get_all_chromosomes(): 152 | if request.is_json: 153 | data = request.get_json() 154 | try: 155 | chromosomes=[] 156 | if "cytobands_file" in data and data["cytobands_file"]!="": 157 | if not os.path.isfile(data["cytobands_file"]): raise KnownException("The cytobands file could not be found: "+str(data["cytobands_file"]+". Will use the human chromosomes by default.")) 158 | with open(data["cytobands_file"],"r") as infile: 159 | for line in infile: 160 | if line.startswith("#"): continue 161 | linesplit = line.rstrip("\n").split("\t") 162 | chr = linesplit[0].lstrip("chr") 163 | if not chr in chromosomes: chromosomes.append(chr) 164 | return jsonify({"status":"success","chromosomes":chromosomes}) 165 | else: 166 | return jsonify({"status":"known_error","message":"No cytobands (or .fai) file was provided, so by default all human chromosomes were added. Please provide a cytobands (or .fai) file if you want to add the chromosomes present in your reference.","chromosomes":[]}) 167 | except KnownException as e: 168 | print(str(e)) 169 | return jsonify({"status":"known_error","message":str(e),"chromosomes":[]}) 170 | except Exception as e: 171 | print(traceback.format_exc()) 172 | return jsonify({"status":"unknown_error","message":traceback.format_exc(),"chromosomes":[]}) 173 | 174 | 175 | 176 | @app.route('/') 177 | def ind(): 178 | return app.send_static_file('index.html') 179 | 180 | def main(args=None): 181 | 182 | debug=False 183 | if args is not None: debug=args.debug 184 | if not debug: 185 | logging.getLogger('werkzeug').disabled = True 186 | webview.start(debug=args.debug) -------------------------------------------------------------------------------- /figeno/gui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figeno", 3 | "version": "1.7.0", 4 | "private": true, 5 | "homepage": "./", 6 | "dependencies": { 7 | "@dnd-kit/core": "^6.1.0", 8 | "@dnd-kit/sortable": "^8.0.0", 9 | "@dnd-kit/utilities": "^3.2.2", 10 | "@testing-library/jest-dom": "^5.17.0", 11 | "@testing-library/react": "^13.4.0", 12 | "@testing-library/user-event": "^13.5.0", 13 | "framer-motion": "^11.0.3", 14 | "react": "^18.2.0", 15 | "react-colorful": "^5.6.1", 16 | "react-dom": "^18.2.0", 17 | "react-loading-icons": "^1.1.0", 18 | "react-native": "^0.73.4", 19 | "react-scripts": "5.0.1", 20 | "react-select": "^5.8.0", 21 | "uuid": "^9.0.1", 22 | "web-vitals": "^2.1.4" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "proxy": "http://localhost:5000" 49 | } 50 | -------------------------------------------------------------------------------- /figeno/gui/public/figeno_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/gui/public/figeno_icon.png -------------------------------------------------------------------------------- /figeno/gui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 26 | Figeno 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /figeno/gui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | 6 | ], 7 | "start_url": ".", 8 | "display": "standalone", 9 | "theme_color": "#000000", 10 | "background_color": "#ffffff" 11 | } 12 | -------------------------------------------------------------------------------- /figeno/gui/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /figeno/gui/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /figeno/gui/src/ColorButton.jsx: -------------------------------------------------------------------------------- 1 | import {React, Fragment,useState,useRef, useCallback} from 'react'; 2 | import { useSortable } from '@dnd-kit/sortable'; 3 | import {CSS} from '@dnd-kit/utilities'; 4 | import { HexColorPicker, HexColorInput } from "react-colorful"; 5 | import useClickOutside from "./useClickOutside"; 6 | import "./style.css"; 7 | 8 | export function ColorButton({color,setColor,openColorPanel,id}){ 9 | // selectColor opens the ColorPanel 10 | 11 | // 12 | return ( 13 | <> 14 | 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | function ColorButtonInPanel({color,setColor}){ 58 | return ( 59 | <> 60 | 56 | 61 | 62 | 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /figeno/gui/src/FileDialog.jsx: -------------------------------------------------------------------------------- 1 | import {React, Fragment} from 'react'; 2 | import "./style.css"; 3 | 4 | 5 | 6 | export function FileDialog({fileDialogData, setFileDialogData,close}){ 7 | 8 | function update_data(new_dir){ 9 | fetch('/browse',{headers: {'Content-Type': 'application/json'}, body: JSON.stringify({path:new_dir,dialog_type:fileDialogData.dialog_type}), 10 | method:"POST"}).then(res => res.json()).then(data => { 11 | setFileDialogData({...fileDialogData,current_dir:data.current_dir,dirs:data.dirs,files:data.files}) 12 | }); 13 | } 14 | function handle_click(dirname){ 15 | let new_dir=fileDialogData.current_dir+"/"+dirname; 16 | if (fileDialogData.current_dir.slice(-1)=="/") new_dir=fileDialogData.current_dir+dirname; 17 | update_data(new_dir); 18 | } 19 | function handle_click_file(filename){ 20 | let path=fileDialogData.current_dir+"/"+filename; 21 | if (fileDialogData.current_dir.slice(-1)=="/") path=fileDialogData.current_dir+filename; 22 | if (fileDialogData.dialog_type=="save_file" || fileDialogData.dialog_type=="save_config"){ 23 | setFileDialogData({...fileDialogData,file:filename}) 24 | } 25 | else{ 26 | fileDialogData.update(path); 27 | close(); 28 | } 29 | 30 | } 31 | 32 | function handle_up(){ 33 | let s=fileDialogData.current_dir; 34 | if (s.slice(-1)=="/") s=s.substring(0,s.length-1); 35 | s=s.substring(0,s.lastIndexOf("/")); 36 | update_data(s); 37 | } 38 | function handle_refresh(){ 39 | update_data(fileDialogData.current_dir); 40 | } 41 | 42 | function handle_save(){ 43 | let path=fileDialogData.current_dir+"/"+fileDialogData.file; 44 | if (fileDialogData.current_dir.slice(-1)=="/") path=fileDialogData.current_dir+fileDialogData.file; 45 | fileDialogData.update(path); 46 | close() 47 | } 48 | 49 | return( 50 |
51 |
52 | 55 | setFileDialogData({...fileDialogData,current_dir:e.target.value})} /> 56 | 59 |
60 | 61 |
62 | {fileDialogData.dirs.map((directory)=>{return handle_click(directory)}/>})} 63 | {fileDialogData.files.map((file)=>{return handle_click_file(file)}/>})} 64 |
65 | {(fileDialogData.dialog_type=="save_config" || fileDialogData.dialog_type=="save_file")?( 66 |
67 | setFileDialogData({...fileDialogData,file:e.target.value})} /> 68 | 69 |
70 | ):""} 71 | 72 | 73 | ) 74 | } 75 | 76 | function DirectoryButton({dirname,onClick}){ 77 | return( 78 | 82 | ) 83 | } 84 | 85 | function FileButton({filename,onClick}){ 86 | return( 87 | 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /figeno/gui/src/GeneSelector.jsx: -------------------------------------------------------------------------------- 1 | import {React, Fragment,useState, useRef} from 'react'; 2 | import useClickOutside from './useClickOutside'; 3 | import "./style.css"; 4 | 5 | export function GeneSelector({ updateRegion,setGeneSelectorActive, generalParams}){ 6 | const popover = useRef(); 7 | useClickOutside(popover, ()=>setGeneSelectorActive(false)); 8 | const [errorMessage,setErrorMessage]=useState(""); 9 | const [status,setStatus]=useState(""); 10 | const [genename,setGenename]=useState(""); 11 | function handleButton(){ 12 | setStatus("") 13 | setErrorMessage(""); 14 | setGeneSelectorActive(false); 15 | } 16 | 17 | function handleClickOK(){ 18 | let genes_file=""; 19 | if (generalParams.hasOwnProperty("genes_file")) genes_file=generalParams.genes_file; 20 | fetch("/find_gene",{headers: {'Content-Type': 'application/json'}, body: JSON.stringify({gene_name:genename,reference:generalParams.reference,genes_file:genes_file}), 21 | method:"POST"}).then(res => res.json()).then((data)=>{ 22 | if (data.status=="success"){ 23 | updateRegion(data.chr,data.start,data.end); 24 | setGeneSelectorActive(false); 25 | } 26 | else{ 27 | setStatus(data.status) 28 | setErrorMessage(data.message); 29 | } 30 | }); 31 | } 32 | 33 | return( 34 |
35 |

Center the region around a gene.

36 |
37 | 38 | {if (e.key=="Enter") handleClickOK();}} onChange={(e) => setGenename(e.target.value)}/> 39 | 40 |
41 | 42 | 43 | 44 | {(status=="known_error")? ( 45 | <> 46 |
An error has occured:
47 |
48 |

{errorMessage}

49 |
50 | 51 | ):""} 52 | {(status=="unknown_error")? ( 53 | <> 54 |
An error has occured:
55 |
56 |

{errorMessage}

57 |
58 | 59 | ):""} 60 | 61 | 62 | ) 63 | } -------------------------------------------------------------------------------- /figeno/gui/src/GeneralContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./style.css"; 3 | import { PathEntry } from './Track'; 4 | 5 | 6 | export function GeneralContainer({generalParams,setGeneralParams}) { 7 | 8 | function set_value(attribute,value){ 9 | const newVal={...generalParams}; 10 | newVal[attribute]= value; 11 | setGeneralParams(newVal); 12 | } 13 | 14 | function handle_reference_change(value){ 15 | if (value!=generalParams.reference){ 16 | const newVal={...generalParams,reference:value}; 17 | if (value=="custom"){ 18 | newVal["genes_file"] = ""; 19 | newVal["cytobands_file"] = ""; 20 | 21 | } 22 | else if (generalParams.reference=="custom"){ 23 | delete newVal.genes_file; 24 | delete newVal.cytobands_file; 25 | } 26 | setGeneralParams(newVal); 27 | } 28 | } 29 | 30 | 31 | 32 | 33 | 34 | const header=
35 |

General

36 |
; 37 | 38 | return ( 39 |
40 | {header} 41 |
42 |
43 |
44 | 45 | 51 |
52 |
53 | 54 | 60 |
61 | 62 | {generalParams.reference==="custom"?(<> 63 | set_value("genes_file",val)}/> 64 | set_value("cytobands_file",val)}/> 65 | ):""} 66 |
67 |
68 |
69 | ) 70 | } -------------------------------------------------------------------------------- /figeno/gui/src/Highlight.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ColorButton } from './ColorButton'; 3 | import "./style.css"; 4 | import { DndItem } from './DndItem'; 5 | 6 | 7 | 8 | 9 | 10 | export function Highlight({region, set_value, className, copy_region,delete_region, openColorPanel}) { 11 | const chrClass=(region.chr!=="")?"":"unsetPath"; 12 | return ( 13 | copy_region(region.id)} delete_item={()=>delete_region(region.id)} className={className}> 14 |
15 | 16 |
17 | 18 | set_value("chr",e.target.value)} className={chrClass} /> 19 |
20 | 21 |
22 | 23 | set_value("start",e.target.value)}> 24 |
25 | 26 |
27 | 28 | set_value("end",e.target.value)}> 29 |
30 | set_value("color",c)} openColorPanel={openColorPanel}/> 31 | 32 |
33 | 34 | set_value("opacity",e.target.value)}> 35 |
36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /figeno/gui/src/HighlightsContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {v4 as uuid4} from 'uuid'; 3 | import "./style.css"; 4 | import {Highlight} from './Highlight' 5 | import { DndContainer } from './DndContainer'; 6 | 7 | 8 | export function HighlightsContainer({regionsList,setRegionsList,openColorPanel}) { 9 | 10 | 11 | function set_value(id,attribute,value){ 12 | setRegionsList((regions)=>{ 13 | return regions.map((region)=>{ 14 | if (region.id==id) { 15 | region[attribute]=value; 16 | return region;} 17 | else {return region;} 18 | }) 19 | }); 20 | } 21 | 22 | 23 | 24 | function add_region(){ 25 | setRegionsList([...regionsList,{id:uuid4(),chr:"",start:"",end:"",color:"#eba434",opacity:"0.3"}]); 26 | } 27 | function delete_region(id){ 28 | setRegionsList((regions_list) => {return regions_list.filter(region => region.id!==id)}) 29 | } 30 | function copy_region(id){ 31 | const index = regionsList.findIndex((t)=>t.id==id); 32 | const newTrack = {...regionsList[index],id:uuid4()}; 33 | const newList = [...regionsList] 34 | newList.splice(index,0,newTrack); 35 | setRegionsList(newList); 36 | } 37 | function getRegionById (regions, id) { 38 | return regions.find((region) => region.id === id); 39 | } 40 | 41 | 42 | const header=
43 |

Highlights

44 | 47 |
; 48 | 49 | function show_active_item(active_id){ 50 | return 51 | } 52 | 53 | return ( 54 | <> 55 | 56 | {regionsList.map((region)=>{ 57 | return set_value(region.id,attribute,value)} className={"track"} copy_region={copy_region} delete_region={delete_region} openColorPanel={openColorPanel} /> 58 | })} 59 | 60 | 61 | ) 62 | } -------------------------------------------------------------------------------- /figeno/gui/src/LoadingScreen.jsx: -------------------------------------------------------------------------------- 1 | import {React, Fragment,useState,useRef, useCallback} from 'react'; 2 | import {v4 as uuid4} from 'uuid'; 3 | import useClickOutside from "./useClickOutside"; 4 | import LoadingIcons from 'react-loading-icons' 5 | import "./style.css"; 6 | 7 | 8 | 9 | export function LoadingScreen({message, setMessage,status,setStatus, warning, setWarning, setLoadingscreenActive}){ 10 | function handleButton(){ 11 | setStatus(""); 12 | setMessage(""); 13 | setWarning(""); 14 | setLoadingscreenActive(false); 15 | } 16 | 17 | function openImage(){ 18 | fetch("/open_image",{headers: {'Content-Type': 'application/json'}, body: JSON.stringify({file:message}), 19 | method:"POST"}) 20 | } 21 | function openDir(){ 22 | fetch("/open_dir",{headers: {'Content-Type': 'application/json'}, body: JSON.stringify({file:message}), 23 | method:"POST"}) 24 | } 25 | 26 | const popover = useRef(); 27 | useClickOutside(popover, ()=>{if (status!="") handleButton();}); 28 | 29 | return( 30 |
31 | {(status==="")?( 32 | <> 33 |

Generating the figure...

34 | 35 | 36 | ):""} 37 | 38 | {(status==="success")?( 39 | <> 40 |

The figure was successfully generated. ✅

41 |

42 | The figure was saved to {message}. 43 |

44 | 45 | {(warning!="")?( 46 | <> 47 | 48 |
49 |

Warning ⚠️

50 |

{warning}

51 |
52 | 53 | ):""} 54 | 55 | 56 |
57 | 58 | 59 | 60 |
61 | 62 | ):""} 63 | 64 | {(status==="known_error")?( 65 | <> 66 |

An error has occured. ❌

67 |

{message}

68 | 69 | 70 | ):""} 71 | 72 | {(status==="unknown_error")?( 73 | <> 74 |

An unknown error has occured. ❌

75 |

76 | The traceback below might help you solve the error. Otherwise, you can ask for help by creating an issue on the figeno GitHub repository. Please provide a copy of the error message below, and of the config file that you used. 77 |

78 |
79 |

{message}

80 |
81 | 82 | 83 | ):""} 84 | 85 | 86 | ) 87 | } 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /figeno/gui/src/OutputContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./style.css"; 3 | 4 | 5 | export function OutputContainer({outputParams,setOutputParams, setFileDialogData, setFileDialogActive}) { 6 | 7 | const fileClass=(outputParams.file.endsWith(".svg") || outputParams.file.endsWith(".pdf") || outputParams.file.endsWith(".eps") 8 | || outputParams.file.endsWith(".ps") || outputParams.file.endsWith(".png"))? "":"unsetPath"; 9 | 10 | function set_value(attribute,value){ 11 | const newVal={...outputParams}; 12 | newVal[attribute]= value; 13 | setOutputParams(newVal); 14 | } 15 | 16 | 17 | const header=
18 |

Output

19 |
; 20 | 21 | return ( 22 |
23 | {header} 24 |
25 |
26 | set_value("file",val)} setFileDialogData={setFileDialogData} setFileDialogActive={setFileDialogActive} className={fileClass}/> 27 | 28 |
29 | 30 | set_value("dpi",e.target.value)} > 31 |
32 | 33 |
34 | 35 | set_value("width",e.target.value)} > 36 |
37 | 38 |
39 |
40 |
41 | ) 42 | } 43 | 44 | function select_file_save(set_value,value, setFileDialogData, setFileDialogActive){ 45 | fetch('/browse',{headers: {'Content-Type': 'application/json'}, body: JSON.stringify({path:value,dialog_type:"save_file"}), 46 | method:"POST"}).then(res => res.json()).then(data => { 47 | if (data.hasOwnProperty("current_dir")){ 48 | setFileDialogData({current_dir:data.current_dir,dirs:data.dirs,files:data.files,file:"figure.svg",dialog_type:"save_file",update:function(f){set_value(f)}}) 49 | setFileDialogActive(true); 50 | } 51 | else{ 52 | if (data.path.length>0) set_value(data.path); 53 | } 54 | }); 55 | } 56 | export function PathEntrySave({id,label,value,set_value,className="" ,setFileDialogData, setFileDialogActive}){ 57 | return( 58 |
59 | 60 | set_value(e.target.value) } style={{marginLeft:"2mm", marginRight:"1mm"}} className={className}> 61 | 66 |
67 | ) 68 | } -------------------------------------------------------------------------------- /figeno/gui/src/Region.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ColorButton } from './ColorButton'; 3 | import "./style.css"; 4 | import { DndItem } from './DndItem'; 5 | 6 | 7 | 8 | 9 | 10 | export function Region({region, set_value, className, copy_region, delete_region, openColorPanel,openGeneSelector, show_region_color}) { 11 | const chrClass=(region.chr!=="")?"":"unsetPath"; 12 | 13 | function updateRegionCoords(chr,start,end){ 14 | set_value("chr",chr); 15 | set_value("start",start); 16 | set_value("end",end); 17 | } 18 | 19 | return ( 20 | copy_region(region.id)} delete_item={()=>delete_region(region.id)} className={className}> 21 |
22 | 23 |
24 | 25 | set_value("chr",e.target.value)} className={chrClass} /> 26 |
27 | 28 |
29 | 30 | set_value("start",e.target.value)}> 31 |
32 | 33 |
34 | 35 | set_value("end",e.target.value)}> 36 |
37 | {show_region_color&&( 38 | set_value("color",c)} openColorPanel={openColorPanel}/> 39 | )} 40 | 41 | 42 |
43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /figeno/gui/src/RegionsContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {v4 as uuid4} from 'uuid'; 3 | import "./style.css"; 4 | import {Region} from './Region' 5 | import { DndContainer } from './DndContainer'; 6 | 7 | 8 | export function RegionsContainer({regionsList,setRegionsList,openColorPanel, openGeneSelector,add_all_chromosomes,show_regions_color}) { 9 | 10 | function set_value(id,attribute,value){ 11 | setRegionsList((regions)=>{ 12 | return regions.map((region)=>{ 13 | if (region.id==id) { 14 | region[attribute]=value; 15 | return region;} 16 | else {return region;} 17 | }) 18 | }); 19 | } 20 | 21 | 22 | 23 | function add_region(){ 24 | setRegionsList([...regionsList,{id:uuid4(),chr:"",start:"",end:"",color:"#f4a460"}]); 25 | } 26 | function delete_region(id){ 27 | setRegionsList((regions_list) => {return regions_list.filter(region => region.id!==id)}) 28 | } 29 | function copy_region(id){ 30 | const index = regionsList.findIndex((t)=>t.id==id); 31 | const newTrack = {...regionsList[index],id:uuid4()}; 32 | const newList = [...regionsList] 33 | newList.splice(index,0,newTrack); 34 | setRegionsList(newList); 35 | } 36 | function getRegionById (regions, id) { 37 | return regions.find((region) => region.id === id); 38 | } 39 | 40 | const header=
41 |

Regions

42 | 46 | 50 |
; 51 | 52 | function show_active_item(active_id){ 53 | return 54 | } 55 | 56 | return ( 57 | <> 58 | 59 | {regionsList.map((region)=>{ 60 | return set_value(region.id,attribute,value)} className={"track"} copy_region={copy_region} delete_region={delete_region} openColorPanel={openColorPanel} openGeneSelector={openGeneSelector} show_region_color={show_regions_color}/> 61 | })} 62 | 63 | 64 | ) 65 | } -------------------------------------------------------------------------------- /figeno/gui/src/TemplatePanel.jsx: -------------------------------------------------------------------------------- 1 | import {React, useRef} from 'react'; 2 | import {v4 as uuid4} from 'uuid'; 3 | import useClickOutside from "./useClickOutside"; 4 | import "./style.css"; 5 | import { defaultTrackValues } from './TracksContainer'; 6 | 7 | import bigwig_figure from './images/template_bigwig.png'; 8 | import hic_figure from './images/template_hic.png'; 9 | import asm_figure from './images/template_asm.png'; 10 | import wgs_figure from './images/template_wgs_chr.png'; 11 | import circos_figure from './images/template_wgs_circos.png'; 12 | 13 | 14 | export function TemplatePanel({setTracksList,setRegionsList,setHighlightsList,setGeneralParams,close}){ 15 | const templates = ["bigwig", "hic", "asm", "wgs_chr", "wgs_circos"]; 16 | const popover = useRef(); 17 | useClickOutside(popover, close); 18 | 19 | function select_bigwig(){ 20 | setGeneralParams((params)=> {return {...params,layout:"horizontal"}}); 21 | setRegionsList([{id:uuid4(),chr:"",start:"",end:"",color:"#f4a460"}]); 22 | let bigwigTrack= {id:uuid4(),type:"bigwig",...defaultTrackValues.bigwig}; 23 | let genesTrack={id:uuid4(),type:"genes",...defaultTrackValues.genes}; 24 | let chrTrack={id:uuid4(),type:"chr_axis",...defaultTrackValues["chr_axis"]}; 25 | setHighlightsList([]); 26 | setTracksList([bigwigTrack,genesTrack,chrTrack]) 27 | close(); 28 | } 29 | 30 | function select_hic(){ 31 | setGeneralParams((params)=> {return {...params,layout:"horizontal"}}); 32 | setRegionsList([{id:uuid4(),chr:"",start:"",end:"",color:"#f4a460"}]); 33 | let hicTrack= {id:uuid4(),type:"hic",...defaultTrackValues.hic}; 34 | let genesTrack={id:uuid4(),type:"genes",...defaultTrackValues.genes}; 35 | let chrTrack={id:uuid4(),type:"chr_axis",...defaultTrackValues["chr_axis"]}; 36 | setHighlightsList([]); 37 | setTracksList([hicTrack,genesTrack,chrTrack]) 38 | close(); 39 | } 40 | 41 | function select_asm(){ 42 | setGeneralParams((params)=> {return {...params,layout:"horizontal"}}); 43 | setRegionsList([{id:uuid4(),chr:"",start:"",end:"",color:"#f4a460"}]); 44 | let alignmentsTrack= {id:uuid4(),type:"alignments",...defaultTrackValues.alignments,group_by:"haplotype",color_by:"basemod"}; 45 | let basemodfreqTrack= {id:uuid4(),type:"basemod_freq",...defaultTrackValues["basemod_freq"], 46 | bams:[{id:uuid4(),"file": "","base": "C","mod": "m","min_coverage": 6,"linewidth": 3,"opacity": 1, 47 | "fix_hardclip": false,"split_by_haplotype": true,"colors": ["#27ae60","#e67e22"],"labels":["",""]}]}; 48 | let genesTrack={id:uuid4(),type:"genes",...defaultTrackValues.genes}; 49 | let chrTrack={id:uuid4(),type:"chr_axis",...defaultTrackValues["chr_axis"]}; 50 | setHighlightsList([]); 51 | setTracksList([alignmentsTrack,basemodfreqTrack,genesTrack,chrTrack]) 52 | close(); 53 | } 54 | 55 | function select_wgs_chr(){ 56 | setGeneralParams((params)=> {return {...params,layout:"horizontal"}}); 57 | setRegionsList([{id:uuid4(),chr:"",start:"",end:"",color:"#f4a460"}]); 58 | let svTrack= {id:uuid4(),type:"sv",...defaultTrackValues.sv}; 59 | let copynumberTrack= {id:uuid4(),type:"copynumber",...defaultTrackValues.copynumber,margin_above:0}; 60 | let chrTrack={id:uuid4(),type:"chr_axis",...defaultTrackValues["chr_axis"],margin_above:0,unit:"Mb"}; 61 | setHighlightsList([]); 62 | setTracksList([svTrack,copynumberTrack,chrTrack]) 63 | close(); 64 | } 65 | 66 | function select_wgs_circos(){ 67 | setGeneralParams((params)=> {return {...params,layout:"circular"}}); 68 | 69 | const regions=[]; 70 | const colors=["#98671F","#65661B","#969833","#CE151D","#FF1A25","#FF0BC8","#FFCBCC","#FF9931","#FFCC3A","#FCFF44","#C4FF40","#00FF3B", 71 | "#2F7F1E","#2800C6","#6A96FA","#98CAFC","#00FEFD","#C9FFFE","#9D00C6","#D232FA","#956DB5","#5D5D5D","#989898","#CBCBCB"]; 72 | for (let i = 1; i < 23; i++) { 73 | regions.push({id:uuid4(),"chr":i.toString(),"start":"","end":"",color:colors[i-1]}); 74 | } 75 | regions.push({id:uuid4(),"chr":"X","start":"","end":"",color:colors[22]}); 76 | regions.push({id:uuid4(),"chr":"Y","start":"","end":"",color:colors[23]}); 77 | setRegionsList(regions); 78 | 79 | let svTrack= {id:uuid4(),type:"sv",...defaultTrackValues.sv}; 80 | let copynumberTrack= {id:uuid4(),type:"copynumber",...defaultTrackValues.copynumber,margin_above:0,max_cn:3.9,grid_major:false,grid_minor:false}; 81 | let chrTrack={id:uuid4(),type:"chr_axis",...defaultTrackValues["chr_axis"],margin_above:0}; 82 | setHighlightsList([]); 83 | setTracksList([svTrack,copynumberTrack,chrTrack]) 84 | close(); 85 | } 86 | 87 | return( 88 |
89 |

Select template

90 |
91 | hic 92 | hic 93 | hic 94 | hic 95 | hic 96 |
97 | 98 | 99 | ) 100 | } 101 | 102 | function TemplateButton({children,name,onClick}){ 103 | return( 104 |
105 | {name} 106 | {children} 107 | 108 |
109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /figeno/gui/src/TrackTypePanel.jsx: -------------------------------------------------------------------------------- 1 | import {React, Fragment,useState,useRef, useCallback} from 'react'; 2 | import useClickOutside from "./useClickOutside"; 3 | import "./style.css"; 4 | import { TrackIcon } from './Track'; 5 | 6 | 7 | export function TrackTypePanel({setTrackType,close}){ 8 | const track_types = ["chr_axis", "genes", "bed", "bigwig", "coverage", "alignments", "basemod_freq", "hic", "sv", "copynumber", "ase"]; 9 | const popover = useRef(); 10 | useClickOutside(popover, close); 11 | 12 | return( 13 |
14 |

Select track type

15 |
16 | {track_types.map((t)=>{ 17 | return {setTrackType(t);close();}}/> 18 | })} 19 |
20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /figeno/gui/src/TracksContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {v4 as uuid4} from 'uuid'; 3 | import "./style.css"; 4 | import { DndContainer } from './DndContainer'; 5 | import {Track} from './Track' 6 | 7 | 8 | export const defaultTrackValues={ 9 | "chr_axis":{ 10 | height:"10", 11 | margin_above:"1.5", 12 | bounding_box:false, 13 | fontscale:1.0, 14 | label:"", 15 | label_rotate:false, 16 | style:"default", 17 | lw_scale:"1.0", 18 | ticklabels_pos:"below", 19 | unit:"kb", 20 | ticks_interval:"auto", 21 | ticks_angle:0, 22 | chr_prefix: "chr" 23 | }, 24 | "genes":{ 25 | height:"10", 26 | margin_above:"1.5", 27 | bounding_box:false, 28 | fontscale:1.0, 29 | label:"", 30 | label_rotate:false, 31 | style:"default", 32 | collapsed:true, 33 | only_protein_coding:true, 34 | exon_color:"#2980b9", 35 | genes:"auto", 36 | show_gene_names:true 37 | }, 38 | "bed":{ 39 | height:"3", 40 | margin_above:"1.5", 41 | bounding_box:false, 42 | fontscale:1.0, 43 | file:"", 44 | color:"#444444", 45 | show_names:false, 46 | show_strand:false, 47 | use_file_colors:false, 48 | collapsed:false, 49 | label:"", 50 | label_rotate:false 51 | }, 52 | "bigwig":{ 53 | height:"10", 54 | margin_above:"1.5", 55 | bounding_box:false, 56 | fontscale:1.0, 57 | label:"", 58 | label_rotate:false, 59 | file:"", 60 | color:"#2980b9", 61 | n_bins:500, 62 | scale:"auto", 63 | scale_max:"", 64 | group:"1", 65 | scale_pos:"corner", 66 | upside_down:false, 67 | show_negative:false, 68 | negative_color:"#2980b9" 69 | }, 70 | "coverage":{ 71 | height:"10", 72 | margin_above:"1.5", 73 | bounding_box:false, 74 | fontscale:1.0, 75 | label:"", 76 | label_rotate:false, 77 | file:"", 78 | color:"#888888", 79 | n_bins:500, 80 | scale:"auto", 81 | scale_max:"", 82 | group:"1", 83 | scale_pos:"corner", 84 | upside_down:false 85 | }, 86 | "alignments":{ 87 | height:"50", 88 | margin_above:"1.5", 89 | bounding_box:false, 90 | fontscale:1.0, 91 | file:"", 92 | label:"", 93 | label_rotate:false, 94 | hgap_bp:30, 95 | vgap_frac:0.3, 96 | read_color:"#cccccc", 97 | link_splitreads:false, 98 | splitread_color:"#999999", 99 | link_color:"#999999", 100 | link_lw:0.2, 101 | only_show_splitreads:false, 102 | min_splitreads_breakpoints:2, 103 | 104 | 105 | group_by:"none", 106 | show_unphased:true, 107 | exchange_haplotypes:false, 108 | show_haplotype_colors:true, 109 | haplotype_colors:["#27ae60","#e67e22","#808080"], 110 | haplotype_labels:["HP1", "HP2", "Unphased"], 111 | 112 | color_by:"none", 113 | color_unmodified:"#0f57e5", 114 | basemods:[["C","m","#f40202"]], 115 | fix_hardclip_basemod:false 116 | }, 117 | "basemod_freq":{ 118 | height:"20", 119 | margin_above:"1.5", 120 | bounding_box:true, 121 | fontscale:1.0, 122 | label:"Methylation freq", 123 | label_rotate:true, 124 | style:"lines", 125 | smooth:4, 126 | gap_frac:0.1, 127 | bams:[], 128 | bedmethyls:[] 129 | }, 130 | "hic":{ 131 | height:"50", 132 | margin_above:"1.5", 133 | bounding_box:true, 134 | fontscale:1.0, 135 | label:"", 136 | label_rotate:false, 137 | file:"", 138 | color_map:"red", 139 | pixel_border:false, 140 | upside_down:false, 141 | max_dist:700, 142 | extend:true, 143 | scale:"auto", 144 | scale_max_percentile:90, 145 | scale_min:"0.0", 146 | scale_max:"0.15", 147 | show_colorbar: false, 148 | interactions_across_regions:true, 149 | double_interactions_across_regions:true 150 | }, 151 | "sv":{ 152 | height:"15", 153 | margin_above:"1.5", 154 | bounding_box:true, 155 | fontscale:1.0, 156 | label:"", 157 | label_rotate:false, 158 | file:"", 159 | lw:"0.5", 160 | color_del:"#4a69bd", 161 | color_dup:"#e55039", 162 | color_t2t:"#8e44ad", 163 | color_h2h:"#8e44ad", 164 | color_trans:"#27ae60", 165 | min_sv_height:0.1, 166 | show_unpaired:true 167 | }, 168 | "copynumber":{ 169 | height:"30", 170 | margin_above:"1.5", 171 | bounding_box:true, 172 | fontscale:1.0, 173 | label:"", 174 | label_rotate:false, 175 | input_type:"freec", 176 | freec_ratios:"", 177 | freec_CNAs:"", 178 | purple_cn:"", 179 | delly_cn:"", 180 | delly_CNAs:"", 181 | genes:"", 182 | ploidy:"2", 183 | min_cn:"", 184 | max_cn:"", 185 | grid: true, 186 | grid_major:true, 187 | grid_minor:true, 188 | grid_cn:true, 189 | marker_size:"0.7", 190 | color_normal:"#000000", 191 | color_loss:"#4a69bd", 192 | color_gain:"#e55039", 193 | color_cnloh:"#f6b93b" 194 | }, 195 | "ase":{ 196 | height:"50", 197 | margin_above:"1.5", 198 | bounding_box:false, 199 | fontscale:1.0, 200 | label:"RNA,DNA", 201 | label_rotate:true, 202 | file:"", 203 | vcf_DNA:"", 204 | min_depth:"6", 205 | color1:"#e55039", 206 | color2:"#4a69bd", 207 | max_bar_width:"10.0", 208 | lw: "0.1", 209 | only_exonic: false, 210 | grid:false 211 | }, 212 | "other":{ 213 | height:"8", 214 | margin_above:"1.5", 215 | bounding_box:false, 216 | fontscale:1.0 217 | } 218 | } 219 | 220 | export function TracksContainer({tracksList,setTracksList,openColorPanel, openTrackTypePanel, setFileDialogData,setFileDialogActive}) { 221 | 222 | 223 | 224 | function set_value(id,attribute,value){ 225 | setTracksList((tracks)=>{ 226 | return tracks.map((track)=>{ 227 | if (track.id===id) { 228 | track[attribute]=value; 229 | if (attribute==="type"){ 230 | track = create_track({id:track.id,type:value}) 231 | } 232 | return track;} 233 | else {return track;} 234 | }) 235 | }); 236 | //track[attribute] = value 237 | } 238 | 239 | function get_tracks(){ 240 | return tracksList; 241 | } 242 | function copy_track(id){ 243 | const index = tracksList.findIndex((t)=>t.id==id); 244 | const newTrack = {...tracksList[index],id:uuid4()}; 245 | const newList = [...tracksList] 246 | newList.splice(index,0,newTrack); 247 | setTracksList(newList); 248 | } 249 | 250 | function create_track({id,type,track={}}){ 251 | let new_track = {id:id, type:type}; 252 | if (["chr_axis","genes","bed","bigwig","coverage","alignments", "basemod_freq","hic","sv","copynumber","ase"].includes(new_track.type)){ 253 | for (const attribute in defaultTrackValues[new_track.type]){ 254 | new_track[attribute] = track.hasOwnProperty(attribute) ? track[attribute] : defaultTrackValues[new_track.type][attribute] 255 | } 256 | } 257 | else{ 258 | for (const attribute in defaultTrackValues["other"]){ 259 | new_track[attribute] = track.hasOwnProperty(attribute) ? track[attribute] : defaultTrackValues["other"][attribute] 260 | } 261 | } 262 | return new_track 263 | } 264 | 265 | function handle_add_track(){ 266 | openTrackTypePanel((t)=>{add_track({"type":t})}); 267 | } 268 | 269 | function add_track(track={}){ 270 | const new_type = track.hasOwnProperty("type") ? track.type : ""; 271 | setTracksList([create_track({id:uuid4(),type:new_type,track:track}),...tracksList]); 272 | } 273 | function getTrackById (tracks, id) { 274 | return tracks.find((track) => track.id === id); 275 | } 276 | function delete_track(id){ 277 | setTracksList((tracks_list) => {return tracks_list.filter(track => track.id!==id)}) 278 | } 279 | 280 | function add_tracks_files(files){ 281 | const tracks=[]; 282 | for (const f of files){ 283 | if (f.endsWith(".bed")){ 284 | tracks.push({id:uuid4(),type:"bed",...defaultTrackValues["bed"],file:f}) 285 | } 286 | else if (f.endsWith(".bw") || (f.endsWith(".bigwig") || (f.endsWith("bigWig")))){ 287 | tracks.push({id:uuid4(),type:"bigwig",...defaultTrackValues["bigwig"],file:f}) 288 | } 289 | else if (f.endsWith(".cool")){ 290 | tracks.push({id:uuid4(),type:"hic",...defaultTrackValues["hic"],file:f}) 291 | } 292 | else if (f.endsWith(".bam")){ 293 | tracks.push({id:uuid4(),type:"alignments",...defaultTrackValues["alignments"],file:f}) 294 | } 295 | } 296 | setTracksList([...tracks,...tracksList]); 297 | } 298 | 299 | function open_files(){ 300 | fetch('/browse',{headers: {'Content-Type': 'application/json'}, body: JSON.stringify({path:"",dialog_type:"open_files"}), 301 | method:"POST"}).then(res => res.json()).then(data => { 302 | let files=[]; 303 | if (data.hasOwnProperty("current_dir")){ 304 | setFileDialogData({current_dir:data.current_dir,dirs:data.dirs,files:data.files,dialog_type:"open_files",update:function(f){add_tracks_files([f])}}) 305 | setFileDialogActive(true); 306 | } 307 | else if (data.hasOwnProperty("files")) add_tracks_files(data.files); 308 | }); 309 | } 310 | 311 | 312 | const header=
313 |

Tracks

314 | 318 | 324 |
; 325 | 326 | function show_active_item(active_id){ 327 | return 328 | } 329 | 330 | return ( 331 | <> 332 | 333 | {tracksList.map((track)=>{ 334 | return set_value(track.id,attribute,value)} className={"track"} copy_track={copy_track} delete_track={delete_track} openColorPanel={openColorPanel} openTrackTypePanel={openTrackTypePanel} setFileDialogActive={setFileDialogActive} setFileDialogData={setFileDialogData} /> 335 | })} 336 | 337 | 338 | ) 339 | } -------------------------------------------------------------------------------- /figeno/gui/src/images/template_asm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/gui/src/images/template_asm.png -------------------------------------------------------------------------------- /figeno/gui/src/images/template_bigwig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/gui/src/images/template_bigwig.png -------------------------------------------------------------------------------- /figeno/gui/src/images/template_hic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/gui/src/images/template_hic.png -------------------------------------------------------------------------------- /figeno/gui/src/images/template_wgs_chr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/gui/src/images/template_wgs_chr.png -------------------------------------------------------------------------------- /figeno/gui/src/images/template_wgs_circos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/figeno/gui/src/images/template_wgs_circos.png -------------------------------------------------------------------------------- /figeno/gui/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /figeno/gui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /figeno/gui/src/useClickOutside.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | // Improved version of https://usehooks.com/useOnClickOutside/ 4 | const useClickOutside = (ref, handler) => { 5 | useEffect(() => { 6 | let startedInside = false; 7 | let startedWhenMounted = false; 8 | 9 | const listener = (event) => { 10 | // Do nothing if `mousedown` or `touchstart` started inside ref element 11 | if (startedInside || !startedWhenMounted) return; 12 | // Do nothing if clicking ref's element or descendent elements 13 | if (!ref.current || ref.current.contains(event.target)) return; 14 | 15 | handler(event); 16 | }; 17 | 18 | const validateEventStart = (event) => { 19 | startedWhenMounted = ref.current; 20 | startedInside = ref.current && ref.current.contains(event.target); 21 | }; 22 | 23 | document.addEventListener("mousedown", validateEventStart); 24 | document.addEventListener("touchstart", validateEventStart); 25 | document.addEventListener("click", listener); 26 | 27 | return () => { 28 | document.removeEventListener("mousedown", validateEventStart); 29 | document.removeEventListener("touchstart", validateEventStart); 30 | document.removeEventListener("click", listener); 31 | }; 32 | }, [ref, handler]); 33 | }; 34 | 35 | export default useClickOutside; 36 | -------------------------------------------------------------------------------- /figeno/track_ase.py: -------------------------------------------------------------------------------- 1 | import pysam 2 | import matplotlib.pyplot as plt 3 | import matplotlib.patches as patches 4 | import numpy as np 5 | import pandas as pd 6 | 7 | from figeno.utils import split_box, draw_bounding_box, KnownException 8 | from figeno.genes import read_transcripts 9 | from figeno.ase import read_SNPs_RNA, read_SNPs_DNA 10 | 11 | import importlib_resources as resources 12 | import figeno.data 13 | 14 | class ase_track: 15 | def __init__(self,reference="hg19",genes_file=None,file=None,vcf_DNA=None,min_depth=6,color1="#e55039",color2="#4a69bd",max_bar_width=10.0,lw=0.1,only_exonic=False,grid=False,label="RNA,DNA",label_rotate=True,fontscale=1, 16 | bounding_box=False,height=50,margin_above=1.5,**kwargs): 17 | self.reference=reference 18 | self.genes_file = genes_file 19 | self.file=file 20 | if self.file is None or self.file=="": raise KnownException("Please provide a file for the ase track (generated by fast_ase or GATK ASEReadCounter).") 21 | self.vcf_DNA = vcf_DNA 22 | if self.vcf_DNA=="": self.vcf_DNA=None 23 | self.min_depth= int(min_depth) 24 | self.color1=color1 25 | self.color2=color2 26 | self.max_bar_width= float(max_bar_width) 27 | self.lw=float(lw) 28 | self.only_exonic=only_exonic 29 | self.grid=grid 30 | self.labels=label.split(",") 31 | if len(self.labels)==1: self.labels.append("") 32 | self.label_rotate=label_rotate 33 | self.fontscale=float(fontscale) 34 | 35 | self.bounding_box=bounding_box 36 | self.height = float(height) 37 | self.margin_above= float(margin_above) 38 | 39 | self.box_DNA=None 40 | self.box_RNA=None 41 | self.kwargs=kwargs 42 | 43 | def draw(self, regions, box ,hmargin,warnings=[]): 44 | if ("projection" in box and box["projection"]=="polar") or "top2" in box: 45 | raise KnownException("The ase track only supports the horizontal layout.") 46 | boxes = split_box(box,regions,hmargin) 47 | for i in range(len(regions)): 48 | res=self.draw_region(regions[i][0],boxes[i]) 49 | if not res: 50 | return False 51 | self.draw_title(self.box_RNA,self.labels[0]) 52 | if self.use_DNA: 53 | self.draw_title(self.box_DNA,self.labels[1]) 54 | 55 | for x in self.kwargs: 56 | warnings.append(x+" parameter was ignored in the ase track because it is not one of the accepted parameters.") 57 | 58 | def draw_region(self,region,box): 59 | if self.bounding_box: draw_bounding_box(box) 60 | 61 | if self.genes_file is None or self.genes_file=="": 62 | if self.reference in ["hg19","hg38","mm10"]: 63 | with resources.as_file(resources.files(figeno.data) / (self.reference+"_genes.txt.gz")) as infile: 64 | transcripts = read_transcripts(infile,region.chr,region.start,region.end) 65 | else: 66 | transcripts=[] 67 | else: 68 | transcripts = read_transcripts(self.genes_file,region.chr,region.start,region.end,only_protein_coding=False) 69 | 70 | exons=[] 71 | for transcript in transcripts: 72 | exons = exons+transcript.exons 73 | 74 | df_ase = read_SNPs_RNA(self.file,chr=region.chr,start=region.start,end=region.end,exons=exons,min_depth=self.min_depth) 75 | if self.only_exonic: df_ase = df_ase[df_ase["exonic"]] 76 | if df_ase.shape[0]==0: return False 77 | if self.vcf_DNA is not None: 78 | df_ase = read_SNPs_DNA(self.vcf_DNA,df_ase) 79 | 80 | self.use_DNA = "VAF_DNA" in df_ase.columns 81 | 82 | if self.use_DNA: 83 | box_DNA = {"ax":box["ax"],"left":box["left"]+(box["right"]-box["left"])*0.15,"right":box["left"]+(box["right"]-box["left"])*0.95,"bottom":box["bottom"] + (box["top"]-box["bottom"]) * 0.65, "top":box["top"]} 84 | box_RNA = {"ax":box["ax"],"left":box["left"]+(box["right"]-box["left"])*0.15,"right":box["left"]+(box["right"]-box["left"])*0.95,"bottom":box["bottom"] + (box["top"]-box["bottom"]) * 0.2, "top":box["bottom"] + (box["top"]-box["bottom"]) * 0.55} 85 | else: 86 | box_RNA = {"ax":box["ax"],"left":box["left"]+(box["right"]-box["left"])*0.15,"right":box["left"]+(box["right"]-box["left"])*0.95,"bottom":box["bottom"] + (box["top"]-box["bottom"]) * 0.2, "top":box["top"]} 87 | x_positions = draw_hist_VAF(box_RNA,list(df_ase["VAF"]),color1=self.color1,color2=self.color2,max_bar_width=self.max_bar_width) 88 | if self.grid: draw_grid(box_RNA) 89 | if self.box_RNA is None: self.box_RNA = box_RNA 90 | 91 | if self.use_DNA: 92 | tmp = draw_hist_VAF(box_DNA,list(df_ase["VAF_DNA"]),color1=self.color1,color2=self.color2,max_bar_width=self.max_bar_width) 93 | if self.grid: draw_grid(box_DNA) 94 | if self.box_DNA is None: self.box_DNA = box_DNA 95 | 96 | 97 | 98 | box_mapping = {"ax":box["ax"],"left":box["left"],"right":box["right"],"bottom":box["bottom"], "top":box["bottom"] + (box["top"]-box["bottom"]) * 0.2} 99 | draw_mapping_SNP(box_mapping,region,x_positions,list(df_ase["position"]),lw=self.lw) 100 | return True 101 | 102 | 103 | 104 | 105 | def draw_title(self,box,label): 106 | 107 | tick_width=1.0 108 | lw=1 109 | 110 | box["ax"].plot([box["left"],box["left"]],[box["bottom"],box["top"]],color="black",linewidth=lw,zorder=4) 111 | 112 | box["ax"].plot([box["left"],box["left"]-tick_width],[box["bottom"],box["bottom"]],color="black",linewidth=lw,zorder=4) 113 | box["ax"].text(box["left"]-tick_width*1.3,box["bottom"],"0",horizontalalignment="right",verticalalignment="bottom",fontsize=9*self.fontscale) 114 | 115 | box["ax"].plot([box["left"],box["left"]-tick_width],[box["bottom"]+(box["top"]-box["bottom"])*0.25,box["bottom"]+(box["top"]-box["bottom"])*0.25],color="black",linewidth=lw,zorder=4) 116 | 117 | box["ax"].plot([box["left"],box["left"]-tick_width],[(box["bottom"]+box["top"])/2,(box["bottom"]+box["top"])/2],color="black",linewidth=lw,zorder=4) 118 | box["ax"].text(box["left"]-tick_width*1.3,(box["bottom"]+box["top"])/2,"0.5",horizontalalignment="right",verticalalignment="center",fontsize=9*self.fontscale) 119 | 120 | box["ax"].plot([box["left"],box["left"]-tick_width],[box["bottom"]+(box["top"]-box["bottom"])*0.75,box["bottom"]+(box["top"]-box["bottom"])*0.75],color="black",linewidth=lw,zorder=4) 121 | 122 | box["ax"].plot([box["left"],box["left"]-tick_width],[box["top"],box["top"]],color="black",linewidth=lw,zorder=4) 123 | box["ax"].text(box["left"]-tick_width*1.3,box["top"],"1",horizontalalignment="right",verticalalignment="top",fontsize=9*self.fontscale) 124 | 125 | if len(label)>0: 126 | rotation = 90 if self.label_rotate else 0 127 | box["ax"].text(box["left"] - 7.0,(box["top"]+box["bottom"])/2, 128 | label,rotation=rotation,horizontalalignment="right",verticalalignment="center",fontsize=12*self.fontscale) 129 | 130 | 131 | def draw_hist_VAF(box,VAFs,color1="#e55039",color2="#4a69bd",max_bar_width=5): 132 | bar_width=(box["right"]-box["left"]) * 0.9 / len(VAFs) 133 | if bar_width>max_bar_width: bar_width=max_bar_width 134 | #bar_width=(box["right"]-box["left"]) * bar_width / len(VAFs) 135 | x_positions = [] 136 | for i in range(len(VAFs)): 137 | x = box["left"] + (box["right"]-box["left"]) * (i+0.5)/len(VAFs) 138 | x_positions.append(x) 139 | rect = patches.Rectangle((x-bar_width/2,box["bottom"]),width=bar_width, height=(box["top"]-box["bottom"])*VAFs[i],color=color1,lw=0) 140 | box["ax"].add_patch(rect) 141 | rect = patches.Rectangle((x-bar_width/2,box["bottom"]+(box["top"]-box["bottom"])*VAFs[i]),width=bar_width,height=(box["top"]-box["bottom"])*(1-VAFs[i]),color=color2,lw=0) 142 | box["ax"].add_patch(rect) 143 | return x_positions 144 | 145 | def draw_grid(box): 146 | for y in [0.25,0.5,0.75]: 147 | y2 = box["bottom"] + y *(box["top"]-box["bottom"]) 148 | box["ax"].plot([box["left"],box["right"]],[y2,y2],linewidth=1,linestyle="dashed",color="gray") 149 | 150 | def draw_mapping_SNP(box,region,x_positions,SNP_coords,lw=0.1): 151 | tip_height = (box["top"] - box["bottom"]) * 0.1 152 | rect_width=0.15 153 | x_realpositions = [box["left"] + (box["right"]-box["left"]) * (coord-region.start) / (region.end-region.start) for coord in SNP_coords] 154 | for i in range(len(x_positions)): 155 | if x_positions[i]=1.2*self.arrow_width: 104 | # Number of arrows that can fit in the rectangle 105 | n_arrows= 1+ int((rect_width-3*self.arrow_width)/(self.arrow_gapwidth+self.arrow_width)) 106 | 107 | if rect_width<3*self.arrow_width: 108 | current_left_pos=(converted_start+converted_end)/2 - self.arrow_width/2 109 | else: 110 | arrows_width= 3*self.arrow_width + (n_arrows-1)*(self.arrow_gapwidth+self.arrow_width) 111 | current_left_pos=(converted_start+converted_end)/2 -arrows_width/2 + self.arrow_width 112 | for i in range(n_arrows): 113 | if bedrect.strand=="+": 114 | box["ax"].plot([current_left_pos,current_left_pos+self.arrow_width,current_left_pos],[arrow_top,arrow_center,arrow_bottom],color="white",lw=0.4,zorder=3) 115 | else: 116 | box["ax"].plot([current_left_pos+self.arrow_width,current_left_pos,current_left_pos+self.arrow_width],[arrow_top,arrow_center,arrow_bottom],color="white",lw=0.4,zorder=3) 117 | current_left_pos+=self.arrow_gapwidth+self.arrow_width 118 | 119 | 120 | def draw_title(self,box): 121 | if len(self.label)>0: 122 | self.label = self.label.replace("\\n","\n") 123 | rotation = 90 if self.label_rotate else 0 124 | box["ax"].text(box["left"] - 1,(box["top"]+box["bottom"])/2, 125 | self.label,rotation=rotation,horizontalalignment="right",verticalalignment="center",fontsize=7*self.fontscale) 126 | 127 | def get_record_name(self,linesplit): 128 | if self.show_names: 129 | if len(linesplit)>3: 130 | return linesplit[3] 131 | else: 132 | raise KnownException("For the bed file "+self.file+", you ticked the show_names option, but no names were found for line "+"\t".join(linesplit) +" (should be the 4th column).") 133 | else: return "" 134 | 135 | 136 | def get_record_strand(self,linesplit): 137 | if self.show_strand: 138 | if len(linesplit)>5: 139 | strand=linesplit[5] 140 | if not strand in ["+","-","."]: raise KnownException("Unknown strand information for line "+"\t".join(linesplit) +" (should be the '+', '-', or '.' in the 6th column).") 141 | return strand 142 | else: raise KnownException("For the bed file "+self.file+", you ticked the show_strand option, but no strand information was found for line "+"\t".join(linesplit) +" (should be the 6th column).") 143 | else: return "." 144 | 145 | def get_record_color(self,linesplit): 146 | if self.use_file_colors: 147 | if len(linesplit)>=9: 148 | return "#{:02x}{:02x}{:02x}".format(*[int(x) for x in linesplit[8].split(",")]) 149 | else: raise KnownException("For the bed file "+self.file+", you ticked the use_file_colors option, but no color information was found for line "+"\t".join(linesplit) +" (should be the 9th column).") 150 | else: return self.color 151 | 152 | def read_records_regions(self,regions): 153 | regions = [reg[0] for reg in regions] 154 | lines_regions=[] 155 | for region in regions: 156 | lines_region=[] 157 | if self.collapsed: lines_region.append([]) 158 | infile = gzip.open(self.file,"rt") if self.file.endswith(".gz") else open(self.file,"r") 159 | for line in infile: 160 | if line.startswith("#") or line.startswith("track") or line.startswith("browser"): continue 161 | linesplit = line.rstrip("\n").split("\t") 162 | if len(linesplit)<3: raise KnownException("The following bed file has the wrong format: "+self.file+".\nThe file should be tab-separated, without header, with at least 3 columns: chr, start, end.\nProblematic line:\n"+line[:70]) 163 | if linesplit[0].lstrip("chr")==region.chr.lstrip("chr"): 164 | try: 165 | start,end = int(linesplit[1]),int(linesplit[2]) 166 | except: 167 | raise KnownException("The following bed file has the wrong format: "+self.file+".\nThe file should be tab-separated, without header, with at least 3 columns: chr, start, end (where start and end should be integers). \nProblematic line:\n"+line[:70]) 168 | if start <=region.end and end >= region.start: 169 | name=self.get_record_name(linesplit) 170 | strand=self.get_record_strand(linesplit) 171 | color=self.get_record_color(linesplit) 172 | bedrect=BedRect(region.chr,start,end,name,strand,color) 173 | if self.collapsed: lines_region[0].append(bedrect) 174 | else: add_BedRect_pile(bedrect,lines_region,margin=0) 175 | infile.close() 176 | lines_regions.append(lines_region) 177 | max_nlines = max([len(l) for l in lines_regions]) 178 | 179 | new_line_order = reorder_balanced(0,max_nlines) 180 | 181 | for i in range(len(regions)): 182 | lines_regions[i]+=[[] for j in range(0,max_nlines-len(lines_regions[i]))] 183 | lines_regions[i] = [lines_regions[i][j] for j in new_line_order] 184 | return lines_regions 185 | 186 | 187 | 188 | 189 | 190 | def add_BedRect_pile(bedrect,l,margin=0): 191 | # Assign rectangles to lines (to avoid overlap) 192 | i=0 193 | while i1: warnings.append("You provided only "+str(len(self.scale_max))+" values for scale_max, even though "+str(len(regions))+" were used. "\ 49 | "The first scale_max parameter will be used for all regions for which no scale_max value was provided.") 50 | self.draw_region(regions[i][0],boxes[i],scale_max=scale_max_region,nbins=bins_regions[i],show_scale_inside=show_scale_inside) 51 | self.draw_title(box) 52 | 53 | for x in self.kwargs: 54 | warnings.append(x+" parameter was ignored in the bigwig track because it is not one of the accepted parameters.") 55 | 56 | 57 | 58 | def draw_region(self,region,box,scale_max,nbins,show_scale_inside): 59 | if scale_max<0: raise KnownException("scale_max for bigwig tracks must be >=0.") 60 | if self.bounding_box: draw_bounding_box(box) 61 | if nbins>region.end-region.start: nbins=region.end-region.start 62 | 63 | region = correct_region_chr(region,self.bw.chroms()) 64 | values_binned_original=self.bw.stats(region.chr,region.start,region.end,nBins=nbins,exact=abs(region.end-region.start)<1000000,type="mean") 65 | values_binned_original = [x if x is not None else 0 for x in values_binned_original] 66 | if region.orientation=="-": values_binned_original = values_binned_original[::-1] 67 | n_bases_per_bin = (region.end-region.start) / nbins #float 68 | 69 | if scale_max<=0: scale_max=0.001 70 | values_binned = [max(min(scale_max,x),0) for x in values_binned_original] 71 | if self.show_negative: 72 | values_binned_negative = [min(max(-scale_max,x),0) for x in values_binned_original] 73 | 74 | if self.show_negative: 75 | if not self.upside_down: 76 | y0=(box["bottom"]+box["top"])/2 77 | z=1/scale_max/2 * (box["top"]-box["bottom"]) 78 | else: 79 | y0=(box["bottom"]+box["top"])/2 80 | z=-1/scale_max/2 * (box["top"]-box["bottom"]) 81 | else: 82 | if not self.upside_down: 83 | y0=box["bottom"] 84 | z=1/scale_max * (box["top"]-box["bottom"]) 85 | else: 86 | y0=box["top"] 87 | z=-1/scale_max * (box["top"]-box["bottom"]) 88 | 89 | def compute_y(value): 90 | return y0+value*z 91 | 92 | 93 | polygon_vertices = [(box["right"],compute_y(0)),(box["left"],compute_y(0))] 94 | polygon_vertices_negative = [(box["right"],compute_y(0)),(box["left"],compute_y(0))] 95 | for i in range(nbins): 96 | x=box["left"] + i*n_bases_per_bin/(region.end-region.start) * (box["right"] - box["left"]) 97 | if values_binned[i] is not None: 98 | y=compute_y(values_binned[i]) 99 | polygon_vertices.append((x,y)) 100 | if self.show_negative and values_binned_negative[i] is not None: 101 | y_neg=compute_y(values_binned_negative[i]) 102 | polygon_vertices_negative.append((x,y_neg)) 103 | if "projection" in box and box["projection"]=="polar": 104 | polygon_vertices = interpolate_polar_vertices(polygon_vertices) 105 | if self.show_negative: 106 | polygon_vertices_negative = interpolate_polar_vertices(polygon_vertices_negative) 107 | polygon = patches.Polygon(polygon_vertices,lw=0.0,color=self.color) 108 | box["ax"].add_patch(polygon) 109 | if self.show_negative: 110 | polygon_neg = patches.Polygon(polygon_vertices_negative,lw=0.0,color=self.negative_color) 111 | box["ax"].add_patch(polygon_neg) 112 | 113 | 114 | if show_scale_inside and ((not "projection" in box) or box["projection"]!="polar"): 115 | upperlimit_string = "{:.1f}".format(scale_max) if scale_max>=1 else "{:.2f}".format(scale_max) 116 | lowerlimit_string = "0" if not self.show_negative else "-"+upperlimit_string 117 | label="["+lowerlimit_string+" - "+upperlimit_string+"]" 118 | box["ax"].text(box["left"]+0.05,box["top"]-0.02,label,horizontalalignment="left",verticalalignment="top",fontsize=6*self.fontscale) 119 | 120 | def draw_title(self,box): 121 | if "projection" in box and box["projection"]=="polar": 122 | if len(self.label)>0: 123 | self.label = self.label.replace("\\n","\n") 124 | rotation = 90 if self.label_rotate else 0 125 | x,y= polar2cartesian((box["left"],(box["top"]+box["bottom"])/2)) 126 | theta,r = cartesian2polar((x-1,y)) 127 | 128 | box["ax"].text(theta,r, 129 | self.label,rotation=rotation,horizontalalignment="right",verticalalignment="center",fontsize=10*self.fontscale) 130 | if self.scale_pos=="left": 131 | if not self.upside_down: 132 | upperlimit_string = "{:.1f}".format(self.scale_max[0]) if self.scale_max[0]>=1 else "{:.2f}".format(self.scale_max[0]) 133 | lowerlimit_string = "0" if not self.show_negative else "-"+upperlimit_string 134 | else: 135 | lowerlimit_string = "{:.1f}".format(self.scale_max[0]) if self.scale_max[0]>=1 else "{:.2f}".format(self.scale_max[0]) 136 | upperlimit_string = "0" if not self.show_negative else "-"+upperlimit_string 137 | x,y= polar2cartesian((box["left"],box["top"])) 138 | theta,r = cartesian2polar((x-0.2,y)) 139 | box["ax"].text(theta,r, 140 | upperlimit_string,horizontalalignment="right",verticalalignment="top",fontsize=7*self.fontscale) 141 | x,y= polar2cartesian((box["left"],box["bottom"])) 142 | theta,r = cartesian2polar((x-0.2,y)) 143 | box["ax"].text(theta,r, 144 | lowerlimit_string,horizontalalignment="right",verticalalignment="bottom",fontsize=7*self.fontscale) 145 | else: 146 | if len(self.label)>0: 147 | self.label = self.label.replace("\\n","\n") 148 | rotation = 90 if self.label_rotate else 0 149 | 150 | box["ax"].text(box["left"] - 1.0,(box["top"]+box["bottom"])/2, 151 | self.label,rotation=rotation,horizontalalignment="right",verticalalignment="center",fontsize=7*self.fontscale) 152 | if self.scale_pos=="left": 153 | upperlimit_string = "{:.1f}".format(self.scale_max[0]) if self.scale_max[0]>=1 else "{:.2f}".format(self.scale_max[0]) 154 | lowerlimit_string = "0" if not self.show_negative else "-"+upperlimit_string 155 | if not self.upside_down: 156 | box["ax"].text(box["left"] - 0.5,box["top"], 157 | upperlimit_string,horizontalalignment="right",verticalalignment="top",fontsize=6*self.fontscale) 158 | box["ax"].text(box["left"] - 0.5,box["bottom"], 159 | lowerlimit_string,horizontalalignment="right",verticalalignment="bottom",fontsize=6*self.fontscale) 160 | else: 161 | box["ax"].text(box["left"] - 0.5,box["top"], 162 | lowerlimit_string,horizontalalignment="right",verticalalignment="top",fontsize=6*self.fontscale) 163 | box["ax"].text(box["left"] - 0.5,box["bottom"], 164 | upperlimit_string,horizontalalignment="right",verticalalignment="bottom",fontsize=6*self.fontscale) 165 | 166 | 167 | def compute_bins(self,regions): 168 | total_length_regions = np.sum([abs(reg[0].end-reg[0].start) for reg in regions]) 169 | return [max(1,int(self.n_bins/total_length_regions * abs(reg[0].end-reg[0].start))) for reg in regions] 170 | 171 | def compute_max(self,regions,per_region=False): 172 | bins = self.compute_bins(regions) 173 | l=[] 174 | m=0 175 | for i in range(len(regions)): 176 | if per_region: m=0 177 | reg = regions[i][0] 178 | region = correct_region_chr(reg,self.bw.chroms()) 179 | if region.end>self.bw.chroms()[region.chr]: 180 | raise KnownException("The region "+region.chr+":"+str(region.start)+"-"+str(region.end)+" has coordinates greater than the chromosome length ("+str(self.bw.chroms()[region.chr])+") in the bigwig file "+self.filename) 181 | values_binned=self.bw.stats(region.chr,region.start,region.end,nBins=bins[i],exact=True,type="mean") 182 | values_binned = [x if x is not None else 0 for x in values_binned] 183 | m = max(m,np.max(values_binned)*1.1) 184 | if self.show_negative: 185 | m = max(m,-np.min(values_binned)*1.1) 186 | if per_region: l.append(m) 187 | if not per_region: l.append(m) 188 | return l -------------------------------------------------------------------------------- /figeno/track_coverage.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pysam 3 | import matplotlib.pyplot as plt 4 | import matplotlib.patches as patches 5 | import numpy as np 6 | import pandas as pd 7 | 8 | from figeno.utils import KnownException, correct_region_chr, split_box, draw_bounding_box 9 | 10 | class coverage_track: 11 | def __init__(self,file,n_bins=500,color="gray",scale="auto",scale_max=None,scale_pos="corner",group=None,upside_down=False,label="",label_rotate=False,fontscale=1, 12 | vcf=None,SNP_colors="auto",exchange_haplotypes=False,bounding_box=False,height=10,margin_above=1.5,**kwargs): 13 | if file=="" or file is None: 14 | raise KnownException("Please provide a bam file for the coverage track.") 15 | if not os.path.isfile(file): 16 | raise KnownException("The following bam file does not exist (in coverage track): "+file) 17 | try: 18 | self.samfile =pysam.AlignmentFile(file, "rb") 19 | except: 20 | raise KnownException("Failed to open bam file (in coverage track): "+str(file)) 21 | if not self.samfile.has_index(): 22 | raise KnownException("Missing index file for bam file (in coverage track): "+str(file)+". Such an index (ending in .bai) is required and can be generated with samtools index.") 23 | self.filename=file 24 | self.n_bins = int(n_bins) 25 | self.color=color 26 | self.label=label 27 | self.label_rotate=label_rotate 28 | self.fontscale=float(fontscale) 29 | self.scale = scale 30 | self.scale_max = scale_max 31 | self.group=group 32 | self.scale_pos = scale_pos 33 | self.upside_down= upside_down 34 | self.vcf = vcf 35 | self.SNP_colors=SNP_colors 36 | self.exchange_haplotypes = exchange_haplotypes 37 | self.bounding_box=bounding_box 38 | self.height = float(height) 39 | self.margin_above= float(margin_above) 40 | self.kwargs=kwargs 41 | 42 | self.atleast_one_region_has_coverage=False 43 | 44 | 45 | def draw(self, regions, box ,hmargin,warnings=[]): 46 | boxes = split_box(box,regions,hmargin) 47 | for i in range(len(regions)): 48 | show_scale_inside = self.scale_pos=="corner all" or (self.scale_pos=="corner" and i==0) 49 | if i1: warnings.append("You provided only "+str(len(self.scale_max))+" values for scale_max, even though "+str(len(regions))+" were used. "\ 53 | "The first scale_max parameter will be used for all regions for which no scale_max value was provided.") 54 | self.draw_region(regions[i][0],boxes[i],scale_max=scale_max_region,show_scale_inside=show_scale_inside) 55 | self.draw_title(box) 56 | 57 | for x in self.kwargs: 58 | warnings.append(x+" parameter was ignored in the coverage track because it is not one of the accepted parameters.") 59 | 60 | if not self.atleast_one_region_has_coverage: warnings.append("The coverage was null for all displayed regions in the bam file "+self.filename+".") 61 | 62 | def draw_region(self,region,box,scale_max,show_scale_inside): 63 | region = correct_region_chr(region,self.samfile.references) 64 | if self.bounding_box: draw_bounding_box(box) 65 | coverage=np.sum(self.samfile.count_coverage(region.chr,region.start,region.end),axis=0) 66 | coverage=np.nan_to_num(coverage,0) 67 | n_bins = min(self.n_bins,len(coverage)) 68 | n_bases_per_bin = (region.end-region.start) / n_bins 69 | coverage_bin = [np.mean(coverage[int(i*n_bases_per_bin):int((i+1)*n_bases_per_bin)]) for i in range(n_bins)] 70 | 71 | max_coverage = np.max(coverage_bin) * 1.1 72 | if max_coverage>=1: self.atleast_one_region_has_coverage=True 73 | rect_width = (box["right"] - box["left"]) / len(coverage_bin) 74 | 75 | 76 | # Draw rectangles for each bin 77 | if not self.upside_down: 78 | vertices=[(box["right"],box["bottom"]),(box["left"],box["bottom"])] 79 | else: 80 | vertices=[(box["right"],box["top"]),(box["left"],box["top"])] 81 | for i in range(len(coverage_bin)): 82 | rect_left = box["left"] + i*rect_width 83 | 84 | if region.orientation=="+": 85 | rect_height = coverage_bin[i]/scale_max * (box["top"]-box["bottom"]) 86 | else: 87 | rect_height = coverage_bin[len(coverage_bin)-1-i]/scale_max * (box["top"]-box["bottom"]) 88 | #rect = patches.Rectangle((rect_left,box["bottom"]), 89 | # rect_width,rect_height,color=self.color,lw=0.2,zorder=1) 90 | #box["ax"].add_patch(rect) 91 | if not self.upside_down: 92 | vertices.append((rect_left,box["bottom"]+rect_height)) 93 | vertices.append((rect_left+rect_width,box["bottom"]+rect_height)) 94 | else: 95 | vertices.append((rect_left,box["top"]-rect_height)) 96 | vertices.append((rect_left+rect_width,box["top"]-rect_height)) 97 | 98 | #if i in bin2vaf: # if there is a SNP in the bin 99 | # rect = patches.Rectangle((rect_left,box["bottom"]), 100 | # rect_width,rect_height*bin2vaf[i][0],color=self.SNP_colors[0],lw=0.2,zorder=1.1) 101 | # box["ax"].add_patch(rect) 102 | # rect = patches.Rectangle((rect_left,box["bottom"] + rect_height*bin2vaf[i][0]), 103 | # rect_width,rect_height*bin2vaf[i][1],color=self.SNP_colors[1],lw=0.2,zorder=1.1) 104 | # box["ax"].add_patch(rect) 105 | polygon = patches.Polygon(vertices,lw=0,zorder=1,color=self.color) 106 | box["ax"].add_patch(polygon) 107 | 108 | if show_scale_inside: 109 | upperlimit_string = "{:d}".format(int(scale_max)) 110 | lowerlimit_string = "0" 111 | label="["+lowerlimit_string+"-"+upperlimit_string+"]" 112 | box["ax"].text(box["left"]+0.05,box["top"]-0.02,label,horizontalalignment="left",verticalalignment="top",fontsize=6*self.fontscale) 113 | 114 | 115 | def draw_title(self,box): 116 | if len(self.label)>0: 117 | self.label = self.label.replace("\\n","\n") 118 | rotation = 90 if self.label_rotate else 0 119 | box["ax"].text(box["left"] - 1.0,(box["top"]+box["bottom"])/2, 120 | self.label,rotation=rotation,horizontalalignment="right",verticalalignment="center",fontsize=7*self.fontscale) 121 | if self.scale_pos=="left": 122 | if not self.upside_down: 123 | upperlimit_string = "{:d}".format(int(self.scale_max[0])) 124 | lowerlimit_string = "0" 125 | else: 126 | lowerlimit_string = "{:d}".format(int(self.scale_max[0])) 127 | upperlimit_string = "0" 128 | box["ax"].text(box["left"] - 0.5,box["top"], 129 | upperlimit_string,horizontalalignment="right",verticalalignment="top",fontsize=6*self.fontscale) 130 | box["ax"].text(box["left"] - 0.5,box["bottom"], 131 | lowerlimit_string,horizontalalignment="right",verticalalignment="bottom",fontsize=6*self.fontscale) 132 | 133 | 134 | def compute_max(self,regions,per_region=False): 135 | l=[] 136 | max_cov=1 137 | for i in range(len(regions)): 138 | if per_region: max_cov=1 139 | reg = regions[i][0] 140 | region = correct_region_chr(reg,self.samfile.references) 141 | coverage=np.sum(self.samfile.count_coverage(region.chr,region.start,region.end),axis=0) 142 | coverage=np.nan_to_num(coverage,nan=0) 143 | n_bins = min(self.n_bins,len(coverage)) 144 | n_bases_per_bin = (region.end-region.start) // n_bins 145 | coverage_bin = [np.mean(coverage[i*n_bases_per_bin:(i+1)*n_bases_per_bin]) for i in range(n_bins)] 146 | if (region.end-region.start) % n_bins > 0: 147 | new_value = np.mean(coverage[n_bases_per_bin*(n_bins-1):]) 148 | if new_value==new_value:coverage_bin.append(new_value) 149 | coverage_bin+= [] 150 | max_cov=max(max_cov,np.max(coverage_bin) * 1.1) 151 | if max_cov<1 or max_cov!=max_cov: max_cov=1 152 | if per_region: l.append(round(max_cov)) 153 | if not per_region: l.append(round(max_cov)) 154 | return l 155 | 156 | 157 | -------------------------------------------------------------------------------- /figeno/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from matplotlib import patches 3 | from matplotlib.collections import PatchCollection 4 | from collections import namedtuple 5 | Region = namedtuple('Region', 'chr start end orientation color') 6 | Highlight = namedtuple('Highlight', 'chr start end color alpha') 7 | 8 | 9 | class KnownException(Exception): 10 | pass 11 | 12 | def correct_region_chr(region,chromosomes,file=""): 13 | if region.chr in chromosomes: 14 | return region 15 | elif "chr"+region.chr in chromosomes: 16 | return Region("chr"+region.chr,region.start,region.end,region.orientation,region.color) 17 | elif "Chr"+region.chr in chromosomes: 18 | return Region("Chr"+region.chr,region.start,region.end,region.orientation,region.color) 19 | elif region.chr.lstrip("chr") in chromosomes: 20 | return Region(region.chr.lstrip("chr"),region.start,region.end,region.orientation,region.color) 21 | else: 22 | error_message="Could not find chromosome "+region.chr 23 | if file!="": error_message+=" in file "+file+"." 24 | else: error_message+="." 25 | error_message+=" Only the following chromosome names were found: "+", ".join(chromosomes)+" (the chr prefix can be omitted)." 26 | raise KnownException(error_message) 27 | 28 | def split_box(box,regions,hmargin): 29 | boxes=[] 30 | current_left=box["left"] 31 | additional_width = hmargin/len(regions) # compensate the last margin 32 | if "projection" in box and box["projection"]=="polar": 33 | total_width = np.sum([x[1] for x in regions]) + hmargin * (len(regions)-1) 34 | for region in regions: 35 | width = region[1] 36 | width_angle = (box["right"] +0.25 - box["left"]) * width / total_width 37 | margin_angle = (box["right"] +0.25 - box["left"]) * hmargin / total_width 38 | box1= {"ax":box["ax"],"bottom":box["bottom"],"top":box["top"],"left":current_left,"right":current_left+width_angle,"projection":"polar"} 39 | current_left+=width_angle + margin_angle 40 | boxes.append(box1) 41 | elif "bottom2" in box: # 2 rows 42 | # Count number of regions in each row 43 | count0,count1=0,0 44 | length0,length1=0,0 45 | for reg,width,row in regions: 46 | if row==0: 47 | count0+=1 48 | length0+=width 49 | else: 50 | count1+=1 51 | length1+=width 52 | if length0>length1: additional_width = hmargin/count0 53 | else: additional_width = hmargin/count1 54 | 55 | current_row=0 56 | for reg,width,row in regions: 57 | if row==0: 58 | box1= {"ax":box["ax"],"bottom":box["bottom"],"top":box["top"],"left":current_left,"right":current_left+width+additional_width-hmargin,"upside_down":True} 59 | else: 60 | if current_row==0: 61 | current_row=1 62 | current_left=box["left"] 63 | box1= {"ax":box["ax"],"bottom":box["bottom2"],"top":box["top2"],"left":current_left,"right":current_left+width+additional_width-hmargin} 64 | current_left+=width+additional_width 65 | boxes.append(box1) 66 | elif "total_height" in box: 67 | for i, region in enumerate(regions): 68 | box1= {"ax":box["ax"],"bottom":box["bottom"]-i*box["total_height"],"top":box["top"]-i*box["total_height"],"left":0,"right":region[1]} 69 | boxes.append(box1) 70 | else: 71 | for region in regions: 72 | width = region[1] 73 | box1= {"ax":box["ax"],"bottom":box["bottom"],"top":box["top"],"left":current_left,"right":current_left+width+additional_width-hmargin} 74 | current_left+=width+additional_width 75 | boxes.append(box1) 76 | return boxes 77 | 78 | def draw_bounding_box(box): 79 | #rect = patches.Rectangle((box["left"],box["bottom"]),(box["right"]-box["left"])*1.00,box["top"]-box["bottom"],color="black",fc='none',lw=0.5,zorder=2) 80 | #box["ax"].add_patch(rect) 81 | w=0.3 82 | if "projection" in box and box["projection"]=="polar": 83 | w=0.2 84 | # Left 85 | theta,r1,r2 = box["left"] , box["bottom"] - w , box["top"] + w 86 | x1,y1 = polar2cartesian((theta,r1)) 87 | x2,y2 = x1 - w*np.sin(theta) , y1 + w*np.cos(theta) 88 | x4,y4 = polar2cartesian((theta,r2)) 89 | x3,y3 =x4- w*np.sin(theta) , y4 + w*np.cos(theta) 90 | 91 | left_vertices = [cartesian2polar((x1,y1)) , cartesian2polar((x2,y2)) , cartesian2polar((x3,y3)) ,cartesian2polar((x4,y4))] 92 | #left_vertices = interpolate_polar_vertices(left_vertices) 93 | left = patches.Polygon(left_vertices,color="black",lw=0,zorder=2) 94 | 95 | # Right 96 | theta,r1,r2 = box["right"] , box["bottom"] - w , box["top"] + w 97 | x1,y1 = polar2cartesian((theta,r1)) 98 | x2,y2 = x1 + w*np.sin(theta) , y1 - w*np.cos(theta) 99 | x4,y4 = polar2cartesian((theta,r2)) 100 | x3,y3 =x4+ w*np.sin(theta) , y4 - w*np.cos(theta) 101 | right_vertices = [cartesian2polar((x1,y1)) , cartesian2polar((x2,y2)) , cartesian2polar((x3,y3)) ,cartesian2polar((x4,y4))] 102 | #right_vertices = interpolate_polar_vertices(right_vertices) 103 | right = patches.Polygon(right_vertices,color="black",lw=0,zorder=2) 104 | 105 | # Top 106 | top_vertices = [(box["left"]+0.001,box["top"]) , (box["left"]+0.001 , box["top"]+w) , (box["right"]-0.001 , box["top"] + w) , (box["right"]-0.001,box["top"])] 107 | top_vertices = interpolate_polar_vertices(top_vertices) 108 | top = patches.Polygon(top_vertices,color="black",lw=0,zorder=2) 109 | 110 | # Bottom 111 | bottom_vertices = [(box["left"]+0.001,box["bottom"]) , (box["left"]+0.001 , box["bottom"]-w) , (box["right"]-0.001 , box["bottom"] - w) , (box["right"]-0.001,box["bottom"])] 112 | bottom_vertices = interpolate_polar_vertices(bottom_vertices) 113 | bottom = patches.Polygon(bottom_vertices,color="black",lw=0,zorder=2) 114 | 115 | coll = PatchCollection([left,right,top,bottom],match_original=True,zorder=5) 116 | box["ax"].add_collection(coll) 117 | 118 | else: 119 | rect_left = patches.Rectangle((box["left"]-w,box["bottom"]-w),width=w,height=box["top"]-box["bottom"]+2*w,color="black",zorder=2,lw=0) 120 | rect_right = patches.Rectangle((box["right"],box["bottom"]-w),width=w,height=box["top"]-box["bottom"]+2*w,color="black",zorder=2,lw=0) 121 | rect_top = patches.Rectangle((box["left"]-w,box["top"]),width=box["right"]-box["left"]+2*w,height=w,color="black",zorder=2,lw=0) 122 | rect_bottom = patches.Rectangle((box["left"]-w,box["bottom"]-w),width=box["right"]-box["left"]+2*w,height=w,color="black",zorder=2,lw=0) 123 | coll = PatchCollection([rect_left,rect_top,rect_right,rect_bottom],match_original=True,zorder=5) 124 | box["ax"].add_collection(coll) 125 | #box["ax"].add_patch(rect_left) 126 | 127 | def draw_highlights(box,regions,highlights,hmargin): 128 | boxes = split_box(box,regions,hmargin) 129 | for highlight in highlights: 130 | for i in range(len(regions)): 131 | box = boxes[i] 132 | region = regions[i][0] 133 | if highlight.end>=region.start and highlight.start<=region.end: 134 | if region.orientation=="+": 135 | left = box["left"] + (highlight.start-region.start) / (region.end-region.start) * (box["right"]-box["left"]) 136 | right = box["left"] + (highlight.end-region.start) / (region.end-region.start) * (box["right"]-box["left"]) 137 | else: 138 | left = box["right"] - (highlight.start-region.start) / (region.end-region.start) * (box["right"]-box["left"]) 139 | right = box["right"] - (highlight.end-region.start) / (region.end-region.start) * (box["right"]-box["left"]) 140 | vertices = [(left,box["top"]),(right,box["top"]) , (right,box["bottom"]) , (left,box["bottom"])] 141 | if box["left"] < box["right"]: 142 | vertices = [(max(box["left"],min(box["right"],x)),y) for (x,y) in vertices] 143 | else: 144 | vertices = [(min(box["left"],max(box["right"],x)),y) for (x,y) in vertices] 145 | if "projection" in box and box["projection"]=="polar": 146 | vertices = interpolate_polar_vertices(vertices) 147 | box["ax"].add_patch(patches.Polygon(vertices,color=highlight.color,alpha=highlight.alpha,lw=0,zorder=100)) 148 | 149 | 150 | 151 | def interpolate_polar_vertices(vertices): 152 | new_vertices = [vertices[0]] 153 | for i in range(len(vertices)): 154 | theta1,r1 = vertices[i] 155 | if i+10: 161 | x_list = np.linspace(theta1,theta2,n_points+2) 162 | y_list = np.linspace(r1,r2,n_points+2) 163 | new_vertices = new_vertices + list(zip(x_list,y_list))[1:-1] 164 | if i+12*np.pi: angle_text-=2*np.pi 170 | while angle_text<0: angle_text+=2*np.pi 171 | angle_text_deg = angle_text *180 / np.pi # in [0 , +360] 172 | if angle_text_deg>180: 173 | rotation_text = angle_text_deg+90 174 | valign="top" 175 | else: 176 | rotation_text = angle_text_deg-90 177 | valign="bottom" 178 | 179 | if angle_text_deg<45 or angle_text_deg>315 or (angle_text_deg>135 and angle_text_deg < 225): 180 | valign="center" 181 | if angle_text_deg <90 or angle_text_deg>270: 182 | halign="left" 183 | else: 184 | halign="right" 185 | return rotation_text,halign,valign 186 | 187 | def polar2cartesian(vertex): 188 | theta,r = vertex 189 | return ( r*np.cos(theta) , r*np.sin(theta) ) 190 | 191 | def cartesian2polar(vertex): 192 | x,y = vertex 193 | r = np.sqrt(x*x + y*y) 194 | theta = np.arctan(y/x) 195 | if x<0: theta+=np.pi 196 | if theta=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.setuptools] 6 | packages = ["figeno", "figeno.data", "figeno.cli", "figeno.gui"] 7 | 8 | 9 | 10 | [project] 11 | name = 'figeno' 12 | version = "1.7.0" 13 | description = 'Package for generating genomics figures.' 14 | readme = 'README.md' 15 | authors = [ 16 | { name = 'Etienne Sollier'}, 17 | ] 18 | classifiers = [ 19 | "Programming Language :: Python :: 3", 20 | "Operating System :: OS Independent", 21 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 22 | "Intended Audience :: Science/Research", 23 | "Topic :: Scientific/Engineering :: Bio-Informatics" 24 | ] 25 | requires-python = '>=3.7' 26 | dependencies = [ 27 | "numpy>=1.10.0,<2.0", 28 | "pandas>=1.3.0", 29 | "matplotlib>=3.4.3", 30 | "importlib-resources>=5.0.0", 31 | "pysam>=0.16.0", 32 | "pyBigWig>=0.3.18", 33 | "vcfpy>=0.13.5", 34 | "cooler>=0.9.1", 35 | "Flask>=2.2.5", 36 | "filedialpy>=1.2.0", 37 | "pywebview>=4.4.1" 38 | ] 39 | 40 | [project.scripts] 41 | figeno = "figeno.cli.cli:main" 42 | 43 | [project.urls] 44 | "Homepage" = "https://github.com/CompEpigen/figeno" 45 | -------------------------------------------------------------------------------- /test_data/GDM1_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "layout": "horizontal", 4 | "reference": "hg19" 5 | }, 6 | "output": { 7 | "file": "GDM1_figure.svg", 8 | "dpi": 800, 9 | "width": 85 10 | }, 11 | "regions": [ 12 | { 13 | "chr": "7", 14 | "start": 156795000, 15 | "end": 156807000 16 | } 17 | ], 18 | "highlights": [], 19 | "tracks": [ 20 | { 21 | "type": "alignments", 22 | "height": 35, 23 | "margin_above": 1.5, 24 | "bounding_box": false, 25 | "fontscale": 0.9, 26 | "file": "GDM1_subset.bam", 27 | "label": "", 28 | "label_rotate": false, 29 | "hgap_bp": 30, 30 | "vgap_frac": 0.1, 31 | "read_color": "#cccccc", 32 | "splitread_color": "#999999", 33 | "link_splitreads": false, 34 | "min_splitreads_breakpoints": 2, 35 | "group_by": "haplotype", 36 | "show_unphased": false, 37 | "exchange_haplotypes": false, 38 | "show_haplotype_colors": true, 39 | "haplotype_colors": [ 40 | "#27ae60", 41 | "#e67e22", 42 | "#808080" 43 | ], 44 | "haplotype_labels": [ 45 | "WT", 46 | "Rearranged", 47 | "Unphased" 48 | ], 49 | "color_by": "basemod", 50 | "color_unmodified": "#0f57e5", 51 | "basemods": [ 52 | [ 53 | "C", 54 | "m", 55 | "#f40202" 56 | ] 57 | ], 58 | "fix_hardclip_basemod": true 59 | }, 60 | { 61 | "type": "basemod_freq", 62 | "height": 15, 63 | "margin_above": 2.5, 64 | "bounding_box": true, 65 | "fontscale": 0.9, 66 | "label": "Methylation freq", 67 | "label_rotate": true, 68 | "bams": [ 69 | { 70 | "file": "GDM1_subset.bam", 71 | "base": "C", 72 | "mod": "m", 73 | "min_coverage": 6, 74 | "linewidth": "2", 75 | "opacity": 1, 76 | "fix_hardclip": false, 77 | "split_by_haplotype": true, 78 | "colors": [ 79 | "#27ae60", 80 | "#e67e22" 81 | ] 82 | } 83 | ] 84 | }, 85 | { 86 | "type": "genes", 87 | "height": 7, 88 | "margin_above": 1.5, 89 | "bounding_box": false, 90 | "fontscale": 1, 91 | "label": "", 92 | "label_rotate": false, 93 | "style": "default", 94 | "collapsed": true, 95 | "only_protein_coding": true, 96 | "exon_color": "#2980b9", 97 | "genes": "auto" 98 | }, 99 | { 100 | "type": "chr_axis", 101 | "height": 8, 102 | "margin_above": 1.5, 103 | "bounding_box": false, 104 | "fontscale": 1, 105 | "label": "", 106 | "label_rotate": false, 107 | "style": "default", 108 | "unit": "kb", 109 | "ticklabels_pos": "below", 110 | "ticks_interval": "auto" 111 | } 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /test_data/GDM1_splitread_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "layout": "horizontal", 4 | "reference": "hg19" 5 | }, 6 | "output": { 7 | "dpi": 1400, 8 | "file": "GDM1_splitread_figure.svg", 9 | "width": 120 10 | }, 11 | "regions": [ 12 | { 13 | "chr": "7", 14 | "start": 156799000, 15 | "end": 156813000, 16 | "color": "#f4a460" 17 | }, 18 | { 19 | "chr": "6", 20 | "start": 135502000, 21 | "end": 135508000, 22 | "color": "#95CDCD" 23 | } 24 | ], 25 | "highlights": [], 26 | "tracks": [ 27 | { 28 | "type": "alignments", 29 | "height": 60, 30 | "margin_above": 1.5, 31 | "bounding_box": true, 32 | "fontscale": 0.9, 33 | "file": "GDM1_subset.bam", 34 | "label": "", 35 | "label_rotate": false, 36 | "hgap_bp": 30, 37 | "vgap_frac": "0.5", 38 | "read_color": "#cccccc", 39 | "splitread_color": "#999999", 40 | "link_splitreads": true, 41 | "only_show_splitreads": false, 42 | "min_splitreads_breakpoints": 2, 43 | "group_by": "none", 44 | "show_unphased": false, 45 | "exchange_haplotypes": false, 46 | "show_haplotype_colors": true, 47 | "haplotype_colors": [ 48 | "#27ae60", 49 | "#e67e22", 50 | "#808080" 51 | ], 52 | "haplotype_labels": [ 53 | "WT", 54 | "Rearramged", 55 | "Unphased" 56 | ], 57 | "color_by": "basemod", 58 | "color_unmodified": "#0f57e5", 59 | "basemods": [ 60 | [ 61 | "C", 62 | "m", 63 | "#f40202" 64 | ] 65 | ], 66 | "fix_hardclip_basemod": true 67 | }, 68 | { 69 | "type": "genes", 70 | "height": 7, 71 | "margin_above": 1.5, 72 | "bounding_box": false, 73 | "fontscale": 0.8, 74 | "label": "", 75 | "label_rotate": false, 76 | "style": "default", 77 | "collapsed": true, 78 | "only_protein_coding": true, 79 | "exon_color": "#2980b9", 80 | "genes": "auto" 81 | }, 82 | { 83 | "type": "chr_axis", 84 | "height": 8, 85 | "margin_above": 1.5, 86 | "bounding_box": false, 87 | "fontscale": 0.7, 88 | "label": "", 89 | "label_rotate": false, 90 | "style": "arrow", 91 | "unit": "kb", 92 | "ticklabels_pos": "below", 93 | "ticks_interval": "auto" 94 | } 95 | ] 96 | } -------------------------------------------------------------------------------- /test_data/GDM1_subset.bam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/test_data/GDM1_subset.bam -------------------------------------------------------------------------------- /test_data/GDM1_subset.bam.bai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/test_data/GDM1_subset.bam.bai -------------------------------------------------------------------------------- /test_data/LNCaP_ENCFF282KWR_subset.bigwig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/test_data/LNCaP_ENCFF282KWR_subset.bigwig -------------------------------------------------------------------------------- /test_data/LNCaP_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "layout": "horizontal", 4 | "reference": "hg38" 5 | }, 6 | "output": { 7 | "dpi": 400, 8 | "file": "LNCaP_figure.svg", 9 | "width": 80 10 | }, 11 | "regions": [ 12 | { 13 | "chr": "7", 14 | "start": 13100000, 15 | "end": 14150000, 16 | "color": "#f4a460" 17 | }, 18 | { 19 | "chr": "14", 20 | "start": 37510000, 21 | "end": 36600000, 22 | "color": "#95CDCD" 23 | } 24 | ], 25 | "highlights": [], 26 | "tracks": [ 27 | { 28 | "type": "hic", 29 | "height": 25, 30 | "margin_above": 1.5, 31 | "bounding_box": true, 32 | "fontscale": 1, 33 | "label": "HiC ", 34 | "label_rotate": false, 35 | "file": "LNCaP_subset_hg38.cool", 36 | "color_map": "red", 37 | "pixel_border": false, 38 | "upside_down": false, 39 | "max_dist": "1300", 40 | "extend": true, 41 | "scale": "auto", 42 | "scale_max_percentile": 90, 43 | "show_colorbar": false, 44 | "interactions_across_regions": true, 45 | "double_interactions_across_regions": true 46 | }, 47 | { 48 | "type": "bigwig", 49 | "height": 10, 50 | "margin_above": 1.5, 51 | "bounding_box": false, 52 | "fontscale": 1, 53 | "label": "DNase-seq", 54 | "label_rotate": false, 55 | "file": "LNCaP_ENCFF282KWR_subset.bigwig", 56 | "color": "#2980b9", 57 | "n_bins": 500, 58 | "scale": "auto", 59 | "scale_pos": "corner" 60 | }, 61 | { 62 | "type": "genes", 63 | "height": 11, 64 | "margin_above": 1.5, 65 | "bounding_box": false, 66 | "fontscale": 1, 67 | "label": "", 68 | "label_rotate": false, 69 | "style": "default", 70 | "collapsed": true, 71 | "only_protein_coding": true, 72 | "exon_color": "#2980b9", 73 | "genes": "auto" 74 | }, 75 | { 76 | "type": "chr_axis", 77 | "height": 10, 78 | "margin_above": 1.5, 79 | "bounding_box": false, 80 | "fontscale": 0.8, 81 | "label": "", 82 | "label_rotate": false, 83 | "style": "arrow", 84 | "unit": "Mb", 85 | "ticklabels_pos": "below", 86 | "ticks_interval": "auto" 87 | } 88 | ] 89 | } -------------------------------------------------------------------------------- /test_data/LNCaP_subset_hg38.cool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/test_data/LNCaP_subset_hg38.cool -------------------------------------------------------------------------------- /test_data/README.md: -------------------------------------------------------------------------------- 1 | This directory contains data to test figeno. There are 5 _config.json files which can be used to generate example figures, using data contained in this directory. Please note that the input files were subsetted to only contain regions of interest (in order to save space), and that relative paths were used in the config files, so in order to generate the figures you either have to start figeno from this directory, or change the paths to absolute paths. In order to generate the figures, you can either 2 | - run `figeno make GDM1_config.json` (or any other of the config files) while in this directory. 3 | - run `figeno gui` from this directory, click "Load config", select the config file that you want, and click "Generate figure". 4 | 5 | ## GDM1 (nanopore) 6 | GDM1_subset.bam contains nanopore reads for the leukemic GDM-1 cell line, subsetted to regions around the t(6;7) breakpoint (6:135,505,353 to 7:156,812,311; hg19). The full data can be downloaded from the SRA (accession SRR28257102). GDM1_config.json will show allele-specific DNA methylation at the MNX1 locus, and GDM1_splitread_config.json will show both sides of the breakpoints, with lines connecting different alignments coming from the same splitread. 7 | 8 | ## LNCaP (HiC) 9 | HiC data for the LNCaP cell line, subsetted around the t(7;14). This data is from the [NeoLoopFinder manuscript (Wang et al. 2021, Nature Methods)](https://www.nature.com/articles/s41592-021-01164-w), and the full data can be downloaded from GEO accession [GSE161493](http://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE161493). LNCaP_config.json will display HiC data around the breakpoint, as well as a DNase-seq bigwig track. 10 | 11 | ## THP1 (WGS) 12 | Processed whole genome sequencing data from the THP-1 cell line (WGS data from the cancer cell line encyclopedia (SRA accession SRR8670675). THP1_SV.vcf contains structural variants in vcf format, THP1_ratio.txt and THP1_CNVs contain CNA information produced by Control-FREEC. THP1_circos_config.json will generate a circos plot with all chromosomes, while THP1_symmetrical_config.json will produce a symmetrical plot with a few chromosomes. 13 | -------------------------------------------------------------------------------- /test_data/THP1_CNVs: -------------------------------------------------------------------------------- 1 | 1 39990000 40330000 10 gain 2 | 1 40330000 41360000 30 gain 3 | 1 41360000 43640000 9 gain 4 | 1 43640000 43780000 3 gain 5 | 1 43780000 44270000 9 gain 6 | 1 44270000 53860000 3 gain 7 | 1 53860000 54880000 4 gain 8 | 1 91000000 217140000 3 gain 9 | 1 217140000 217250000 5 gain 10 | 1 217250000 248110000 4 gain 11 | 1 248530000 249250621 4 gain 12 | 2 13770000 13800000 1 loss 13 | 2 37950000 38000000 1 loss 14 | 2 95420000 95570000 1 loss 15 | 2 133040000 133100000 0 loss 16 | 3 1140000 1240000 1 loss 17 | 3 75270000 76020000 1 loss 18 | 3 90190000 93660000 1 loss 19 | 4 3580000 3640000 1 loss 20 | 4 4180000 4230000 1 loss 21 | 4 22590000 22620000 1 loss 22 | 4 33820000 33870000 1 loss 23 | 4 181990000 182010000 1 loss 24 | 4 182010000 182040000 0 loss 25 | 4 182040000 182090000 1 loss 26 | 4 182090000 182400000 0 loss 27 | 4 182400000 182550000 1 loss 28 | 4 188080000 188240000 1 loss 29 | 4 190470000 190680000 0 loss 30 | 5 5470000 5680000 1 loss 31 | 5 21530000 21570000 0 loss 32 | 5 58400000 58420000 1 loss 33 | 5 106950000 106990000 1 loss 34 | 6 0 31500000 1 loss 35 | 6 31500000 57200000 3 gain 36 | 6 57200000 57610000 1 loss 37 | 6 57610000 125530000 3 gain 38 | 6 125530000 125590000 4 gain 39 | 6 125590000 171115067 3 gain 40 | 7 110800000 110930000 1 loss 41 | 8 0 3690000 3 gain 42 | 8 3690000 5940000 4 gain 43 | 8 5940000 12550000 3 gain 44 | 8 12570000 146364022 3 gain 45 | 9 0 12730000 3 gain 46 | 9 12730000 15460000 4 gain 47 | 9 15460000 20370000 3 gain 48 | 9 21270000 22250000 0 loss 49 | 9 22730000 23800000 3 gain 50 | 9 33120000 33480000 3 gain 51 | 9 33800000 34180000 3 gain 52 | 9 34270000 34910000 3 gain 53 | 9 37450000 38770000 3 gain 54 | 9 99870000 100040000 1 loss 55 | 9 136820000 141213431 3 gain 56 | 10 0 135534747 1 loss 57 | 11 107210000 107230000 3 gain 58 | 11 118360000 134840000 3 gain 59 | 11 134870000 135006516 3 gain 60 | 12 0 21590000 1 loss 61 | 12 21850000 29790000 3 gain 62 | 12 34480000 34540000 3 gain 63 | 12 45020000 45310000 3 gain 64 | 12 45400000 48100000 3 gain 65 | 12 48100000 51190000 1 loss 66 | 13 21870000 21940000 1 loss 67 | 13 63600000 63640000 1 loss 68 | 14 43200000 43690000 1 loss 69 | 15 20000000 20020000 1 loss 70 | 15 23630000 23700000 1 loss 71 | 16 14490000 14520000 1 loss 72 | 16 27610000 27630000 1 loss 73 | 16 33930000 33970000 1 loss 74 | 16 87950000 87970000 1 loss 75 | 17 0 22220000 1 loss 76 | 19 27830000 27860000 0 loss 77 | 20 0 490000 3 gain 78 | 20 490000 20800000 1 loss 79 | 20 20800000 21450000 3 gain 80 | 20 21450000 21890000 5 gain 81 | 20 21890000 22640000 4 gain 82 | X 0 41750000 1 loss 83 | X 41750000 44930000 0 loss 84 | X 44930000 153540000 1 loss 85 | X 153540000 153610000 0 loss 86 | X 153610000 155270560 1 loss 87 | -------------------------------------------------------------------------------- /test_data/THP1_circos_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "layout": "circular", 4 | "reference": "hg19" 5 | }, 6 | "output": { 7 | "dpi": 400, 8 | "file": "THP1_circos_figure.svg", 9 | "width": 180 10 | }, 11 | "regions": [ 12 | { 13 | "chr": "1", 14 | "start": null, 15 | "end": null, 16 | "color": "#98671F" 17 | }, 18 | { 19 | "chr": "2", 20 | "start": null, 21 | "end": null, 22 | "color": "#65661B" 23 | }, 24 | { 25 | "chr": "3", 26 | "start": null, 27 | "end": null, 28 | "color": "#969833" 29 | }, 30 | { 31 | "chr": "4", 32 | "start": null, 33 | "end": null, 34 | "color": "#CE151D" 35 | }, 36 | { 37 | "chr": "5", 38 | "start": null, 39 | "end": null, 40 | "color": "#FF1A25" 41 | }, 42 | { 43 | "chr": "6", 44 | "start": null, 45 | "end": null, 46 | "color": "#FF0BC8" 47 | }, 48 | { 49 | "chr": "7", 50 | "start": null, 51 | "end": null, 52 | "color": "#FFCBCC" 53 | }, 54 | { 55 | "chr": "8", 56 | "start": null, 57 | "end": null, 58 | "color": "#FF9931" 59 | }, 60 | { 61 | "chr": "9", 62 | "start": null, 63 | "end": null, 64 | "color": "#FFCC3A" 65 | }, 66 | { 67 | "chr": "10", 68 | "start": null, 69 | "end": null, 70 | "color": "#FCFF44" 71 | }, 72 | { 73 | "chr": "11", 74 | "start": null, 75 | "end": null, 76 | "color": "#C4FF40" 77 | }, 78 | { 79 | "chr": "12", 80 | "start": null, 81 | "end": null, 82 | "color": "#00FF3B" 83 | }, 84 | { 85 | "chr": "13", 86 | "start": null, 87 | "end": null, 88 | "color": "#2F7F1E" 89 | }, 90 | { 91 | "chr": "14", 92 | "start": null, 93 | "end": null, 94 | "color": "#2800C6" 95 | }, 96 | { 97 | "chr": "15", 98 | "start": null, 99 | "end": null, 100 | "color": "#6A96FA" 101 | }, 102 | { 103 | "chr": "16", 104 | "start": null, 105 | "end": null, 106 | "color": "#98CAFC" 107 | }, 108 | { 109 | "chr": "17", 110 | "start": null, 111 | "end": null, 112 | "color": "#00FEFD" 113 | }, 114 | { 115 | "chr": "18", 116 | "start": null, 117 | "end": null, 118 | "color": "#C9FFFE" 119 | }, 120 | { 121 | "chr": "19", 122 | "start": null, 123 | "end": null, 124 | "color": "#9D00C6" 125 | }, 126 | { 127 | "chr": "20", 128 | "start": null, 129 | "end": null, 130 | "color": "#D232FA" 131 | }, 132 | { 133 | "chr": "21", 134 | "start": null, 135 | "end": null, 136 | "color": "#956DB5" 137 | }, 138 | { 139 | "chr": "22", 140 | "start": null, 141 | "end": null, 142 | "color": "#5D5D5D" 143 | }, 144 | { 145 | "chr": "X", 146 | "start": null, 147 | "end": null, 148 | "color": "#989898" 149 | }, 150 | { 151 | "chr": "Y", 152 | "start": null, 153 | "end": null, 154 | "color": "#CBCBCB" 155 | } 156 | ], 157 | "highlights": [], 158 | "tracks": [ 159 | { 160 | "type": "sv", 161 | "height": 15, 162 | "margin_above": 1.5, 163 | "bounding_box": true, 164 | "fontscale": 1, 165 | "label": "", 166 | "label_rotate": false, 167 | "file": "THP1_SV.vcf", 168 | "lw": "0.5", 169 | "color_del": "#4a69bd", 170 | "color_dup": "#e55039", 171 | "color_t2t": "#8e44ad", 172 | "color_h2h": "#8e44ad", 173 | "color_trans": "#27ae60" 174 | }, 175 | { 176 | "type": "copynumber", 177 | "height": 30, 178 | "margin_above": 0, 179 | "bounding_box": true, 180 | "fontscale": 1, 181 | "label": "", 182 | "label_rotate": false, 183 | "freec_ratios": "THP1_ratio.txt", 184 | "freec_CNAs": "THP1_CNVs", 185 | "purple_cn": "", 186 | "genes": "", 187 | "ploidy": "2", 188 | "min_cn": "", 189 | "max_cn": "3.9", 190 | "grid": true, 191 | "grid_major": false, 192 | "grid_minor": false, 193 | "grid_cn": true, 194 | "marker_size": "0.7", 195 | "color_normal": "#000000", 196 | "color_loss": "#4a69bd", 197 | "color_gain": "#e55039", 198 | "color_cnloh": "#f6b93b" 199 | }, 200 | { 201 | "type": "chr_axis", 202 | "height": 10, 203 | "margin_above": 0, 204 | "bounding_box": false, 205 | "fontscale": 1, 206 | "label": "", 207 | "label_rotate": false, 208 | "style": "default", 209 | "unit": "kb", 210 | "ticklabels_pos": "below", 211 | "ticks_interval": "auto" 212 | } 213 | ] 214 | } 215 | -------------------------------------------------------------------------------- /test_data/THP1_symmetrical_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "layout": "symmetrical", 4 | "reference": "hg19" 5 | }, 6 | "output": { 7 | "dpi": 400, 8 | "file": "THP1_symmetrical_figure.png", 9 | "width": 180 10 | }, 11 | "regions": [ 12 | { 13 | "chr": "9", 14 | "start": null, 15 | "end": null 16 | }, 17 | { 18 | "chr": "6", 19 | "start": null, 20 | "end": null 21 | }, 22 | { 23 | "chr": "11", 24 | "start": null, 25 | "end": null 26 | }, 27 | { 28 | "chr": "13", 29 | "start": null, 30 | "end": null 31 | }, 32 | { 33 | "chr": "20", 34 | "start": null, 35 | "end": null 36 | } 37 | ], 38 | "highlights": [], 39 | "tracks": [ 40 | { 41 | "type": "sv", 42 | "height": 6, 43 | "margin_above": 1.5, 44 | "bounding_box": false, 45 | "fontscale": 1, 46 | "label": "", 47 | "label_rotate": false, 48 | "file": "THP1_SV.vcf", 49 | "lw": "0.5", 50 | "color_del": "#4a69bd", 51 | "color_dup": "#e55039", 52 | "color_t2t": "#8e44ad", 53 | "color_h2h": "#8e44ad", 54 | "color_trans": "#27ae60" 55 | }, 56 | { 57 | "type": "copynumber", 58 | "height": 25, 59 | "margin_above": 0, 60 | "bounding_box": true, 61 | "fontscale": 1, 62 | "label": "", 63 | "label_rotate": false, 64 | "freec_ratios": "THP1_ratio.txt", 65 | "freec_CNAs": "THP1_CNVs", 66 | "purple_cn": "", 67 | "genes": "", 68 | "ploidy": "2", 69 | "min_cn": "", 70 | "max_cn": "4.5", 71 | "grid": true, 72 | "grid_major": true, 73 | "grid_minor": true, 74 | "grid_cn": true, 75 | "marker_size": "0.7", 76 | "color_normal": "#000000", 77 | "color_loss": "#4a69bd", 78 | "color_gain": "#e55039", 79 | "color_cnloh": "#f6b93b" 80 | }, 81 | { 82 | "type": "chr_axis", 83 | "height": 8, 84 | "margin_above": 0, 85 | "bounding_box": false, 86 | "fontscale": 1, 87 | "label": "", 88 | "label_rotate": false, 89 | "style": "default", 90 | "unit": "Mb", 91 | "ticklabels_pos": "below", 92 | "ticks_interval": "20000000" 93 | } 94 | ] 95 | } -------------------------------------------------------------------------------- /test_data/THP1_symmetrical_figure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/test_data/THP1_symmetrical_figure.png -------------------------------------------------------------------------------- /test_data/delly.cov.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompEpigen/figeno/8f05d0cd6d1067ef7ba78458a6bef5887e80ce30/test_data/delly.cov.gz -------------------------------------------------------------------------------- /test_data/purple_cn.tsv: -------------------------------------------------------------------------------- 1 | chromosome start end copyNumber bafCount observedBAF baf segmentStartSupport segmentEndSupport method depthWindowCount gcContent minStart maxStart minorAlleleCopyNumber majorAlleleCopyNumber 2 | 1 1 153777000 2.004777535127848 23524 0.5447 0.5053 TELOMERE CENTROMERE BAF_WEIGHTED 91204 0.418 1 1 0.9891 1.0102 3 | 1 153777001 154311911 0.9928 2 0.8939 1.0 NONE BND BAF_WEIGHTED 402 0.4524 153777001 153777001 0.0 0.9928 4 | 1 154311912 154922425 2.0376 117 0.5286 0.5092 BND BND BAF_WEIGHTED 439 0.4738 154311912 154311912 1.0 1.0376 5 | 1 154922426 155144597 1.01 0 0.0 0.9983 BND BND BAF_WEIGHTED 115 0.5289 154922426 154922426 0.0017 1.0083 6 | 1 155144598 249250621 1.9973 20364 0.5448 0.5048 BND TELOMERE BAF_WEIGHTED 74743 0.4029 155144598 155144598 0.989 1.0083 7 | 2 1 243199373 1.9964327509129096 5219 0.5445 0.5041 TELOMERE NONE BAF_WEIGHTED 18193 0.4171 1 1 0.9873 1.0037 8 | 3 1 168873213 1.9963148973343374 10115 0.5448 0.5063 TELOMERE NONE BAF_WEIGHTED 37051 0.4025 1 1 0.9866 1.0119 9 | 3 168873214 168873651 0.9579 0 0.0 1.0 DUP BND STRUCTURAL_VARIANT 0 0.0 168873214 168873214 0.0 0.9579 10 | 3 168873652 198022430 1.9922 6384 0.5445 0.5058 BND TELOMERE BAF_WEIGHTED 23782 0.3972 168873652 168873652 0.9845 1.0078 11 | 4 1 191154276 1.9858871178909658 11296 0.5451 0.5057 TELOMERE CENTROMERE BAF_WEIGHTED 38222 0.3941 1 1 0.9794 1.0019 12 | 5 1 17607306 1.9787 4681 0.544 0.5077 TELOMERE DEL BAF_WEIGHTED 13071 0.4165 1 1 0.9741 1.0047 13 | 5 17607307 17652748 0.9466 0 0.0 1.0 DEL DEL BAF_WEIGHTED 21 0.4033 17607307 17607307 0.0 0.9466 14 | 5 17652749 87828672 1.9899638982495513 6493 0.5464 0.5065 DEL NONE BAF_WEIGHTED 18401 0.3692 17652749 17652749 0.9769 1.0027 15 | 5 87828673 164677870 0.9792 1904 0.9077 0.9941 DEL DEL BAF_WEIGHTED 63945 0.388 87828673 87828673 0.0058 0.9734 16 | 5 164677871 180915260 1.9992516525995436 3012 0.5461 0.5082 DEL NONE BAF_WEIGHTED 10334 0.4368 164677871 164677871 0.9809 1.0136 17 | 6 1 171115067 1.993373414975961 1856 0.5455 0.5032 TELOMERE NONE BAF_WEIGHTED 6310 0.4311 1 1 0.9998 1.0128 18 | 7 1 59554330 0.9821 2018 0.9089 0.9979 TELOMERE CENTROMERE BAF_WEIGHTED 43614 0.4033 1 1 0.0021 0.9801 19 | 7 59554331 62752500 1.9718 626 0.545 0.5142 CENTROMERE NONE BAF_WEIGHTED 202 0.4037 59554331 59554331 0.9579 1.0139 20 | 7 62752501 159138663 0.9785 3112 0.9065 0.9992 NONE TELOMERE BAF_WEIGHTED 71829 0.3999 62678001 62827001 0.0007 0.9778 21 | 8 1 146364022 1.9830511601621765 13791 0.5457 0.5076 TELOMERE CENTROMERE BAF_WEIGHTED 32878 0.4107 1 1 0.9748 1.0048 22 | 9 1 141213431 1.9951547120224664 10012 0.5456 0.5046 TELOMERE CENTROMERE BAF_WEIGHTED 31906 0.3861 1 1 0.9856 1.0038 23 | 10 1 135534747 1.98828874170596 9941 0.5465 0.5053 TELOMERE CENTROMERE BAF_WEIGHTED 30427 0.4096 1 1 0.9784 0.9995 24 | 11 1 62560403 2.0003800714431534 14136 0.5439 0.5066 TELOMERE CENTROMERE BAF_WEIGHTED 37513 0.4034 1 1 0.9858 1.0122 25 | 11 62560404 62562278 0.8547 0 0.0 1.0 BND BND BAF_WEIGHTED 1 0.486 62560404 62560404 0.0 0.8547 26 | 11 62562279 62672672 2.0093661666237126 0 0.0 0.6279 BND INF BAF_WEIGHTED 62 0.4846 62562279 62562279 0.7529 1.2706 27 | 11 62672673 63593000 2.0225 175 0.5472 0.5055 BND NONE BAF_WEIGHTED 597 0.433 62672673 62672673 1.0 1.0225 28 | 11 63593001 65005525 1.0221 48 0.9143 0.9569 NONE DUP BAF_WEIGHTED 755 0.5337 63593001 63593001 0.0441 0.978 29 | 11 65005526 119909663 1.9968 11048 0.5449 0.506 DUP BND BAF_WEIGHTED 41971 0.4071 65005526 65005526 0.9865 1.0103 30 | 11 119909664 121534512 0.9818 75 0.9091 1.0 BND BND BAF_WEIGHTED 1096 0.446 119909664 119909664 0.0 0.9818 31 | 11 121534513 122654195 2.024079083858986 173 0.5455 0.5041 BND DUP BAF_WEIGHTED 630 0.3937 121534513 121534513 1.0 1.0163 32 | 11 122654196 123434481 0.9865 34 0.9077 1.0 BND BND BAF_WEIGHTED 676 0.4373 122654196 122654196 0.0 0.9865 33 | 11 123434482 135006516 1.9936 2678 0.5453 0.5038 BND TELOMERE BAF_WEIGHTED 8860 0.4286 123434482 123434482 0.9893 1.0044 34 | 12 1 133851895 1.9940703995026803 9166 0.5455 0.5043 TELOMERE CENTROMERE BAF_WEIGHTED 26933 0.3995 1 1 0.9892 1.0065 35 | 13 1 115169878 1.9763000000000002 0 0.0 0.5062 TELOMERE CENTROMERE LONG_ARM 0 0.0 1 1 0.9759 1.0004 36 | 14 1 101057409 1.9966371054985392 0 0.0 0.5054 TELOMERE CENTROMERE LONG_ARM 0 0.0 1 1 1.0002 1.022 37 | 14 101057410 101176974 0.9805 0 0.0 1.0 BND DEL BAF_WEIGHTED 71 0.5314 101057410 101057410 0.0 0.9805 38 | 14 101176975 107349540 2.025275287291795 92 0.546 0.51 DEL BND BAF_WEIGHTED 220 0.4664 101176975 101176975 0.9608 1.0 39 | 15 1 102531392 2.0082 0 0.0 0.5062 TELOMERE CENTROMERE LONG_ARM 0 0.0 1 1 0.9917 1.0165 40 | 16 1 34462500 1.9816656858304353 7900 0.5459 0.5098 TELOMERE INV BAF_WEIGHTED 20980 0.4566 1 1 0.9715 1.0102 41 | 16 34462501 34757736 3.0276 17 0.6423 0.6472 NONE INV BAF_WEIGHTED 192 0.4033 34459001 34466001 1.068 1.9596 42 | 16 34757737 90354753 1.988362686286305 34 0.5528 0.5061 INV CENTROMERE BAF_WEIGHTED 280 0.4062 34757737 34757737 0.9765 1.0006 43 | 17 1 3662942 2.012 1146 0.5441 0.5043 TELOMERE BND BAF_WEIGHTED 2192 0.4934 1 1 0.9974 1.0147 44 | 17 3662943 3813000 1.0075 3 0.898 0.9387 BND NONE BAF_WEIGHTED 99 0.5056 3662943 3662943 0.0618 0.9457 45 | 17 3813001 4026324 2.0562 83 0.54 0.5133 NONE BND BAF_WEIGHTED 138 0.4881 3813001 3813001 1.0008 1.0554 46 | 17 4026325 4367087 0.9941 14 0.9117 1.0 BND BND BAF_WEIGHTED 240 0.4575 4026325 4026325 0.0 0.9941 47 | 17 4367088 4770767 2.0298265191699465 42 0.5507 0.5061 BND BND BAF_WEIGHTED 135 0.5253 4367088 4367088 1.0006 1.0254 48 | 17 4770768 6725898 0.9795 85 0.9143 1.0 DEL BND BAF_WEIGHTED 1373 0.4682 4770768 4770768 0.0 0.9795 49 | 17 6725899 6820000 2.0424 43 0.5362 0.508 BND NONE BAF_WEIGHTED 61 0.4239 6725899 6725899 1.0048 1.0376 50 | 17 6820001 6962507 0.9865 0 0.0 1.0 NONE INF BAF_WEIGHTED 98 0.4674 6820001 6820001 0.0 0.9865 51 | 17 6962509 18181852 1.996601328345304 0 0.0 0.5019 BND BND BAF_WEIGHTED 24 0.4802 6962509 6962509 1.013 1.0206 52 | 17 18181853 18184324 1.0198 0 0.0 0.9939 INV BND BAF_WEIGHTED 1 0.4925 18181853 18181853 0.0063 1.0135 53 | 17 18184325 38458345 2.019877900903615 610 0.5522 0.5151 BND CENTROMERE BAF_WEIGHTED 1689 0.4526 18184325 18184325 0.975 1.0356 54 | 17 38458346 38522613 0.9762 0 0.0 1.0 INV INV BAF_WEIGHTED 15 0.5591 38458346 38458346 0.0 0.9762 55 | 17 38522614 39810640 2.0189 340 0.5429 0.5039 INV INV BAF_WEIGHTED 921 0.4388 38522614 38522614 1.0016 1.0174 56 | 17 39810641 39835874 1.0303 0 0.0 0.9601 INV INV BAF_WEIGHTED 18 0.5297 39810641 39810641 0.0411 0.9892 57 | 17 39835875 39848950 2.0207 0 0.0 0.5105 INV INV BAF_WEIGHTED 10 0.5 39835875 39835875 0.9892 1.0315 58 | 17 39848951 39850089 1.0631 0 0.0 0.9305 INV DEL BAF_WEIGHTED 1 0.477 39848951 39848951 0.0739 0.9892 59 | 17 39850090 81195210 2.0103 8207 0.5432 0.508 DEL TELOMERE BAF_WEIGHTED 28459 0.448 39850090 39850090 0.9892 1.0212 60 | 18 1 78077248 1.9869461083245687 4257 0.5472 0.5049 TELOMERE CENTROMERE BAF_WEIGHTED 11103 0.4091 1 1 0.9879 1.0075 61 | 19 1 59128983 1.9964796295407155 5694 0.5446 0.5049 TELOMERE CENTROMERE BAF_WEIGHTED 14951 0.4933 1 1 0.99 1.0096 62 | 20 1 44155849 2.0018096505618375 6057 0.5457 0.5045 TELOMERE CENTROMERE BAF_WEIGHTED 20560 0.416 1 1 0.9889 1.007 63 | 20 44155850 44173035 0.9666 0 0.0 1.0 BND BND BAF_WEIGHTED 16 0.4244 44155850 44155850 0.0 0.9666 64 | 20 44173036 44438748 1.9836 3 0.7145 0.7261 BND BND BAF_WEIGHTED 202 0.4124 44173036 44173036 0.5434 1.4402 65 | 20 44438749 46221082 0.9853 27 0.9184 1.0 BND BND BAF_WEIGHTED 1403 0.466 44438749 44438749 0.0 0.9853 66 | 20 46221083 46518880 1.9895 60 0.5334 0.503 BND INV BAF_WEIGHTED 215 0.4744 46221083 46221083 0.9887 1.0008 67 | 20 46518881 46548338 0.8979 4 0.9161 1.0 INV BND BAF_WEIGHTED 6 0.47 46518881 46518881 0.0 0.8979 68 | 20 46548339 46571645 2.0461 28 0.5255 0.5113 BND BND BAF_WEIGHTED 15 0.4357 46548339 46548339 1.0 1.0461 69 | 20 46571646 47178279 0.9547 18 0.9104 1.0 BND BND BAF_WEIGHTED 424 0.47 46571646 46571646 0.0 0.9547 70 | 20 47178280 47247287 2.0583 28 0.5336 0.5088 BND BND BAF_WEIGHTED 42 0.5058 47178280 47178280 1.011 1.0473 71 | 20 47247288 47509320 1.0022 4 0.8956 0.9363 BND BND BAF_WEIGHTED 116 0.4875 47247288 47247288 0.0638 0.9384 72 | 20 47509321 47859014 2.0504 153 0.5429 0.513 BND BND BAF_WEIGHTED 273 0.4405 47509321 47509321 0.9986 1.0518 73 | 20 47859015 47860505 1.1808 0 0.0 0.8907 BND BND STRUCTURAL_VARIANT 0 0.0 47859015 47859015 0.1291 1.0518 74 | 20 47860506 63025520 1.99563002501774 4 0.515 0.5291 BND BND BAF_WEIGHTED 18 0.4538 47860506 47860506 1.0 1.1235 75 | 21 1 48129895 1.9944 0 0.0 0.5068 TELOMERE CENTROMERE LONG_ARM 0 0.099 1 1 0.9836 1.0108 76 | 22 1 24116413 2.0113 0 0.0 0.5063 TELOMERE CENTROMERE LONG_ARM 0 0.0 1 1 0.993 1.0182 77 | 22 24116414 24201884 3.1588 1 0.6832 0.6893 DUP DUP BAF_WEIGHTED 49 0.5309 24116414 24116414 0.9814 2.1774 78 | 22 24201885 51304566 2.0026 7434 0.5429 0.5078 DUP TELOMERE BAF_WEIGHTED 17954 0.4762 24201885 24201885 0.9858 1.0168 79 | X 1 155270560 1.9489843628706482 6341 0.5467 0.5125 TELOMERE CENTROMERE BAF_WEIGHTED 49706 0.3978 1 1 0.9511 0.9997 80 | -------------------------------------------------------------------------------- /tests/test_figeno.py: -------------------------------------------------------------------------------- 1 | import os 2 | from figeno import figeno_make 3 | import matplotlib.testing.compare 4 | 5 | os.chdir("test_data") 6 | def test_nanopore(): 7 | figeno_make(config_file="GDM1_config.json",config={"output":{"file":"GDM1_figure2.svg"}}) 8 | assert matplotlib.testing.compare.compare_images("GDM1_figure.svg","GDM1_figure2.svg",tol=0.1) is None 9 | 10 | def test_nanopore_splitreads(): 11 | figeno_make(config_file="GDM1_splitread_config.json",config={"output":{"file":"GDM1_splitread_figure2.svg"}}) 12 | assert matplotlib.testing.compare.compare_images("GDM1_splitread_figure.svg","GDM1_splitread_figure2.svg",tol=0.1) is None 13 | 14 | def test_HiC(): 15 | figeno_make(config_file="LNCaP_config.json",config={"output":{"file":"LNCaP_figure2.svg"}}) 16 | assert matplotlib.testing.compare.compare_images("LNCaP_figure.svg","LNCaP_figure2.svg",tol=0.1) is None 17 | 18 | def test_wgs_circos(): 19 | figeno_make(config_file="THP1_circos_config.json",config={"output":{"file":"THP1_circos_figure2.svg"}}) 20 | assert matplotlib.testing.compare.compare_images("THP1_circos_figure.svg","THP1_circos_figure2.svg",tol=0.1) is None 21 | 22 | def test_wgs_symmetrical(): 23 | figeno_make(config_file="THP1_symmetrical_config.json",config={"output":{"file":"THP1_symmetrical_figure2.png"}}) 24 | assert matplotlib.testing.compare.compare_images("THP1_symmetrical_figure.png","THP1_symmetrical_figure2.png",tol=0.1) is None 25 | 26 | --------------------------------------------------------------------------------