diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9121d91 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,56 @@ +name: Auto CI Test On Github Actions + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 1 + matrix: + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install codecov pylint autopep8 + python -m pip install -r requirements.txt + + - name: Pre Format Code Style With Autopep8 + run: | + python -m autopep8 -v -i -r ./ + + - name: Check Code Style With Pylint + run: | + #python -m pylint -v ./*.py + #python -m pylint -v ./src/*.py + #python -m pylint -v ./test/*.py + + - name: Test With Demo Files + run: | + python setup.py install + cd test + python ./test.py + + - name: Test Code Covrage + run: | + export CODECOV_TOKEN="e00c5f99-4f96-4cae-9bbc-d77668302050" + cd test + coverage run ./test.py + codecov + + - name: Auto Deploy .whl File to PyPi + run: | + echo -e "[ INFO ] This Funcation Under Developing will coming soon" + + - name: Auto Freeze a Github Release + run: | + echo -e "[ INFO ] This Funcation Under Developing will coming soon" + diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 6ac06f9..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include install.sh -include RELEASE diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f4cb690 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +.PHONY: hlep pre lint install test clean +.DEFAULT: help +help: + @echo "----------------------------------------" + @echo "PyCircos" + @echo "Design For NGS Circos Plot Within Python" + @echo "----------------------------------------" + @echo "make help" + @echo "\tprint this help info to screen" + @echo "make pre" + @echo "\tinstall needed env, use only once" + @echo "make lint" + @echo "\trun pylint to format all file" + @echo "make install" + @echo "\tinstall pycircos" + @echo "make test" + @echo "\trun a test demo" + @echo "make clean" + @echo "\tclean temp cache and unusing file" +pre: + python3 -m pip install -r ./requirements.txt +lint: + python3 -m pip install pylint + python3 -m pylint +install:pre + python3 ./setup.py install +test: + python3 ./test/test.py +clean: + @rm -rf ./build ./dist ./pycircos.egg-info + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..559ead4 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Python Modules for Circos Plot + +[![Build Status](https://travis-ci.com/KimBioInfoStudio/PyCircos.svg?branch=develop)](https://travis-ci.com/KimBioInfoStudio/PyCircos) +![codecov](https://codecov.io/gh/KimBioInfoStudio/PyCircos/branch/develop/graph/badge.svg) + + + +## 1. Pre Installation + +> As official, Pyhton2 will end life form 2020-1-1, So we develop a new version support Python3.5+, We deeply recommand all users using the python version 3.5+, Thx! + +### Python 3.5+ packages(automatically installed) + +1. numpy +2. pandas +3. matplotlib + + +## 2. Installation +### intall dev branch + +1. install from source code + +``` +git clone https://github.com/KimBioInfoStudio/PyCircos.git +cd PyCircos +pip install -r requirements.txt +python3 setup.py install [--user] +``` + +2. or install form source code with using 'make' + +``` +git clone https://github.com/KimBioInfoStudio/PyCircos.git +cd PyCircos +make install +``` + +3. install release branch + +``` +pip install pycircos +``` + +## 3. Examples +### command + +``` +cd ./test/ +python3 test.py +open ./demo.png +``` +### results + +![](./test/demo.png) + + + diff --git a/README.rst b/README.rst deleted file mode 100644 index 4a33a23..0000000 --- a/README.rst +++ /dev/null @@ -1,29 +0,0 @@ -Python Modules for Circos Plot -=================================== - -1. Prerequisition -------------------- -- Python 2.7 packages (automatically installed) - - - numpy >= 1.13.1 - - pandas >= 0.15.2 - - matplotlib >= 2.0.2 - -2. Installation ----------------- - -:: - - > git clone https://github.com/tsznxx/PyCircos.git - > cd Pycircos - > python setup.py install --user - -3. Examples -------------- - -:: - - > cd demo - > python run_circos.py run - > display Circos.pdf - diff --git a/RELEASE b/RELEASE deleted file mode 100644 index 54c0c63..0000000 --- a/RELEASE +++ /dev/null @@ -1,3 +0,0 @@ -pycircos 1.0.0 release: - class Circos: - ... diff --git a/demo/run_circos.py b/demo/run_circos.py deleted file mode 100644 index e96e17f..0000000 --- a/demo/run_circos.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -- coding:utf-8 -- -# Last-modified: 10 Nov 2017 05:41:24 PM -# -# Module/Scripts Description -# -# Copyright (c) 2016 Yunfei Wang -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the BSD License (see the file COPYING included with -# the distribution). -# -# @status: experimental -# @version: 1.0.0 -# @author: Yunfei Wang -# @contact: yfwang0405@gmail.com - -# ------------------------------------ -# python modules -# ------------------------------------ - -import os -import sys -import pandas -import matplotlib -if not 'DISPLAY' in os.environ: - matplotlib.use('Agg') # if DISPLAY is not set -import pycircos -import matplotlib.pyplot as plt - -# ------------------------------------ -# constants -# ------------------------------------ - -# ------------------------------------ -# Misc functions -# ------------------------------------ - -# ------------------------------------ -# Classes -# ------------------------------------ - -# ------------------------------------ -# Main -# ------------------------------------ - -if __name__=="__main__": - if len(sys.argv)==1: - sys.exit("Example:"+sys.argv[0]+" run") - - CNV = pandas.read_table("scores.gistic") - CNV['chrom'] = 'chr' + CNV.Chromosome.astype(str) - CNV = CNV.sort_values(['Chromosome','Start']) - CNV.loc[CNV.Type=='Del','frequency'] *= -1 - - AMP = CNV.loc[CNV.Type=='Amp',:] - DEL = CNV.loc[CNV.Type=='Del',:] - - chromsizes = pandas.read_table("hg19.fa.sizes",index_col=0,header=None,names=['length']) - cg = pycircos.Circos(chromsizes, gap=2) - # draw cytoband - cg.draw_cytobands(8.1,0.3,"cytoBand.txt.gz") - - # draw chrom region - cg.draw_scaffold(8.1,0.3) - cg.draw_ticks(8.1,0.2,inside=True) - cg.draw_scaffold_ids(9.2,inside=False,fontsize=15) - - # CNV - cg.draw_scaffold(5.5,0.01) - cg.fill_between(5.5,AMP.iloc[:100,:],start='Start',end='End',score='frequency',scale=2.0,facecolor='red',alpha=0.5) - cg.fill_between(5.5,DEL.iloc[:100,:],start='Start',end='End',score='frequency',scale=2.0,facecolor='blue',alpha=0.5) - - # draw links - cg.draw_link(4.5,['chr1','chr4'],[10000000,10000000],[110000000,110000000],color='purple',alpha=0.5) - cg.draw_link(4.5,['chr3','chr8'],[10000000,10000000],[110000000,110000000],color='lightblue',alpha=0.5) - - plt.savefig('Circos.pdf') diff --git a/install.sh b/install.sh deleted file mode 100644 index de69016..0000000 --- a/install.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -#Last-modified: 17 Jul 2017 11:02:25 AM - -####################### Module/Scripts Description ###################### -# -# Copyright (c) 2008 Yunfei Wang -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the BSD License (see the file COPYING included with -# the distribution). -# -# @status: experimental -# @version: $Revision$ -# @author: Yunfei Wang -# @contact: tszn1984@gmail.com -# -######################################################################### - - -IFS='%' -USAGE=" Installation:\n Usage 1: install to \$HOME/local\n $0 install\n Usage 2: install to a specified path\n $0 path\n\n Uninstallation:\n $0 uninstall\n\n Clean built files:\n $0 clean\n\n Distribute:\n $0 distribute\n" -case $# in - 0) echo -en $USAGE - exit;; - *) ;; -esac - -# Parse parameters -install_path=$1 - -# clean built files -if [ "$install_path" == "clean" ]; then - python setup.py clean --all -elif [ "$install_path" == "distribute" ]; then - python setup.py register sdist upload -# uninstall -elif [ "$install_path" == "uninstall" ]; then - if [ -f installed_files.txt ]; then - cat installed_files.txt|xargs rm -rf - fi - rm installed_files.txt -# install -else - if [ "$install_path" == "install" ]; then - install_path=$HOME/TData/miniconda2 - fi - # check if path exists - if [ -d $install_path ]; then - if [ -f installed_files.txt ]; then - cat installed_files.txt|xargs rm -rf - fi - python setup.py install --record installed_files.txt --prefix=$install_path - else - echo "ERROR: Cannot parse the option, or the install path doesn't exist!" - fi -fi - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4021870 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy +pandas +matplotlib diff --git a/setup.py b/setup.py index a339c8c..b05410f 100644 --- a/setup.py +++ b/setup.py @@ -1,91 +1,54 @@ -#!/usr/bin/python -#Last-modified: 17 Jul 2017 11:00:55 AM - -# Module/Scripts Description -# -# Copyright (c) 2008 Yunfei Wang -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the BSD License (see the file COPYING included with -# the distribution). -# -# @status: experimental -# @version: 1.1.0 -# @author: Yunfei Wang -# @contact: yfwang0405@gmail.com - -# ------------------------------------ -# python modules -# ------------------------------------ - -import os,sys -from setuptools import setup, find_packages, Extension - -# ------------------------------------ -# constants -# ------------------------------------ - -# ------------------------------------ -# Misc functions -# ------------------------------------ - -# ------------------------------------ -# Classes -# ------------------------------------ - -# ------------------------------------ -# Main -# ------------------------------------ - -if __name__ == '__main__': - if float(sys.version[:3])<2.6 or float(sys.version[:3])>=2.8: - sys.stderr.write("CRITICAL: Python version must be 2.6 or 2.7!\n") - sys.exit(1) - - # includepy = "%s/include/python%s" % (sys.prefix, sys.version[:3]) - with open("README.rst",'r') as fh: - long_description = fh.read() - idx = long_description.find('\n') - description = long_description[:idx].rstrip() - # ngsplot version - with open('RELEASE','r') as fh: - PROG, VERSION = fh.next().rstrip().split()[:2] - - # Compile Kent lib - if 'clean' in sys.argv: - print >>sys.stderr, "Clean dist and egg info ..." - os.system('if [ -d dist ]; then rm -rf dist; fi') - os.system('if [ -f {0}.egg-info ]; then rm {0}.egg-info; fi'.format(PROG)) - os.system('if [ -d {0}.egg-info ]; then rm -rf {0}.egg-info; fi'.format(PROG)) - - # install requirement - install_requires = [["numpy >= 1.4.1"], - ["matplotlib >= 2.0.0"], - ["pandas >= 0.18.0"]] - # Python 2.6 requires argparse - if float(sys.version[:3]) == 2.6: - install_requires.append(["argparse >= 1.2.1"]) - - setup(name=PROG, - version=VERSION, - author='Yunfei Wang', - author_email='yfwang0405@gmail.com', - url='https://github.com/tsznxx/{0}'.format(PROG), - license="GNU General Public License (GPL)", - keywords = "Python NGS plot", - description = (description), - long_description = long_description, - package_dir={PROG:'src'}, - packages = [PROG], - scripts=[], - ext_modules=[], - classifiers=['Environment :: Console', - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'License :: Free for non-commercial use', - 'Operating System :: Unix', - 'Programming Language :: Python :: 2.7', - 'Topic :: Scientific/Engineering :: Bio-Informatics'], - install_requires=install_requires) - +#! /usr/bin/env python +# coding: utf-8 +# branch: dev +# version: 1.0.3 +# license: AGPLv3 +# author: Yunfei Wang (yfwang0405@gmail.com), +# Baochen Yang (yangbaochen1217@gmail.com) + + +import sys +from setuptools import setup +from distutils.version import LooseVersion + +if LooseVersion(sys.version) >= LooseVersion("3.5"): + # decsription + with open("README.md", 'r') as readme: + long_description = readme.read() + + setup( + name='pycircos', + version='1.0.3', + author='Yunfei Wang, Baochen Yang', + author_email='yfwang0405@gmail.com, yangbaochen1217@gmail.com', + url='https://github.com/KimBioInfoStudio/PyCircos', + license="AGPLv3", + keywords="Python NGS Circos Plot", + description=("This Tools is Design for NGS Circos Plot with using Python."), + long_description=long_description, + package_dir={'pycircos': 'src'}, + packages=['pycircos'], + scripts=[], + ext_modules=[], + classifiers=[ + 'Environment :: Console', + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: Free for non-commercial use', + 'License :: OSI Approved :: GNU General Public License (GPL)', + # 'License :: OSI APPROVED :: GNU General Public License v3 (GPLv3)', + 'Operating System :: Unix', + 'Operating System :: POSIX :: Linux', + 'Operating System :: Microsoft :: Windows', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Topic :: Scientific/Engineering :: Bio-Informatics'], + install_requires=['numpy', 'pandas', 'matplotlib'], + python_requires='>=3.5') +else: + print("[ WARNING ]") + print("As for www.python.org, Python2 will end life form 1/1/2020,") + print("So we will not support Python2 form now on, and we deeply recommend") + print("all our users to move to Python3.5+ ! Thx!") diff --git a/src/__init__.py b/src/__init__.py index 53cd004..4b55594 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,90 +1,53 @@ -#!/usr/bin/env python -# -- coding:utf-8 -- -# Last-modified: 10 Nov 2017 05:32:59 PM -# -# Module/Scripts Description -# -# Copyright (c) 2016 Yunfei Wang -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the BSD License (see the file COPYING included with -# the distribution). -# -# @status: experimental -# @version: 1.0.0 -# @author: Yunfei Wang -# @contact: yfwang0405@gmail.com +#!/usr/bin/env python3 +# coding: utf-8 +# branch: dev +# version: 1.0.2 +# license: AGPLv3 +# author: Yunfei Wang (yfwang0405@gmail.com) +# Baochen Yang (yangbaochen1217@gmail.com) -# ------------------------------------ -# python modules -# ------------------------------------ -import os -import sys -import pandas -import numpy +import re +import pandas as pd +import numpy as np import matplotlib.pyplot as plt from matplotlib.path import Path from matplotlib.patches import PathPatch +from typing import Sequence -# ------------------------------------ -# constants -# ------------------------------------ -# ------------------------------------ -# Misc functions -# ------------------------------------ - -# ------------------------------------ -# Classes -# ------------------------------------ - -class Utils(object): - def naturalkeys(text): - ''' - nlist = sorted(old_list,key=natural_keys) #sorts in human order - http://nedbatchelder.com/blog/200712/human_sorting.html - (See Toothy's implementation in the comments) - ''' - import re - def atoi(text): - return int(text) if text.isdigit() else text - return [ atoi(c) for c in re.split('(\d+)', text) ] - naturalkeys=staticmethod(naturalkeys) - - class Circos(object): ''' Python Circos. ''' - def __init__(self,data,length='length',figsize=(10,10),gap=0.5): + + def __init__(self, data: pd.DataFrame, length: str = 'length', figsize: Sequence[float] = (10, 10), gap: float = 0.5) -> None: ''' Initiation from a list of beds. Parameters: - data: pandas.DataFrame + data: pd.DataFrame chomosome regions, with index as chromosome IDs, and size as chromosizes. size: string column name for chrom sizes figsize: tuple or list of floats width x height gap: float - gap between each Bed. + gap between each Bed. ''' - self.regions = data - self.regions =self.regions.loc[sorted(self.regions.index,key=Utils.naturalkeys),:] # the gid order of the beds + self.regions = data.loc[sorted(data.index, key=lambda x: [int(c) if c.isdigit() else c for c in re.split('(\d+)', x)]), :] # the gid order of the beds total_len = self.regions.length.sum() - self.len_per_degree = total_len/(360.-gap*data.shape[0]) - self.len_per_theta = total_len/(numpy.pi*2-numpy.deg2rad(gap)*data.shape[0]) + self.len_per_degree = total_len / (360. - gap * data.shape[0]) + self.len_per_theta = total_len / (np.pi * 2 - np.deg2rad(gap) * data.shape[0]) # chromosome start thetas - cumlen = [0] + list(self.regions.length.cumsum())[:-1] # accumulative length - self.regions['theta_start'] = [numpy.deg2rad(l/self.len_per_degree+gap*i) for i,l in enumerate(cumlen)] + cumlen = [0] + list(self.regions.length.cumsum())[:-1] # accumulative length + self.regions['offset'] = [np.deg2rad(l / self.len_per_degree + gap * i) for i, l in enumerate(cumlen)] # polar axis self.fig = plt.figure(figsize=figsize) - l = max(figsize) - self.pax = self.fig.add_axes([0,0,1,1],polar=True) + self.pax = self.fig.add_axes([0, 0, 1, 1], polar=True) self.pax.axis('off') - self.pax.set_ylim(0,l) - def get_theta(self,gid,pos): + self.pax.set_ylim(0, max(figsize)) + + def get_theta(self, gid, pos): ''' get the theta of the position. Parameters: @@ -94,14 +57,15 @@ def get_theta(self,gid,pos): chrom coordinates Note: gid and pos must be the same length if they are lists. - ''' - if isinstance(pos,int) or isinstance(pos,float): - et = self.regions.loc[gid,'theta_start']+pos/self.len_per_theta + ''' + if isinstance(pos, int) or isinstance(pos, float): + et = self.regions.loc[gid, 'offset'] + pos / self.len_per_theta return et # iterable - ets = [self.regions.loc[g,'theta_start']+(p/self.len_per_theta) for g,p in zip(gid,pos)] + ets = [self.regions.loc[g, 'offset'] + (p / self.len_per_theta) for g, p in list(zip(gid, pos))] return ets - def draw_scaffold(self,rad,width,colors=[],fill=False,**kwargs): + + def draw_scaffold(self, rad, width): ''' Draw scaffold. Parameters: @@ -114,18 +78,11 @@ def draw_scaffold(self,rad,width,colors=[],fill=False,**kwargs): alpha: float alpha value. ''' - mtick = numpy.deg2rad(50000000/self.len_per_degree) - n = len(colors) - if fill == False or n == 0: - kwargs.update({'edgecolor':'k','linewidth':1,'linestyle':'-','fill':False}) - else: - kwargs.update({'linewidth':0}) - for i,gid in enumerate(self.regions.index): - if n: - kwargs['color'] = colors[i%n] - et1, et2 = self.regions.theta_start[gid], self.get_theta(gid,self.regions.length[gid]-1) - self.pax.bar([(et1+et2)/2],[width],width=et2-et1,bottom=rad,**kwargs) - def draw_ticks(self,rad,tick_length,tick_gap=50000000,unit=1000000,unit_label='M',inside=False,**kwargs): + for gid in self.regions.index: + et1, et2 = self.regions.offset[gid], self.get_theta([gid], [self.regions.length[gid] - 1]) + self.pax.bar((et1 + et2) / 2, width, width=et2 - et1, bottom=rad, alpha=0.2) + + def draw_ticks(self, rad, tick_length, tick_gap=50000000, unit=1000000, unit_label='M', inside=False, **kwargs): ''' Draw ticks. Parameters: @@ -142,25 +99,26 @@ def draw_ticks(self,rad,tick_length,tick_gap=50000000,unit=1000000,unit_label='M inside: bool draw ticks inside kwargs: dict - parameters to vlines + parameters to vlines ''' - ml = max([len("{0}{1}".format(int(self.regions.loc[gid,'length']/unit),unit_label)) for gid in self.regions.index ]) # max ticklabel length - for i,gid in enumerate(self.regions.index): - et1, et2 = self.regions.theta_start[gid], self.get_theta(gid,self.regions.length[gid]-1) - ets = numpy.arange(et1,et2,tick_gap/self.len_per_theta) + ml = max([len("{0}{1}".format(int(self.regions.loc[gid, 'length'] / unit), unit_label)) for gid in self.regions.index]) # max ticklabel length + for i, gid in enumerate(self.regions.index): + et1, et2 = self.regions.offset[gid], self.get_theta([gid], [self.regions.length[gid] - 1]) + ets = np.arange(et1, et2, tick_gap / self.len_per_theta) if inside: - self.pax.vlines(ets,[rad]*len(ets),[rad-tick_length]*len(ets)) + self.pax.vlines(ets, [rad] * len(ets), [rad - tick_length] * len(ets)) else: - self.pax.vlines(ets,[rad]*len(ets),[rad+tick_length]*len(ets)) - for j,et in enumerate(ets): - lstr = "{0}{1}".format(j*tick_gap/unit,unit_label) + self.pax.vlines(ets, [rad] * len(ets), [rad + tick_length] * len(ets)) + for j, et in enumerate(ets): + lstr = "{0}{1}".format(j * tick_gap / unit, unit_label) if inside: - lstr = ' '*(ml-len(lstr)) + lstr - self.pax.annotate(lstr,xy=[et,rad-tick_length-ml*0.1], ha='center',va='center',rotation=numpy.rad2deg(et)) + lstr = ' ' * (ml - len(lstr)) + lstr + self.pax.annotate(lstr, xy=[et, rad - tick_length - ml * 0.1], ha='center', va='center', rotation=np.rad2deg(et)) else: - lstr += ' '*(ml-len(lstr)) - self.pax.annotate(lstr,xy=[et,rad+tick_length+ml*0.1], ha='center',va='center',rotation=numpy.rad2deg(et)) - def draw_cytobands(self,rad,width,cbfile,**kwargs): + lstr += ' ' * (ml - len(lstr)) + self.pax.annotate(lstr, xy=[et, rad + tick_length + ml * 0.1], ha='center', va='center', rotation=np.rad2deg(et)) + + def draw_cytobands(self, rad, width, cbfile, **kwargs): ''' Draw cytobands. Parameters: @@ -171,18 +129,19 @@ def draw_cytobands(self,rad,width,cbfile,**kwargs): cbfile: string UCSC cytoband file kwargs: dict - parameters passed to pax.bar + parameters passed to pax.bar ''' - cyto_colors = {"gneg":"#FFFFFF","gpos25":"#E5E5E5","gpos50":"#B3B3B3","gpos75":"#666666", - "gpos100":"#000000","gvar":"#FFFFFF","stalk":"#CD3333","acen":"#8B2323"} - cb = pandas.read_table(cbfile,index_col=None,header=None,names=['chrom','start','end','band','color']) + cyto_colors = {"gneg": "#FFFFFF", "gpos25": "#E5E5E5", "gpos50": "#B3B3B3", "gpos75": "#666666", + "gpos100": "#000000", "gvar": "#FFFFFF", "stalk": "#CD3333", "acen": "#8B2323"} + cb = pd.read_table(cbfile, index_col=None, header=None, names=['chrom', 'start', 'end', 'band', 'color']) cb['color'] = [cyto_colors[c] for c in cb.color] - cb['et1'] = self.get_theta(cb.chrom,cb.start) - cb['et2'] = self.get_theta(cb.chrom,cb.end) - cb['etm'] = cb.loc[:,['et1','et2']].mean(axis=1) - cb['etw'] = cb['et2']-cb['et1'] - self.pax.bar(cb.etm,[width]*cb.shape[0],width=cb.etw,bottom=rad,color=cb.color,**kwargs) - def draw_scaffold_ids(self,rad,inside=False,**kwargs): + cb['et1'] = self.get_theta(cb.chrom, cb.start) + cb['et2'] = self.get_theta(cb.chrom, cb.end) + cb['etm'] = cb.loc[:, ['et1', 'et2']].mean(axis=1) + cb['etw'] = cb['et2'] - cb['et1'] + self.pax.bar(cb.etm, [width] * cb.shape[0], width=cb.etw, bottom=rad, color=cb.color, **kwargs) + + def draw_scaffold_ids(self, rad, inside=False, **kwargs): ''' Draw scaffold region IDs. Parameters: @@ -193,28 +152,29 @@ def draw_scaffold_ids(self,rad,inside=False,**kwargs): kwargs: dict to ax.annotate() fontsize, rotation - ''' - kwargs.setdefault('ha','center') - kwargs.setdefault('va','center') - rotation = kwargs.get('rotation',0) + ''' + kwargs.setdefault('ha', 'center') + kwargs.setdefault('va', 'center') + rotation = kwargs.get('rotation', 0) ml = max([len(gid) for gid in self.regions.index]) for gid in self.regions.index: - deg = numpy.rad2deg(self.get_theta(gid,self.regions.length[gid]/2)) + deg = np.rad2deg(self.get_theta(gid, self.regions.length[gid] / 2)) kwargs['rotation'] = rotation + deg if 90 < kwargs['rotation'] < 270: kwargs['rotation'] += 180 - if inside: # add spaces to the right side - lstr = ' '*(ml-len(gid)) + gid + if inside: # add spaces to the right side + lstr = ' ' * (ml - len(gid)) + gid else: - lstr = gid + ' '*(ml-len(gid)) - self.pax.annotate(gid,xy=[numpy.deg2rad(deg),rad],**kwargs) - def fill_between(self,rad,data,gid='chrom',start='start',end='end',score='score',cutoff=0.1,scale=1.,**kwargs): + lstr = gid + ' ' * (ml - len(gid)) + self.pax.annotate(gid, xy=[np.deg2rad(deg), rad], **kwargs) + + def fill_between(self, rad, data, gid='chrom', start='start', end='end', score='score', cutoff=0.1, scale=1., **kwargs): ''' Draw densities. Parameters: rad: float radius - data: pandas.DataFrame + data: pd.DataFrame chromosomal regions start, end: int chrom start or end @@ -227,14 +187,15 @@ def fill_between(self,rad,data,gid='chrom',start='start',end='end',score='score' kwargs: dict parameters passed to ax.fill_between ''' - rads = [rad,rad] - facecolor = kwargs.get('facecolor','red') - for gid, start, end, score in zip(data[gid],data[start],data[end],data[score]): - ets = self.get_theta([gid,gid],[start,end]) - kwargs['facecolor'] = facecolor if abs(score)>cutoff else 'grey' - score = scale*score + rad - self.pax.fill_between(ets,rads,[score, score],**kwargs) - def draw_link(self,rad,gids,starts,ends,color=None,alpha=1.): + rads = [rad, rad] + facecolor = kwargs.get('facecolor', 'red') + for gid, start, end, score in zip(data[gid], data[start], data[end], data[score]): + ets = self.get_theta([gid, gid], [start, end]) + kwargs['facecolor'] = facecolor if abs(score) > cutoff else 'grey' + score = scale * score + rad + self.pax.fill_between(ets, rads, [score, score], **kwargs) + + def draw_link(self, rad, gids, starts, ends, color=None, alpha=1.): ''' Draw links Parameters: @@ -247,29 +208,22 @@ def draw_link(self,rad,gids,starts,ends,color=None,alpha=1.): color: string face color alpha: float - alpha - ''' - ets = self.get_theta(gids,starts) - ete = self.get_theta(gids,ends) - points = [(ets[0],rad), # start1 - ((ets[0]+ete[0])/2,rad), # through point - (ete[0],rad), # end 1 - (0,0), # through point - (ets[1],rad), # start2 - ((ets[1]+ete[1])/2,rad), # through point - (ete[1],rad), # end2 - (0,0), # through point - (ets[0],rad)] + alpha + ''' + ets = self.get_theta(gids, starts) + ete = self.get_theta(gids, ends) + points = [(ets[0], rad), # start1 + ((ets[0] + ete[0]) / 2, rad), # through point + (ete[0], rad), # end 1 + (0, 0), # through point + (ets[1], rad), # start2 + ((ets[1] + ete[1]) / 2, rad), # through point + (ete[1], rad), # end2 + (0, 0), # through point + (ets[0], rad)] # parse patches - codes = [Path.CURVE3]*len(points) + codes = [Path.CURVE3] * len(points) codes[0] = Path.MOVETO path = Path(points, codes) - patch = PathPatch(path, facecolor=color, lw=0.2,alpha=alpha) + patch = PathPatch(path, facecolor=color, lw=0.2, alpha=alpha) self.pax.add_patch(patch) - -# ------------------------------------ -# Main -# ------------------------------------ - -if __name__=="__main__": - pass diff --git a/demo/cytoBand.txt.gz b/test/data/cytoBand.txt.gz similarity index 100% rename from demo/cytoBand.txt.gz rename to test/data/cytoBand.txt.gz diff --git a/demo/hg19.fa.sizes b/test/data/hg19.fa.sizes similarity index 100% rename from demo/hg19.fa.sizes rename to test/data/hg19.fa.sizes diff --git a/demo/scores.gistic b/test/data/scores.gistic similarity index 100% rename from demo/scores.gistic rename to test/data/scores.gistic diff --git a/test/demo.png b/test/demo.png new file mode 100644 index 0000000..d4bf57d Binary files /dev/null and b/test/demo.png differ diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..6a0b7e0 --- /dev/null +++ b/test/test.py @@ -0,0 +1,52 @@ +#! /usr/bin/env python3 +# coding: utf-8 +# branch: dev +# version: 1.0.2 +# license: AGPLv3 +# author: Yunfei Wang (yfwang0405@gmail.com) +# Baochen Yang (yangbaochen1217@gmail.com) + +import os +import pandas +import matplotlib +if not 'DISPLAY' in os.environ: + matplotlib.use('Agg') # if DISPLAY is not set +import pycircos +import matplotlib.pyplot as plt + + +def test(): + chroot = os.path.dirname(os.path.abspath(__file__)) + CNV = pandas.read_table(os.path.join(chroot, "data/scores.gistic")) + CNV['chrom'] = 'chr' + CNV.Chromosome.astype(str) + CNV = CNV.sort_values(['Chromosome', 'Start']) + CNV.loc[CNV.Type == 'Del', 'frequency'] *= -1 + + AMP = CNV.loc[CNV.Type == 'Amp', :] + DEL = CNV.loc[CNV.Type == 'Del', :] + + chromsizes = pandas.read_table(os.path.join(chroot, "data/hg19.fa.sizes"), index_col=0, header=None, names=['length']) + cg = pycircos.Circos(chromsizes, gap=2) + + # draw cytoband + cg.draw_cytobands(8.1, 0.3, os.path.join(chroot, "data/cytoBand.txt.gz")) + + # # draw chrom region + cg.draw_scaffold(8.1, 0.3) + cg.draw_ticks(8.1, 0.2, inside=True) + cg.draw_scaffold_ids(9.2, inside=False, fontsize=15) + + # draw CNV + cg.draw_scaffold(5.5, 0.01) + cg.fill_between(5.5, AMP.iloc[:, :], start='Start', end='End', score='frequency', scale=2.0, facecolor='red', alpha=0.5) + cg.fill_between(5.5, DEL.iloc[:, :], start='Start', end='End', score='frequency', scale=2.0, facecolor='blue', alpha=0.5) + + # draw links + cg.draw_link(4.5, ['chr1', 'chr4'], [10000000, 10000000], [110000000, 110000000], color='purple', alpha=0.5) + cg.draw_link(4.5, ['chr3', 'chr8'], [10000000, 10000000], [110000000, 110000000], color='lightblue', alpha=0.5) + # save result to a png file + plt.savefig(os.path.join(chroot, "./demo.png")) + + +if __name__ == "__main__": + test()