Skip to content

Commit

Permalink
pre-release 2.0.0a0
Browse files Browse the repository at this point in the history
  • Loading branch information
Tao Zhu committed Jul 22, 2019
1 parent bfed0a5 commit ec8d4b6
Show file tree
Hide file tree
Showing 25 changed files with 207 additions and 141 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ venv.bak/

# vscode
.vscode

# test
tests/_internal_
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include tests/example*
include tests/query*
include primerserver2/data/*
include example_cmd.sh
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# PrimerServer2
PrimerServer2: a high-throughput primer design and specificity-checking platform

## Description
PrimerServer was proposed to design genome-wide specific PCR primers. It uses candidate primers produced by Primer3, uses BLAST and nucleotide thermodynamics to search for possible amplicons and filters out specific primers for each site. By using multiple threads, it runs very fast, ~0.4s per site in our case study for more than 10000 sites.

## External Dependencies
* [Samtools](http://www.htslib.org/) (>=1.9).
* [NCBI BLAST+](https://blast.ncbi.nlm.nih.gov/Blast.cgi) (>=2.2.18)

5 changes: 5 additions & 0 deletions example_cmd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# design primers and check specificity (the default mode)
primertool tests/query_design_multiple tests/example.fa -p 2 -o example_design_check.json -t example_design_check.tsv

# check specificity only
primertool tests/query_check_multiple tests/example.fa --only-specificity -p 2 -o example_check.json -t example_check.tsv
16 changes: 16 additions & 0 deletions primerserver2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'''PrimerServer2: a high-throughput primer design and specificity-checking platform
Github: https://github.com/billzt/PrimerServer2
PrimerServer was proposed to design genome(or transcriptome)-wide specific PCR primers. It uses candidate primers
produced by Primer3, uses BLAST and nucleotide thermodynamics to search for possible amplicons and filters out
specific primers for each site. By using multiple CPUs, it runs very fast, ~0.4s per site in our case study for
more than 10000 sites.
External Dependencies:
blastn and makeblastdb (from NCBI BLAST+)
samtools (>=v1.0)
'''
Empty file added primerserver2/cmd/__init__.py
Empty file.
113 changes: 113 additions & 0 deletions primerserver2/cmd/primertool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3

'''PrimerServer2: a high-throughput primer design and specificity-checking platform
Github: https://github.com/billzt/PrimerServer2
'''

__author__ = 'Tao Zhu'
__copyright__ = 'Copyright 2019'
__license__ = 'GPL'
__version__ = '0.1'
__email__ = '[email protected]'
__status__ = 'Development'

import argparse
import re
import os
import sys
import json
import shutil

from distutils.version import LooseVersion

from primerserver2.core import make_sites, make_primers, design_primer, run_blast, sort_primers, output


def error(msg, judge):
if judge is True:
print(json.dumps({'ERROR': msg}))
raise Exception(msg)

def make_args():
parser = argparse.ArgumentParser(description='PrimerServer2: a high-throughput primer \
design and specificity-checking platform', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('query', help='query file. (STDIN is acceptable)', type=argparse.FileType('r'))
parser.add_argument('template', help='template file in FASTA format')

group = parser.add_mutually_exclusive_group()
group.add_argument('--no-specificity', help="Don't check specificity; Only design primers", action='store_true')
group.add_argument('--only-specificity', help="Only check specificity and skip primer designs", action='store_true')
parser.add_argument('--type', choices=['SEQUENCE_TARGET', 'SEQUENCE_INCLUDED_REGION', 'FORCE_END'],\
help='designing primer types', default='SEQUENCE_TARGET')
parser.add_argument('-p', '--cpu', type=int, help='Used CPU number.', default=2)
parser.add_argument('-l', '--checking-size-min', type=int, help='Lower limit of the checking amplicon size range (bp).', \
default=70)
parser.add_argument('-u', '--checking-size-max', type=int, help='Upper limit of the checking amplicon size range (bp).', \
default=1000)
parser.add_argument('-r', '--primer-num-retain', type=int, help='The maximum number of primers for each site to return.', \
default=10)
parser.add_argument('-a', '--report-amplicon-seqs', help="Get amplicon seqs (might be slow)", action='store_true')
parser.add_argument('--json-debug', help="Output debug information in JSON mode", action='store_true')
parser.add_argument('-o', '--out', help="Output primers in JSON format. (Default is STDIN)", type=argparse.FileType('w'))
parser.add_argument('-t', '--tsv', help="Output primers in TSV format", type=argparse.FileType('w'))
args = parser.parse_args()
return args

def check_environments(args):
if shutil.which('samtools') is None:
error('No samtools detected in your system', args.json_debug)

samtools_version = os.popen('samtools --version').readlines()[0].strip().split(' ')[1]
if LooseVersion(samtools_version) < LooseVersion('1.9'):
error(f'Your samtools version is v{samtools_version}, but >=v1.9 is required', args.json_debug)

if shutil.which('blastn') is None:
error('No NCBI-BLAST+ (blastn) detected in your system', args.json_debug)

if shutil.which('makeblastdb') is None:
error('No NCBI-BLAST+ (makeblastdb) detected in your system', args.json_debug)

def check_template(args):
if os.path.isfile(args.template) is False:
error(f'File not found: {args.template}', args.json_debug)
if os.path.isfile(args.template+'.fai') is False:
code = os.system(f'samtools faidx {args.template} 2>/dev/null')
if code != 0:
error(f'File {args.template} cannot be indexed by samtools faidx. Perhaps it is not in FASTA format', args.json_debug)

def run(args):
################### Design primers ###################
query_string = args.query.read()
if args.only_specificity is True:
primers = make_primers.make_primers(query=query_string)
else:
sites = make_sites.build(query=query_string, template_file=args.template, primer_type=args.type)
primers = design_primer.multiple(sites, cpu=args.cpu)

################### Checking specificity #############
dbs = [args.template]
if args.no_specificity is False:
primers = run_blast.run_blast_parallel(primers=primers, dbs=dbs, cpu=args.cpu,\
checking_size_max=args.checking_size_max, checking_size_min=args.checking_size_min, \
report_amplicon_seq=args.report_amplicon_seqs)
primers = sort_primers.sort_rank(primers=primers, dbs=dbs, max_num_return=args.primer_num_retain)

################### Output ###########################
if args.out is not None:
print(json.dumps(primers, indent=4), file=args.out)
else:
print(json.dumps(primers, indent=4))

if args.tsv is not None:
if args.no_specificity is False:
output.after_check(primers, dbs, file=args.tsv)

def main():
args = make_args()
check_environments(args)
check_template(args)
run(args)

if __name__ == "__main__":
main()

File renamed without changes.
Empty file added primerserver2/core/__init__.py
Empty file.
10 changes: 5 additions & 5 deletions src/analysis_blast.py → primerserver2/core/analysis_blast.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import json
import sys

from make_sites import faidx
from Santalucia_NN_Tm import complement, rev_complement, NN_Tm
from make_primers import make_primers
from primerserver2.core.make_sites import faidx
from primerserver2.core.Santalucia_NN_Tm import complement, rev_complement, NN_Tm
from primerserver2.core.make_primers import make_primers

def filter_len(blast_out, len_min, len_max):
'''
Expand Down Expand Up @@ -154,10 +154,10 @@ def add_amplicon_seq(amplicons, template_file):


if __name__ == "__main__":
blast_out = open('tests/query_blast.fa.out').read()
blast_out = open('tests/_internal_/query_blast.fa.out').read()
amplicons = filter_len(blast_out=blast_out, len_min=75, len_max=1000)
hits_seqs = faidx(template_file='tests/example.fa', region_string=amplicons['regions_primer'])
report_amplicons = filter_Tm(amplicons['amplicons'], query_primer_seq={'LEFT':'CTTCTGCAATGCCAAGTCCAG',\
'RIGHT': 'GTGGTGAAGGGTCGGTTGAA'}, hits_seqs=hits_seqs)
report_amplicons = add_amplicon_seq(amplicons=report_amplicons, template_file='tests/example.fa')
print(json.dumps(report_amplicons))
print(json.dumps(report_amplicons, indent=4))
5 changes: 3 additions & 2 deletions src/design_primer.py → primerserver2/core/design_primer.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@

import json
import os
import multiprocessing as mp

import primer3

p3_settings = dict(json.load(open('src/p3_settings.json')))
p3_settings = dict(json.load(open('primerserver2/data/p3_settings.json')))

def single(site):
'''
Expand Down Expand Up @@ -108,4 +109,4 @@ def multiple(sites, cpu=2):
'size_min':75,
'size_max':1000
}
])))
]), indent=4))
5 changes: 3 additions & 2 deletions src/make_primers.py → primerserver2/core/make_primers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import json

import primer3

Expand Down Expand Up @@ -40,6 +41,6 @@ def make_primers(query):
return primers

if __name__ == "__main__":
with open('tests/query_check') as f:
with open('tests/_internal_/query_check') as f:
primers = make_primers(f.read())
print(primers)
print(json.dumps(primers, indent=4))
2 changes: 1 addition & 1 deletion src/make_sites.py → primerserver2/core/make_sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,6 @@ def build(query, template_file, primer_type):
return primer_sites

if __name__ == "__main__":
with open('tests/query_design') as f:
with open('tests/query_design_multiple') as f:
primer_sites = build(query=f.read(), template_file='tests/example.fa', primer_type='SEQUENCE_TARGET')
print(json.dumps(primer_sites, indent=4))
2 changes: 1 addition & 1 deletion src/output.py → primerserver2/core/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ def after_check(primers, dbs, file):


if __name__ == "__main__":
primers = json.load(open('tests/sort_primers.json'))
primers = json.load(open('tests/_internal_/sort_primers.json'))
dbs = ['example.fa']
after_check(primers, dbs, file=sys.stdout)
6 changes: 3 additions & 3 deletions src/run_blast.py → primerserver2/core/run_blast.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

import primer3

from analysis_blast import filter_len, filter_Tm, add_amplicon_seq
from make_sites import faidx
from make_primers import make_primers
from primerserver2.core.analysis_blast import filter_len, filter_Tm, add_amplicon_seq
from primerserver2.core.make_sites import faidx
from primerserver2.core.make_primers import make_primers

def run_blast(p3_input):
'''
Expand Down
4 changes: 2 additions & 2 deletions src/sort_primers.py → primerserver2/core/sort_primers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def sort_rank(primers, dbs, max_num_return=10):
return primers

if __name__ == "__main__":
primers = json.load(open('tests/run_blast.json'))
primers = json.load(open('tests/_internal_/run_blast.json'))
dbs = ['example.fa']
print(json.dumps(sort_rank(primers, dbs)))
print(json.dumps(sort_rank(primers, dbs), indent=4))

File renamed without changes.
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[metadata]
description-file = README.md
37 changes: 35 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@

# primer3-py
# flask
from setuptools import setup, find_packages

VERSION = '2.0.0a0'

setup(name='primerserver2',
version=VERSION,
description="a high-throughput primer design and specificity-checking platform",
long_description=__doc__,
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Environment :: Console',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: MIT License',
'Operating System :: Unix',
'Programming Language :: Python :: 3.6',
'Topic :: Scientific/Engineering :: Bio-Informatics'
],
keywords='primer bioinformatics PCR',
author='Tao Zhu',
author_email='[email protected]',
url='https://github.com/billzt/PrimerServer2',
license='MIT',
packages=find_packages(),
include_package_data=True,
zip_safe=True,
python_requires='>=3.6',
install_requires=[
'primer3-py'
],
entry_points={
'console_scripts': [
'primertool = primerserver2.cmd.primertool:main'
]
},
)
Loading

0 comments on commit ec8d4b6

Please sign in to comment.