From 119df1cedad3d4760e6fd458713da2488eff79cc Mon Sep 17 00:00:00 2001 From: pvtroshin Date: Tue, 8 Feb 2011 17:27:02 +0000 Subject: [PATCH] Copying Bio-python to globplot to satisfy the dependency git-svn-id: link to svn.lifesci.dundee.ac.uk/svn/barton/ptroshin/JABA2@3719 e3abac25-378b-4346-85de-24260fe3988d --- .../globplot/biopython-1.50/Bio/Alphabet/IUPAC.py | 127 ++ .../globplot/biopython-1.50/Bio/Alphabet/IUPAC.pyc | Bin 0 -> 3871 bytes .../biopython-1.50/Bio/Alphabet/Reduced.py | 181 +++ .../biopython-1.50/Bio/Alphabet/__init__.py | 255 +++ .../biopython-1.50/Bio/Alphabet/__init__.pyc | Bin 0 -> 8466 bytes .../biopython-1.50/Bio/Application/__init__.py | 250 +++ .../globplot/biopython-1.50/Bio/Data/CodonTable.py | 802 ++++++++++ .../biopython-1.50/Bio/Data/CodonTable.pyc | Bin 0 -> 21526 bytes .../globplot/biopython-1.50/Bio/Data/IUPACData.py | 209 +++ .../globplot/biopython-1.50/Bio/Data/IUPACData.pyc | Bin 0 -> 3758 bytes .../globplot/biopython-1.50/Bio/Data/__init__.py | 3 + .../globplot/biopython-1.50/Bio/Data/__init__.pyc | Bin 0 -> 230 bytes binaries/src/globplot/biopython-1.50/Bio/Decode.py | 427 +++++ binaries/src/globplot/biopython-1.50/Bio/DocSQL.py | 207 +++ .../biopython-1.50/Bio/Encodings/IUPACEncoding.py | 126 ++ .../biopython-1.50/Bio/Encodings/__init__.py | 3 + .../biopython-1.50/Bio/Fasta/FastaAlign.py | 84 + .../globplot/biopython-1.50/Bio/Fasta/__init__.py | 198 +++ .../globplot/biopython-1.50/Bio/Fasta/__init__.pyc | Bin 0 -> 7303 bytes binaries/src/globplot/biopython-1.50/Bio/File.py | 180 +++ binaries/src/globplot/biopython-1.50/Bio/File.pyc | Bin 0 -> 6206 bytes .../globplot/biopython-1.50/Bio/FilteredReader.py | 152 ++ .../src/globplot/biopython-1.50/Bio/HotRand.py | 77 + binaries/src/globplot/biopython-1.50/Bio/Index.py | 142 ++ .../biopython-1.50/Bio/LogisticRegression.py | 142 ++ .../globplot/biopython-1.50/Bio/ParserSupport.py | 426 +++++ .../biopython-1.50/Bio/Parsers/__init__.py | 2 + .../globplot/biopython-1.50/Bio/Parsers/spark.py | 565 +++++++ .../globplot/biopython-1.50/Bio/PropertyManager.py | 83 + .../biopython-1.50/Bio/PropertyManager.pyc | Bin 0 -> 1831 bytes binaries/src/globplot/biopython-1.50/Bio/Search.py | 153 ++ binaries/src/globplot/biopython-1.50/Bio/Seq.py | 1633 ++++++++++++++++++++ binaries/src/globplot/biopython-1.50/Bio/Seq.pyc | Bin 0 -> 54182 bytes .../src/globplot/biopython-1.50/Bio/SeqFeature.py | 490 ++++++ .../src/globplot/biopython-1.50/Bio/SeqIO/AceIO.py | 63 + .../globplot/biopython-1.50/Bio/SeqIO/FastaIO.py | 208 +++ .../src/globplot/biopython-1.50/Bio/SeqIO/IgIO.py | 97 ++ .../globplot/biopython-1.50/Bio/SeqIO/InsdcIO.py | 377 +++++ .../biopython-1.50/Bio/SeqIO/Interfaces.py | 275 ++++ .../src/globplot/biopython-1.50/Bio/SeqIO/PhdIO.py | 43 + .../src/globplot/biopython-1.50/Bio/SeqIO/PirIO.py | 182 +++ .../globplot/biopython-1.50/Bio/SeqIO/QualityIO.py | 1113 +++++++++++++ .../globplot/biopython-1.50/Bio/SeqIO/SwissIO.py | 65 + .../src/globplot/biopython-1.50/Bio/SeqIO/TabIO.py | 109 ++ .../globplot/biopython-1.50/Bio/SeqIO/__init__.py | 650 ++++++++ .../src/globplot/biopython-1.50/Bio/SeqRecord.py | 628 ++++++++ .../src/globplot/biopython-1.50/Bio/SeqRecord.pyc | Bin 0 -> 24318 bytes .../biopython-1.50/Bio/SeqUtils/CheckSum.py | 124 ++ .../biopython-1.50/Bio/SeqUtils/CodonUsage.py | 153 ++ .../Bio/SeqUtils/CodonUsageIndices.py | 14 + .../Bio/SeqUtils/IsoelectricPoint.py | 114 ++ .../biopython-1.50/Bio/SeqUtils/MeltingTemp.py | 156 ++ .../biopython-1.50/Bio/SeqUtils/ProtParam.py | 251 +++ .../biopython-1.50/Bio/SeqUtils/ProtParamData.py | 43 + .../biopython-1.50/Bio/SeqUtils/__init__.py | 570 +++++++ .../globplot/biopython-1.50/Bio/SeqUtils/lcc.py | 162 ++ binaries/src/globplot/biopython-1.50/Bio/Std.py | 503 ++++++ .../src/globplot/biopython-1.50/Bio/StdHandler.py | 770 +++++++++ .../src/globplot/biopython-1.50/Bio/Transcribe.py | 34 + .../src/globplot/biopython-1.50/Bio/Translate.py | 133 ++ binaries/src/globplot/biopython-1.50/Bio/Writer.py | 17 + .../src/globplot/biopython-1.50/Bio/__init__.py | 16 + .../src/globplot/biopython-1.50/Bio/__init__.pyc | Bin 0 -> 624 bytes .../biopython-1.50/Bio/cMarkovModelmodule.c | 61 + .../globplot/biopython-1.50/Bio/clistfnsmodule.c | 161 ++ .../globplot/biopython-1.50/Bio/cmathfnsmodule.c | 140 ++ .../globplot/biopython-1.50/Bio/cstringfnsmodule.c | 122 ++ .../src/globplot/biopython-1.50/Bio/csupport.c | 30 + .../src/globplot/biopython-1.50/Bio/csupport.h | 2 + .../src/globplot/biopython-1.50/Bio/distance.py | 35 + binaries/src/globplot/biopython-1.50/Bio/kNN.py | 132 ++ .../src/globplot/biopython-1.50/Bio/listfns.py | 158 ++ .../src/globplot/biopython-1.50/Bio/mathfns.py | 100 ++ .../src/globplot/biopython-1.50/Bio/stringfns.py | 90 ++ binaries/src/globplot/biopython-1.50/Bio/trie.c | 778 ++++++++++ binaries/src/globplot/biopython-1.50/Bio/trie.h | 129 ++ .../src/globplot/biopython-1.50/Bio/triefind.py | 92 ++ .../src/globplot/biopython-1.50/Bio/triemodule.c | 661 ++++++++ binaries/src/globplot/biopython-1.50/Bio/utils.py | 163 ++ .../Bio/writers/SeqRecord/__init__.py | 2 + .../biopython-1.50/Bio/writers/SeqRecord/embl.py | 85 + .../biopython-1.50/Bio/writers/SeqRecord/empty.py | 7 + .../biopython-1.50/Bio/writers/SeqRecord/fasta.py | 21 + .../biopython-1.50/Bio/writers/__init__.py | 3 + binaries/src/globplot/biopython-1.50/CONTRIB | 53 + binaries/src/globplot/biopython-1.50/LICENSE | 19 + binaries/src/globplot/biopython-1.50/PKG-INFO | 11 + binaries/src/globplot/biopython-1.50/README | 164 ++ binaries/src/globplot/sav_gol | Bin 0 -> 448988 bytes 89 files changed, 17006 insertions(+) create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Alphabet/IUPAC.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Alphabet/IUPAC.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Alphabet/Reduced.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Alphabet/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Alphabet/__init__.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Application/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Data/CodonTable.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Data/CodonTable.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Data/IUPACData.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Data/IUPACData.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Data/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Data/__init__.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Decode.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/DocSQL.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Encodings/IUPACEncoding.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Encodings/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Fasta/FastaAlign.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Fasta/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Fasta/__init__.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/File.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/File.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/FilteredReader.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/HotRand.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Index.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/LogisticRegression.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/ParserSupport.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Parsers/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Parsers/spark.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/PropertyManager.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/PropertyManager.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Search.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Seq.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Seq.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqFeature.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/AceIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/FastaIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/IgIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/InsdcIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/Interfaces.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/PhdIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/PirIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/QualityIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/SwissIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/TabIO.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqIO/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqRecord.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqRecord.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CheckSum.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsage.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsageIndices.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/IsoelectricPoint.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/MeltingTemp.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParam.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParamData.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/SeqUtils/lcc.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Std.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/StdHandler.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Transcribe.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Translate.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/Writer.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/__init__.pyc create mode 100644 binaries/src/globplot/biopython-1.50/Bio/cMarkovModelmodule.c create mode 100644 binaries/src/globplot/biopython-1.50/Bio/clistfnsmodule.c create mode 100644 binaries/src/globplot/biopython-1.50/Bio/cmathfnsmodule.c create mode 100644 binaries/src/globplot/biopython-1.50/Bio/cstringfnsmodule.c create mode 100644 binaries/src/globplot/biopython-1.50/Bio/csupport.c create mode 100644 binaries/src/globplot/biopython-1.50/Bio/csupport.h create mode 100644 binaries/src/globplot/biopython-1.50/Bio/distance.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/kNN.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/listfns.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/mathfns.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/stringfns.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/trie.c create mode 100644 binaries/src/globplot/biopython-1.50/Bio/trie.h create mode 100644 binaries/src/globplot/biopython-1.50/Bio/triefind.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/triemodule.c create mode 100644 binaries/src/globplot/biopython-1.50/Bio/utils.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/embl.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/empty.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/fasta.py create mode 100644 binaries/src/globplot/biopython-1.50/Bio/writers/__init__.py create mode 100644 binaries/src/globplot/biopython-1.50/CONTRIB create mode 100644 binaries/src/globplot/biopython-1.50/LICENSE create mode 100644 binaries/src/globplot/biopython-1.50/PKG-INFO create mode 100644 binaries/src/globplot/biopython-1.50/README create mode 100644 binaries/src/globplot/sav_gol diff --git a/binaries/src/globplot/biopython-1.50/Bio/Alphabet/IUPAC.py b/binaries/src/globplot/biopython-1.50/Bio/Alphabet/IUPAC.py new file mode 100644 index 0000000..bf5a1e7 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Alphabet/IUPAC.py @@ -0,0 +1,127 @@ +# Copyright 2000-2001 by Andrew Dalke. +# Revisions copyright 2008 by Peter Cock. +# All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Standard nucleotide and protein alphabets defined by IUPAC.""" + +from Bio import Alphabet +from Bio.Data import IUPACData + +##################### Protein + +# From the IUPAC definition at: +# http://www.chem.qmw.ac.uk/iupac/AminoAcid/A2021.html#AA21 + +assert IUPACData.extended_protein_letters == IUPACData.extended_protein_letters.upper() +class ExtendedIUPACProtein(Alphabet.ProteinAlphabet): + """Extended uppercase IUPAC protein single letter alphabet including X etc. + + In addition to the standard 20 single letter protein codes, this includes: + + B = "Asx"; Aspartic acid (R) or Asparagine (N) + X = "Xxx"; Unknown or 'other' amino acid + Z = "Glx"; Glutamic acid (E) or Glutamine (Q) + J = "Xle"; Leucine (L) or Isoleucine (I), used in mass-spec (NMR) + U = "Sec"; Selenocysteine + O = "Pyl"; Pyrrolysine + + This alphabet is not intended to be used with X for Selenocysteine + (an ad-hoc standard prior to the IUPAC adoption of U instead). + """ + letters = IUPACData.extended_protein_letters + +extended_protein = ExtendedIUPACProtein() + +assert IUPACData.protein_letters == IUPACData.protein_letters.upper() +class IUPACProtein(ExtendedIUPACProtein): + """Uppercase IUPAC protein single letter alphabet of the 20 standard amino acids.""" + letters = IUPACData.protein_letters + +protein = IUPACProtein() + +##################### DNA + +# The next two are the IUPAC definitions, from: +# http://www.chem.qmw.ac.uk/iubmb/misc/naseq.html +class IUPACAmbiguousDNA(Alphabet.DNAAlphabet): + """Uppercase IUPAC ambiguous DNA.""" + letters = IUPACData.ambiguous_dna_letters + +ambiguous_dna = IUPACAmbiguousDNA() + +class IUPACUnambiguousDNA(IUPACAmbiguousDNA): + """Uppercase IUPAC unambiguous DNA (letters GATC only).""" + letters = IUPACData.unambiguous_dna_letters + +unambiguous_dna = IUPACUnambiguousDNA() + + +# Also from the URL, but not part of the standard +class ExtendedIUPACDNA(Alphabet.DNAAlphabet): + """Extended IUPAC DNA alphabet. + + In addition to the standard letter codes GATC, this includes: + + B = 5-bromouridine + D = 5,6-dihydrouridine + S = thiouridine + W = wyosine + """ + letters = IUPACData.extended_dna_letters + +extended_dna = ExtendedIUPACDNA() + +##################### RNA + +class IUPACAmbiguousRNA(Alphabet.RNAAlphabet): + """Uppercase IUPAC ambiguous RNA.""" + letters = IUPACData.ambiguous_rna_letters + +ambiguous_rna = IUPACAmbiguousRNA() + +class IUPACUnambiguousRNA(IUPACAmbiguousRNA): + """Uppercase IUPAC unambiguous RNA (letters GAUC only).""" + letters = IUPACData.unambiguous_rna_letters + +unambiguous_rna = IUPACUnambiguousRNA() + +# are there extended forms? +#class ExtendedIUPACRNA(Alphabet.RNAAlphabet): +# letters = extended_rna_letters +# # B == 5-bromouridine +# # D == 5,6-dihydrouridine +# # S == thiouridine +# # W == wyosine + + +# We need to load the property resolution information, but we need to +# wait until after the systems have been loaded. (There's a nasty loop +# where, eg, translation objects need an alphabet, which need to be +# assocated with translators.) + +from Bio.PropertyManager import default_manager + +def _bootstrap(manager, klass, property): + assert manager is default_manager + del default_manager.class_resolver[IUPACProtein] + del default_manager.class_resolver[ExtendedIUPACProtein] + del default_manager.class_resolver[IUPACAmbiguousDNA] + del default_manager.class_resolver[IUPACUnambiguousDNA] + del default_manager.class_resolver[ExtendedIUPACDNA] + del default_manager.class_resolver[IUPACAmbiguousRNA] + del default_manager.class_resolver[IUPACUnambiguousRNA] + + from Bio.Encodings import IUPACEncoding + + return manager.resolve_class(klass, property) + +default_manager.class_resolver[IUPACProtein] = _bootstrap +default_manager.class_resolver[ExtendedIUPACProtein] = _bootstrap +default_manager.class_resolver[IUPACAmbiguousDNA] = _bootstrap +default_manager.class_resolver[IUPACUnambiguousDNA] = _bootstrap +default_manager.class_resolver[ExtendedIUPACDNA] = _bootstrap +default_manager.class_resolver[IUPACAmbiguousRNA] = _bootstrap +default_manager.class_resolver[IUPACUnambiguousRNA] = _bootstrap diff --git a/binaries/src/globplot/biopython-1.50/Bio/Alphabet/IUPAC.pyc b/binaries/src/globplot/biopython-1.50/Bio/Alphabet/IUPAC.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74989a72f5a3a81b5368a68cabacce3db988e67c GIT binary patch literal 3871 zcmcH*Yi|=raPI6lv9a?Y5RwMk6|IWXk{BwWQUOtiL?{)6^nz9DFV^{P6CXL>TDNP0 zCHkfGSGE7Dzo0X7=d+y#8iHzj$NQL_*?CX*?@Fmtc(CI^`pMz{k2v(77Jv`XL}tgz{^% z2(n1CW{K}!Na3jzkbIG^7qaVRkY&DJ0e_kq%reGIhA{_njxpxJM`m+;T*{6YKrZm{ zMevb1zAzzB&M+21E;7a?@GFdQnQ6>s7)v0R7~=}~$Zm;`=d&6Ypl1-&Q>0|4h&zn2 z!zr|maJM!xiUcQ2vr;XZ$e$|eqd1%W~7(HRihAaf9)BTa#`pAg=%N2El**ORg5 zYMHzyd$10o!%&J)8YAQE`67tCuNe^H7%t^>Jh|Iq9h|InWWu&}; zCO?tm zC7Z2xls7U}FAfknH6S|~qNpC5RUL>1vcXrn{@MmVvsxfeBgbvEBDX7BEmPt}SNZ*r zmeaiSm51|5cV5O5nYwc;^@vvDY?`m7^D5%O2$&sQ);g*yb*=Z>#7cL9sHW9{sci*% z``PBx_1hbFV)wY#4peVoIx51AySHkafvOF|tCqxFZ{QH|MI1VhpgfBiwTzQb46j`z z(fGKiLgiA-P^b*UX5_1SgB9&ifIAqv;N%F4};IOaydTY0Sj*Rj-(m5%zJ0vNP<_AQ$jCV8KmLIu8H+a7wva@!Vd-@Lc(2c3Z* zPaN&z5mtkVycxpU2|3f?=%@4TCWIV)Y zi3f6x=b0cB*r&zA`9%4zctEP-@zD7AcsP@Y7g4;%xQIzBL-`LIDz_h+R@aT(L+oWJ zJ5q9_|HnA=F@m?4EG>97ll09t_szHwBdQn+iLpqE zajJO0K~3_bbZmrYiNiaTF`R1zsMJ)+*ogyB!B0-VpxKuMz9qmY>>gw Nucleotide, and Nucleotide+Protein-> generic single + letter. These DO NOT raise an exception!""" + common = None + for alpha in alphabets : + a = _get_base_alphabet(alpha) + if common is None : + common = a + elif common == a : + pass + elif isinstance(a, common.__class__) : + pass + elif isinstance(common, a.__class__) : + common = a + elif isinstance(a, NucleotideAlphabet) \ + and isinstance(common, NucleotideAlphabet) : + #e.g. Give a mix of RNA and DNA alphabets + common = generic_nucleotide + elif isinstance(a, SingleLetterAlphabet) \ + and isinstance(common, SingleLetterAlphabet) : + #This is a pretty big mis-match! + common = single_letter_alphabet + else : + #We have a major mis-match... take the easy way out! + return generic_alphabet + if common is None : + #Given NO alphabets! + return generic_alphabet + return common + +def _consensus_alphabet(alphabets) : + """Returns a common but often generic alphabet object (PRIVATE). + + Note that DNA+RNA -> Nucleotide, and Nucleotide+Protein-> generic single + letter. These DO NOT raise an exception! + + This is aware of Gapped and HasStopCodon and new letters added by + other AlphabetEncoders. This WILL raise an exception if more than + one gap character or stop symbol is present.""" + base = _consensus_base_alphabet(alphabets) + gap = None + stop = None + new_letters = "" + for alpha in alphabets : + #Gaps... + if not hasattr(alpha, "gap_char") : + pass + elif gap is None : + gap = alpha.gap_char + elif gap == alpha.gap_char : + pass + else : + raise ValueError("More than one gap character present") + #Stops... + if not hasattr(alpha, "stop_symbol") : + pass + elif stop is None : + stop = alpha.stop_symbol + elif stop == alpha.stop_symbol : + pass + else : + raise ValueError("More than one stop symbol present") + #New letters... + if hasattr(alpha, "new_letters") : + for letter in alpha.new_letters : + if letter not in new_letters \ + and letter != gap and letter != stop : + new_letters += letter + + alpha = base + if new_letters : + alpha = AlphabetEncoder(alpha, new_letters) + if gap : + alpha = Gapped(alpha, gap_char=gap) + if stop : + alpha = HasStopCodon(alpha, stop_symbol=stop) + return alpha + +def _check_type_compatible(alphabets) : + """Returns True except for DNA+RNA or Nucleotide+Protein (PRIVATE). + + This relies on the Alphabet subclassing hierarchy. It does not + check things like gap characters or stop symbols.""" + dna, rna, nucl, protein = False, False, False, False + for alpha in alphabets : + a = _get_base_alphabet(alpha) + if isinstance(a, DNAAlphabet) : + dna = True + nucl = True + if rna or protein : return False + elif isinstance(a, RNAAlphabet) : + rna = True + nucl = True + if dna or protein : return False + elif isinstance(a, NucleotideAlphabet) : + nucl = True + if protein : return False + elif isinstance(a, ProteinAlphabet) : + protein = True + if nucl : return False + return True diff --git a/binaries/src/globplot/biopython-1.50/Bio/Alphabet/__init__.pyc b/binaries/src/globplot/biopython-1.50/Bio/Alphabet/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d43e071637bd167e1a8cb30f9c0c3916694f3477 GIT binary patch literal 8466 zcmdT}&2t<_74O+yt+cWvE3qv*4kYb><8@+7h9Z2WNaC;926=5pA`T|0snJf$JNE9( zdU_OTQ!$4m#gPjKuKXJmca9WCid&AHps3;q=J$I&GrL;b2~{ygu~PT+*SvoHzWdGK z?~C()e)q3eyQ=ur@q7!#-t?4;l)8=DQ`@L(>QkjYQR=6+J!L$$Zr4;)XM5XIQKOp1 zP+g`QDw<)N)6HrcLo+hnRMBiXJy%U*XjZ1@RMcXdZ_iiL7;4G%yowH#(+kx!h7QQ| zf{GSP;7ir?qKXcd2rpNoODcLsj`(afbWlZylsPDyAFf81Rdl2rU8zQ&QPEL$Za=G{ zW70UJqUWS>SVhOBaYRMWtBv+oNZDVZI^Q4m!Yz|qy<<(J<3w+m`#RmaYq}USc~|GD zj!d^7W=30cf5#+Uqw~?w=rD2=Jla zj*~byI@uX)nM|kK+Qx$z@bzw*MF5r0P2klj&8I$*RibIr{%T0!Ih@Jowv{k0uaF3 zAQ0SwAPEO12-<||vudNbDqlxyt(OjrT^oLoXQ}PQ$(l{K^R-K{y?p(``q^)+ek%)i z*S6wxILdozf`Ql0tzC%IwUQZYK|mS>!75ge8bOemVHO0>peYPjp3zq9s1y4?4+~^clrrS3zY}{DCym|S}(_pGDe!j_f zvIMW{tu*bMFj>`F7@nDa47h0`AWHnT-Psa?#mSutMlUv5n00$2tnQD_i1iG=1LKf#Sg&ZPw@e4^kJ>Ubw<$#y=*)gp@Jil&FZ zTC1LO;|-9%Z`MUcOV)HTE5SVjI$K!$5wcFdJ>;*-jU#ZypsJ z(tJ{EN`Vm(zPhn_QOxL=aZeT%?s#zugUw!M3dt>Gro!MDmWtrMH-!NLg9z?GoITt; ze0n6V8%`S@Iub`5!NtO*!hgb9!cD?K!ZpGvuF!YFYruvpU}o z-P7*L^Ww-B{h>Us^mpWWvuBF_NS;??x9%GBnQ+#N_tuSDe;o=c@NFVb9+>?reb+yq z-I4wV%pu1?o1)*9=R0BFjb{#zcSDEg=E$wP(%%!yHo*%~;3w)XZO7Xv`*pXJ@4{?$ zu|R&6AvuF?B@v&(ihv&ytI2-Yh^3 zZ!OOLZcC+ymFN1HKFayJO6HXpiC;$tjnaH@6Y-&8;r7IE=cGVlc7xK!k{DvroY?YE z{wk|ARAZwvw(zBk-oU)2EmU3$f6LzC+M}+S@(P3zYiN+qb}Ws z+ODgWGQ=XKPTKa#?2JB{opz2zkl=xwgpSEB6Rf7N6ORbtcqA6$T0w_+0UZ|t+TWn} zrQ)A5xQ(quAA@1v3=9H{NN=Z^1QlDMHF`=#&WvLZ34wRxejm}-ZaA_!lZZ;6)9tsz zzBQ*;W8esH$9jS_kBKiN66j)?iz^z4E6A7$?LNouYpgy$a-b9QQHsc+HQSnLHF5&G z5)OwZ;wJJVZ!Vx%a=B7*Dz}RmoN|lJ6cL3W!$nq_T?vVHuOp^_ctF&ly`R*f2u{ag z9QLcNF9^zML=q?%qRMx|VbJY`*|e^H7Yn9Eg|FX3an$k-jRR+%N`&QQG-ysr@uhwq z0$L)%PW>Ru(90s6ZUUPxB~DK@oOWR_l-mr3xUwMx9tYDP6nbG6c5zE9WA^PZ?%PT< z<86^&7yXwuQdfa_FZ=*EJ-rXmiR$H!Q(%c}N&K;Saq+4sweapqf}B>YVSr3YnHJ9#9jwI8> zTA&)((O@g>PYd(wm>!?o9ht)XAzve*R4L4Lv{^2q*dL(62`RkJX+p

H*qv*QIP9 zg==vuW<>6h1%Y>=$HAI1?gtnphDAI}cQBmbEtx5l+4BWUZtkW<5{SYS=Hi2JiyjPv z;OCeYlcT&N-lBK(DQINv|3)LpaP1iWW%O|Z%;U9@e3{_b99i{g(1>6|)V_4iWU>HV&^27v@2SQ zNLG}Nx=3{C_D%oVJLfkqpI)^L`mZG)g#9=gXG*ryog_lUVms~*FP?~YE%e3rmY!6( z@XK0+>T#O5@QVj$`o_^;qMJ7{G_4`L1~K3Y7)k`CCH^HwC1HgW7H-jXwd^f|%zW+=cPNluvt2JH-Y=>>H+i~|4nswR;6tKLkBm{x=NpS9Zxz-=)jsb)OT?5 z`=3GoKU25vpTzSd^sY`4V4l%%zDVfkfD#xjR4`q^%o1mC8^8vicwRZ_DVaPdlh~a% z+iG$KaR$AJ#qfWkyrm#LNpJr*nkITFRe3>#&A@jOeA6f`0=|(JUM&6zs82FiHn8Ao7 z`6_e)3}EbTzlQ312?$C0X&}Wb67ty-k1U?tpj^u2NvBPV;9p}!3m^$X8e?an7+=6i zyUaH{v?N@_Hb~6a3$5fWh@oW7k?q=Xc@05CESxiq?z+fZfy5!sQ;W^H&arh?KVo&0 z6|K)>lRJknF=2vA$@?vaEukqmp#`Sdoeioj!}2WR56{}dBfWW8sZ+J%HP|cIr&(`B zin#B6cIlr03|>_K+YR+zgQf{K3c5_Y^e5-m|0s58mTE;42CLGN5Xw2wm!Xg1Kv_fU z>S{PEI-b&Z;(mSqAEMlJ$k07_Q#$D4Erbj1Ef~AceQQzPdd}@7X66atmnIjAp_yJt zLxWw5tM%_dhSHaBW;>>EYm#OueJF(aQJ*;x!cBPcFaA8GtItwUvuk;E@fAfXFVb+r zR`=t3QyPKqUig_FlAUrfc3S4aw< znR=h_{UuZr;w{dbc}Rbg)lXQ7c)!DSD6Vq3OL}f3Pw=@d;8$Eo`e2AqW~*=hf*E^` z%i(NZDE#ZP$oZ05fP7Qp%hqf$GK+w;(= 0: + output = "%s" % self.names[0] + if self.value is not None: + output += "=%s " % self.value + else: + output += " " + # now short options + elif self.names[0].find("-") >= 0: + output = "%s " % self.names[0] + if self.value is not None: + output += "%s " % self.value + else: + raise ValueError("Unrecognized option type: %s" % self.names[0]) + + return output + +class _Argument(_AbstractParameter): + """Represent an argument on a commandline. + """ + def __str__(self): + if self.value is not None: + return "%s " % self.value + else: + return " " diff --git a/binaries/src/globplot/biopython-1.50/Bio/Data/CodonTable.py b/binaries/src/globplot/biopython-1.50/Bio/Data/CodonTable.py new file mode 100644 index 0000000..16aaccf --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Data/CodonTable.py @@ -0,0 +1,802 @@ +#TODO - Remove this work around once we drop python 2.3 support +try: + set = set +except NameError: + from sets import Set as set + +from Bio import Alphabet +from Bio.Alphabet import IUPAC +from Bio.Data import IUPACData + +unambiguous_dna_by_name = {} +unambiguous_dna_by_id = {} +unambiguous_rna_by_name = {} +unambiguous_rna_by_id = {} +generic_by_name = {} # unambiguous DNA or RNA +generic_by_id = {} # unambiguous DNA or RNA +ambiguous_generic_by_name = {} # ambiguous DNA or RNA +ambiguous_generic_by_id = {} # ambiguous DNA or RNA + +# standard IUPAC unambiguous codons +standard_dna_table = None +standard_rna_table = None + +# In the future, the back_table could return a statistically +# appropriate distribution of codons, so do not cache the results of +# back_table lookups! + +class TranslationError(Exception): + pass + +class CodonTable: + nucleotide_alphabet = Alphabet.generic_nucleotide + protein_alphabet = Alphabet.generic_protein + + forward_table = {} # only includes codons which actually code + back_table = {} # for back translations + start_codons = [] + stop_codons = [] + # Not always called from derived classes! + def __init__(self, nucleotide_alphabet = nucleotide_alphabet, + protein_alphabet = protein_alphabet, + forward_table = forward_table, back_table = back_table, + start_codons = start_codons, stop_codons = stop_codons): + self.nucleotide_alphabet = nucleotide_alphabet + self.protein_alphabet = protein_alphabet + self.forward_table = forward_table + self.back_table = back_table + self.start_codons = start_codons + self.stop_codons = stop_codons + + def __str__(self) : + """Returns a simple text representation of the codon table + + e.g. + >>> import Bio.Data.CodonTable + >>> print Bio.Data.CodonTable.standard_dna_table + >>> print Bio.Data.CodonTable.generic_by_id[1]""" + + if self.id : + answer = "Table %i" % self.id + else : + answer = "Table ID unknown" + if self.names : + answer += " " + ", ".join(filter(None, self.names)) + + #Use the main four letters (and the conventional ordering) + #even for ambiguous tables + letters = self.nucleotide_alphabet.letters + if isinstance(self.nucleotide_alphabet, Alphabet.DNAAlphabet) \ + or (letters is not None and "T" in letters) : + letters = "TCAG" + else : + #Should be either RNA or generic nucleotides, + #e.g. Bio.Data.CodonTable.generic_by_id[1] + letters = "UCAG" + + #Build the table... + answer=answer + "\n\n |" + "|".join( \ + [" %s " % c2 for c2 in letters] \ + ) + "|" + answer=answer + "\n--+" \ + + "+".join(["---------" for c2 in letters]) + "+--" + for c1 in letters : + for c3 in letters : + line = c1 + " |" + for c2 in letters : + codon = c1+c2+c3 + line = line + " %s" % codon + if codon in self.stop_codons : + line = line + " Stop|" + else : + try : + amino = self.forward_table[codon] + except KeyError : + amino = "?" + except TranslationError : + amino = "?" + if codon in self.start_codons : + line = line + " %s(s)|" % amino + else : + line = line + " %s |" % amino + line = line + " " + c3 + answer = answer + "\n"+ line + answer=answer + "\n--+" \ + + "+".join(["---------" for c2 in letters]) + "+--" + return answer + +def make_back_table(table, default_stop_codon): + # ONLY RETURNS A SINGLE CODON + # Do the sort so changes in the hash implementation won't affect + # the result when one amino acid is coded by more than one codon. + back_table = {} + keys = table.keys() ; keys.sort() + for key in keys: + back_table[table[key]] = key + back_table[None] = default_stop_codon + return back_table + + +class NCBICodonTable(CodonTable): + nucleotide_alphabet = Alphabet.generic_nucleotide + protein_alphabet = IUPAC.protein + + def __init__(self, id, names, table, start_codons, stop_codons): + self.id = id + self.names = names + self.forward_table = table + self.back_table = make_back_table(table, stop_codons[0]) + self.start_codons = start_codons + self.stop_codons = stop_codons + + +class NCBICodonTableDNA(NCBICodonTable): + nucleotide_alphabet = IUPAC.unambiguous_dna + +class NCBICodonTableRNA(NCBICodonTable): + nucleotide_alphabet = IUPAC.unambiguous_rna + + + +def register_ncbi_table(name, alt_name, id, + table, start_codons, stop_codons): + names = name.split("; ") + + dna = NCBICodonTableDNA(id, names + [alt_name], table, start_codons, + stop_codons) + # replace all T's with U's for the RNA tables + rna_table = {} + generic_table = {} + for codon, val in table.items(): + generic_table[codon] = val + codon = codon.replace("T", "U") + generic_table[codon] = val + rna_table[codon] = val + rna_start_codons = [] + generic_start_codons = [] + for codon in start_codons: + generic_start_codons.append(codon) + codon = codon.replace("T", "U") + generic_start_codons.append(codon) + rna_start_codons.append(codon) + rna_stop_codons = [] + generic_stop_codons = [] + for codon in stop_codons: + generic_stop_codons.append(codon) + codon = codon.replace("T", "U") + generic_stop_codons.append(codon) + rna_stop_codons.append(codon) + + generic = NCBICodonTable(id, names + [alt_name], generic_table, + generic_start_codons, generic_stop_codons) + rna = NCBICodonTableRNA(id, names + [alt_name], rna_table, + rna_start_codons, rna_stop_codons) + + if id == 1: + global standard_dna_table, standard_rna_table + standard_dna_table = dna + standard_rna_table = rna + + unambiguous_dna_by_id[id] = dna + unambiguous_rna_by_id[id] = rna + generic_by_id[id] = generic + + if alt_name is not None: + names.append(alt_name) + + for name in names: + unambiguous_dna_by_name[name] = dna + unambiguous_rna_by_name[name] = rna + generic_by_name[name] = generic + +### These tables created from the data file +### ftp://ncbi.nlm.nih.gov/entrez/misc/data/gc.prt +### using the following: +##import re +##for line in open("gc.prt").readlines(): +## if line[:2] == " {": +## names = [] +## id = None +## aa = None +## start = None +## bases = [] +## elif line[:6] == " name": +## names.append(re.search('"([^"]*)"', line).group(1)) +## elif line[:8] == " name": +## names.append(re.search('"(.*)$', line).group(1)) +## elif line == ' Mitochondrial; Mycoplasma; Spiroplasma" ,\n': +## names[-1] = names[-1] + " Mitochondrial; Mycoplasma; Spiroplasma" +## elif line[:4] == " id": +## id = int(re.search('(\d+)', line).group(1)) +## elif line[:10] == " ncbieaa ": +## aa = line[12:12+64] +## elif line[:10] == " sncbieaa": +## start = line[12:12+64] +## elif line[:9] == " -- Base": +## bases.append(line[12:12+64]) +## elif line[:2] == " }": +## assert names != [] and id is not None and aa is not None +## assert start is not None and bases != [] +## if len(names) == 1: +## names.append(None) +## print "register_ncbi_table(name = %s," % repr(names[0]) +## print " alt_name = %s, id = %d", % \ +## (repr(names[1]), id) +## print " table = {" +## s = " " +## for i in range(64): +## if aa[i] != "*": +## t = " '%s%s%s': '%s'," % (bases[0][i], bases[1][i], +## bases[2][i], aa[i]) +## if len(s) + len(t) > 75: +## print s +## s = " " + t +## else: +## s = s + t +## print s, "}," + +## s = " stop_codons = [" +## for i in range(64): +## if aa[i] == "*": +## t = " '%s%s%s'," % (bases[0][i], bases[1][i], bases[2][i]) +## if len(s) + len(t) > 75: +## print s +## s = " " + t +## else: +## s = s + t +## print s, "]," + +## s = " start_codons = [" +## for i in range(64): +## if start[i] == "M": +## t = " '%s%s%s'," % (bases[0][i], bases[1][i], bases[2][i]) +## if len(s) + len(t) > 75: +## print s +## s = " " + t +## else: +## s = s + t +## print s, "]" +## print " )" +## elif line[:2] == "--" or line == "\n" or line == "}\n" or \ +## line == 'Genetic-code-table ::= {\n': +## pass +## else: +## raise Exception("Unparsed: " + repr(line)) + +register_ncbi_table(name = 'Standard', + alt_name = 'SGC0', id = 1, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGG': 'W', 'CTT': 'L', 'CTC': 'L', + 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', 'CCA': 'P', + 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', 'CAG': 'Q', + 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', 'ATT': 'I', + 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', 'ACT': 'T', 'ACC': 'T', + 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', 'AAA': 'K', + 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', 'AGG': 'R', + 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', 'GCT': 'A', + 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', 'GAC': 'D', + 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', 'GGA': 'G', + 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', 'TGA', ], + start_codons = [ 'TTG', 'CTG', 'ATG', ] + ) +register_ncbi_table(name = 'Vertebrate Mitochondrial', + alt_name = 'SGC1', id = 2, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGA': 'W', 'TGG': 'W', 'CTT': 'L', + 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'M', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'GTT': 'V', + 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', 'GCT': 'A', 'GCC': 'A', + 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', 'GAC': 'D', 'GAA': 'E', + 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', 'AGA', 'AGG', ], + start_codons = [ 'ATT', 'ATC', 'ATA', 'ATG', 'GTG', ] + ) +register_ncbi_table(name = 'Yeast Mitochondrial', + alt_name = 'SGC2', id = 3, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGA': 'W', 'TGG': 'W', 'CTT': 'T', + 'CTC': 'T', 'CTA': 'T', 'CTG': 'T', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'M', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', + 'AGG': 'R', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', + 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', + 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', + 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', ], + start_codons = [ 'ATG', ] + ) +register_ncbi_table(name = 'Mold Mitochondrial; Protozoan Mitochondrial; Coelenterate Mitochondrial; Mycoplasma; Spiroplasma', + alt_name = 'SGC3', id = 4, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGA': 'W', 'TGG': 'W', 'CTT': 'L', + 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', + 'AGG': 'R', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', + 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', + 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', + 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', ], + start_codons = [ 'TTA', 'TTG', 'CTG', 'ATT', 'ATC', + 'ATA', 'ATG', 'GTG', ] + ) +register_ncbi_table(name = 'Invertebrate Mitochondrial', + alt_name = 'SGC4', id = 5, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGA': 'W', 'TGG': 'W', 'CTT': 'L', + 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'M', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'S', + 'AGG': 'S', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', + 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', + 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', + 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', ], + start_codons = [ 'TTG', 'ATT', 'ATC', 'ATA', 'ATG', + 'GTG', ] + ) +register_ncbi_table(name = 'Ciliate Nuclear; Dasycladacean Nuclear; Hexamita Nuclear', + alt_name = 'SGC5', id = 6, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TAA': 'Q', 'TAG': 'Q', 'TGT': 'C', 'TGC': 'C', 'TGG': 'W', + 'CTT': 'L', 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', + 'CCC': 'P', 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', + 'CAA': 'Q', 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', + 'CGG': 'R', 'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', + 'ACT': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', + 'AAC': 'N', 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', + 'AGA': 'R', 'AGG': 'R', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', + 'GTG': 'V', 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', + 'GAT': 'D', 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', + 'GGC': 'G', 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TGA', ], + start_codons = [ 'ATG', ] + ) +register_ncbi_table(name = 'Echinoderm Mitochondrial', + alt_name = 'SGC8', id = 9, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGA': 'W', 'TGG': 'W', 'CTT': 'L', + 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'N', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'S', + 'AGG': 'S', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', + 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', + 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', + 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', ], + start_codons = [ 'ATG', ] + ) +register_ncbi_table(name = 'Euplotid Nuclear', + alt_name = 'SGC9', id = 10, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGA': 'C', 'TGG': 'W', 'CTT': 'L', + 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', + 'AGG': 'R', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', + 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', + 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', + 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', ], + start_codons = [ 'ATG', ] + ) +register_ncbi_table(name = 'Bacterial', + alt_name = None, id = 11, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGG': 'W', 'CTT': 'L', 'CTC': 'L', + 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', 'CCA': 'P', + 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', 'CAG': 'Q', + 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', 'ATT': 'I', + 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', 'ACT': 'T', 'ACC': 'T', + 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', 'AAA': 'K', + 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', 'AGG': 'R', + 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', 'GCT': 'A', + 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', 'GAC': 'D', + 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', 'GGA': 'G', + 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', 'TGA', ], + start_codons = [ 'TTG', 'CTG', 'ATT', 'ATC', 'ATA', + 'ATG', 'GTG', ] + ) +register_ncbi_table(name = 'Alternative Yeast Nuclear', + alt_name = None, id = 12, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGG': 'W', 'CTT': 'L', 'CTC': 'L', + 'CTA': 'L', 'CTG': 'S', 'CCT': 'P', 'CCC': 'P', 'CCA': 'P', + 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', 'CAG': 'Q', + 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', 'ATT': 'I', + 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', 'ACT': 'T', 'ACC': 'T', + 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', 'AAA': 'K', + 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', 'AGG': 'R', + 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', 'GCT': 'A', + 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', 'GAC': 'D', + 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', 'GGA': 'G', + 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', 'TGA', ], + start_codons = [ 'CTG', 'ATG', ] + ) +register_ncbi_table(name = 'Ascidian Mitochondrial', + alt_name = None, id = 13, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TGT': 'C', 'TGC': 'C', 'TGA': 'W', 'TGG': 'W', 'CTT': 'L', + 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'M', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'G', + 'AGG': 'G', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', + 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', + 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', + 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TAG', ], + start_codons = [ 'ATG', ] + ) +register_ncbi_table(name = 'Flatworm Mitochondrial', + alt_name = None, id = 14, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TAA': 'Y', 'TGT': 'C', 'TGC': 'C', 'TGA': 'W', 'TGG': 'W', + 'CTT': 'L', 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', + 'CCC': 'P', 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', + 'CAA': 'Q', 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', + 'CGG': 'R', 'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', + 'ACT': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', + 'AAC': 'N', 'AAA': 'N', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', + 'AGA': 'S', 'AGG': 'S', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', + 'GTG': 'V', 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', + 'GAT': 'D', 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', + 'GGC': 'G', 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAG', ], + start_codons = [ 'ATG', ] + ) +register_ncbi_table(name = 'Blepharisma Macronuclear', + alt_name = None, id = 15, + table = { + 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', 'TCT': 'S', + 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'TAT': 'Y', 'TAC': 'Y', + 'TAG': 'Q', 'TGT': 'C', 'TGC': 'C', 'TGG': 'W', 'CTT': 'L', + 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'CCT': 'P', 'CCC': 'P', + 'CCA': 'P', 'CCG': 'P', 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', + 'CAG': 'Q', 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', + 'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', 'ACT': 'T', + 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', 'AAT': 'N', 'AAC': 'N', + 'AAA': 'K', 'AAG': 'K', 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', + 'AGG': 'R', 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', + 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', 'GAT': 'D', + 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', 'GGT': 'G', 'GGC': 'G', + 'GGA': 'G', 'GGG': 'G', }, + stop_codons = [ 'TAA', 'TGA', ], + start_codons = [ 'ATG', ] + ) + +######### Deal with ambiguous forward translations + +class AmbiguousCodonTable(CodonTable): + def __init__(self, codon_table, + ambiguous_nucleotide_alphabet, + ambiguous_nucleotide_values, + ambiguous_protein_alphabet, + ambiguous_protein_values): + CodonTable.__init__(self, + ambiguous_nucleotide_alphabet, + ambiguous_protein_alphabet, + AmbiguousForwardTable(codon_table.forward_table, + ambiguous_nucleotide_values, + ambiguous_protein_values), + codon_table.back_table, + + # These two are WRONG! I need to get the + # list of ambiguous codons which code for + # the stop codons XXX + list_ambiguous_codons(codon_table.start_codons, ambiguous_nucleotide_values), + list_ambiguous_codons(codon_table.stop_codons, ambiguous_nucleotide_values) + ) + self._codon_table = codon_table + + # Be sneaky and forward attribute lookups to the original table. + # This lets us get the names, if the original table is an NCBI + # table. + def __getattr__(self, name): + return getattr(self._codon_table, name) + +def list_possible_proteins(codon, forward_table, ambiguous_nucleotide_values): + c1, c2, c3 = codon + x1 = ambiguous_nucleotide_values[c1] + x2 = ambiguous_nucleotide_values[c2] + x3 = ambiguous_nucleotide_values[c3] + possible = {} + stops = [] + for y1 in x1: + for y2 in x2: + for y3 in x3: + try: + possible[forward_table[y1+y2+y3]] = 1 + except KeyError: + # If tripping over a stop codon + stops.append(y1+y2+y3) + if stops: + if possible.keys(): + raise TranslationError("ambiguous codon '%s' codes " % codon \ + + "for both proteins and stop codons") + # This is a true stop codon - tell the caller about it + raise KeyError(codon) + return possible.keys() + +def list_ambiguous_codons(codons, ambiguous_nucleotide_values): + """Extends a codon list to include all possible ambigous codons. + + e.g. ['TAG', 'TAA'] -> ['TAG', 'TAA', 'TAR'] + ['UAG', 'UGA'] -> ['UAG', 'UGA', 'URA'] + + Note that ['TAG', 'TGA'] -> ['TAG', 'TGA'], this does not add 'TRR'. + Thus only two more codons are added in the following: + + e.g. ['TGA', 'TAA', 'TAG'] -> ['TGA', 'TAA', 'TAG', 'TRA', 'TAR'] + + Returns a new (longer) list of codon strings. + """ + + #Note ambiguous_nucleotide_values['R'] = 'AG' (etc) + #This will generate things like 'TRR' from ['TAG', 'TGA'], which + #we don't want to include: + c1_list = [letter for (letter, meanings) \ + in ambiguous_nucleotide_values.iteritems() \ + if set([codon[0] for codon in codons]).issuperset(set(meanings))] + c2_list = [letter for (letter, meanings) \ + in ambiguous_nucleotide_values.iteritems() \ + if set([codon[1] for codon in codons]).issuperset(set(meanings))] + c3_list = [letter for (letter, meanings) \ + in ambiguous_nucleotide_values.iteritems() \ + if set([codon[2] for codon in codons]).issuperset(set(meanings))] + set2 = set([codon[1] for codon in codons]) + set3 = set([codon[2] for codon in codons]) + candidates = set([c1+c2+c3 for c1 in c1_list for c2 in c2_list for c3 in c3_list]) + candidates.difference_update(codons) + answer = codons[:] #copy + #print "Have %i new candidates" % len(candidates) + for ambig_codon in candidates : + wanted = True + #e.g. 'TRR' -> 'TAA', 'TAG', 'TGA', 'TGG' + for codon in [c1+c2+c3 \ + for c1 in ambiguous_nucleotide_values[ambig_codon[0]] \ + for c2 in ambiguous_nucleotide_values[ambig_codon[1]] \ + for c3 in ambiguous_nucleotide_values[ambig_codon[2]]]: + if codon not in codons : + #This ambiguous codon can code for a non-stop, exclude it! + wanted=False + #print "Rejecting %s" % ambig_codon + continue + if wanted : + answer.append(ambig_codon) + return answer +assert list_ambiguous_codons(['TGA', 'TAA'],IUPACData.ambiguous_dna_values) == ['TGA', 'TAA', 'TRA'] +assert list_ambiguous_codons(['TAG', 'TGA'],IUPACData.ambiguous_dna_values) == ['TAG', 'TGA'] +assert list_ambiguous_codons(['TAG', 'TAA'],IUPACData.ambiguous_dna_values) == ['TAG', 'TAA', 'TAR'] +assert list_ambiguous_codons(['UAG', 'UAA'],IUPACData.ambiguous_rna_values) == ['UAG', 'UAA', 'UAR'] +assert list_ambiguous_codons(['TGA', 'TAA', 'TAG'],IUPACData.ambiguous_dna_values) == ['TGA', 'TAA', 'TAG', 'TAR', 'TRA'] + +# Forward translation is "onto", that is, any given codon always maps +# to the same protein, or it doesn't map at all. Thus, I can build +# off of an existing table to produce the ambiguous mappings. +# +# This handles the general case. Perhaps it's overkill? +# >>> t = CodonTable.ambiguous_dna_by_id[1] +# >>> t.forward_table["AAT"] +# 'N' +# >>> t.forward_table["GAT"] +# 'D' +# >>> t.forward_table["RAT"] +# 'B' +# >>> t.forward_table["YTA"] +# 'L' + +class AmbiguousForwardTable: + def __init__(self, forward_table, ambiguous_nucleotide, ambiguous_protein): + self.forward_table = forward_table + + self.ambiguous_nucleotide = ambiguous_nucleotide + self.ambiguous_protein = ambiguous_protein + + inverted = {} + for name, val in ambiguous_protein.items(): + for c in val: + x = inverted.get(c, {}) + x[name] = 1 + inverted[c] = x + for name, val in inverted.items(): + inverted[name] = val.keys() + self._inverted = inverted + + self._cache = {} + + def get(self, codon, failobj = None): + try: + return self.__getitem__(codon) + except KeyError: + return failobj + + def __getitem__(self, codon): + try: + x = self._cache[codon] + except KeyError: + pass + else: + if x is TranslationError: + raise TranslationError(codon) # no unique translation + if x is KeyError: + raise KeyError(codon) # it's a stop codon + return x + try: + x = self.forward_table[codon] + self._cache[codon] = x + return x + except KeyError: + pass + + # XXX Need to make part of this into a method which returns + # a list of all possible encodings for a codon! + try: + possible = list_possible_proteins(codon, + self.forward_table, + self.ambiguous_nucleotide) + except KeyError: + self._cache[codon] = KeyError + raise KeyError(codon) # stop codon + except TranslationError: + self._cache[codon] = TranslationError + raise TranslationError(codon) # does not code + assert len(possible) > 0, "unambiguous codons must code" + + # Hah! Only one possible protein, so use it + if len(possible) == 1: + self._cache[codon] = possible[0] + return possible[0] + + # See if there's an ambiguous protein encoding for the multiples. + # Find residues which exist in every coding set. + ambiguous_possible = {} + for amino in possible: + for term in self._inverted[amino]: + ambiguous_possible[term] = ambiguous_possible.get(term, 0) + 1 + + n = len(possible) + possible = [] + for amino, val in ambiguous_possible.items(): + if val == n: + possible.append(amino) + + # No amino acid encoding for the results + if len(possible) == 0: + self._cache[codon] = TranslationError + raise TranslationError(codon) # no valid translation + + # All of these are valid, so choose one + # To be unique, sort by smallet ambiguity then alphabetically + # Can get this if "X" encodes for everything. + def _sort(x, y, table = self.ambiguous_protein): + a = cmp(len(table[x]), len(table[y])) + if a == 0: + return cmp(x, y) + return a + possible.sort(_sort) + + x = possible[0] + self._cache[codon] = x + return x + +#Prepare the ambiguous tables for DNA, RNA and Generic (DNA or RNA) +ambiguous_dna_by_name = {} +for key, val in unambiguous_dna_by_name.items(): + ambiguous_dna_by_name[key] = AmbiguousCodonTable(val, + IUPAC.ambiguous_dna, + IUPACData.ambiguous_dna_values, + IUPAC.extended_protein, + IUPACData.extended_protein_values) +ambiguous_dna_by_id = {} +for key, val in unambiguous_dna_by_id.items(): + ambiguous_dna_by_id[key] = AmbiguousCodonTable(val, + IUPAC.ambiguous_dna, + IUPACData.ambiguous_dna_values, + IUPAC.extended_protein, + IUPACData.extended_protein_values) + +ambiguous_rna_by_name = {} +for key, val in unambiguous_rna_by_name.items(): + ambiguous_rna_by_name[key] = AmbiguousCodonTable(val, + IUPAC.ambiguous_rna, + IUPACData.ambiguous_rna_values, + IUPAC.extended_protein, + IUPACData.extended_protein_values) +ambiguous_rna_by_id = {} +for key, val in unambiguous_rna_by_id.items(): + ambiguous_rna_by_id[key] = AmbiguousCodonTable(val, + IUPAC.ambiguous_rna, + IUPACData.ambiguous_rna_values, + IUPAC.extended_protein, + IUPACData.extended_protein_values) + +#The following isn't very elegant, but seems to work nicely. +_merged_values = dict(IUPACData.ambiguous_rna_values.iteritems()) +_merged_values["T"] = "U" + +for key, val in generic_by_name.items(): + ambiguous_generic_by_name[key] = AmbiguousCodonTable(val, + Alphabet.NucleotideAlphabet(), + _merged_values, + IUPAC.extended_protein, + IUPACData.extended_protein_values) + +for key, val in generic_by_id.items(): + ambiguous_generic_by_id[key] = AmbiguousCodonTable(val, + Alphabet.NucleotideAlphabet(), + _merged_values, + IUPAC.extended_protein, + IUPACData.extended_protein_values) +del _merged_values +del key, val + +#Basic sanity test, +for n in ambiguous_generic_by_id.keys() : + assert ambiguous_rna_by_id[n].forward_table["GUU"] == "V" + assert ambiguous_rna_by_id[n].forward_table["GUN"] == "V" + assert ambiguous_rna_by_id[n].forward_table["UUN"] == "X" #F or L + #R = A or G, so URR = UAA or UGA / TRA = TAA or TGA = stop codons + if "UAA" in unambiguous_rna_by_id[n].stop_codons \ + and "UGA" in unambiguous_rna_by_id[n].stop_codons : + try : + print ambiguous_dna_by_id[n].forward_table["TRA"] + assert False, "Should be a stop only" + except KeyError : + pass + assert "URA" in ambiguous_generic_by_id[n].stop_codons + assert "URA" in ambiguous_rna_by_id[n].stop_codons + assert "TRA" in ambiguous_generic_by_id[n].stop_codons + assert "TRA" in ambiguous_dna_by_id[n].stop_codons +del n +assert ambiguous_generic_by_id[1].stop_codons == ambiguous_generic_by_name["Standard"].stop_codons +assert ambiguous_generic_by_id[4].stop_codons == ambiguous_generic_by_name["SGC3"].stop_codons +assert ambiguous_generic_by_id[15].stop_codons == ambiguous_generic_by_name['Blepharisma Macronuclear'].stop_codons diff --git a/binaries/src/globplot/biopython-1.50/Bio/Data/CodonTable.pyc b/binaries/src/globplot/biopython-1.50/Bio/Data/CodonTable.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a5472e060a339bef1ac3cd8d33c7fc08963469a GIT binary patch literal 21526 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 N-6g;KP-6(c{{yUh75M-F literal 0 HcmV?d00001 diff --git a/binaries/src/globplot/biopython-1.50/Bio/Data/IUPACData.py b/binaries/src/globplot/biopython-1.50/Bio/Data/IUPACData.py new file mode 100644 index 0000000..ebd5a12 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Data/IUPACData.py @@ -0,0 +1,209 @@ +# Information about the IUPAC alphabets + +protein_letters = "ACDEFGHIKLMNPQRSTVWY" +extended_protein_letters = "ACDEFGHIKLMNPQRSTVWYBXZJUO" +# B = "Asx"; aspartic acid or asparagine (D or N) +# X = "Xxx"; unknown or 'other' amino acid +# Z = "Glx"; glutamic acid or glutamine (E or Q) +# J = "Xle"; leucine or isoleucine (L or I, used in mass-spec) +# U = "Sec"; selenocysteine +# O = "Pyl"; pyrrolysine +ambiguous_dna_letters = "GATCRYWSMKHBVDN" +unambiguous_dna_letters = "GATC" +ambiguous_rna_letters = "GAUCRYWSMKHBVDN" +unambiguous_rna_letters = "GAUC" + +# B == 5-bromouridine +# D == 5,6-dihydrouridine +# S == thiouridine +# W == wyosine +extended_dna_letters = "GATCBDSW" + +# are there extended forms? +#extended_rna_letters = "GAUCBDSW" + +ambiguous_dna_values = { + "A": "A", + "C": "C", + "G": "G", + "T": "T", + "M": "AC", + "R": "AG", + "W": "AT", + "S": "CG", + "Y": "CT", + "K": "GT", + "V": "ACG", + "H": "ACT", + "D": "AGT", + "B": "CGT", + "X": "GATC", + "N": "GATC", + } +ambiguous_rna_values = { + "A": "A", + "C": "C", + "G": "G", + "U": "U", + "M": "AC", + "R": "AG", + "W": "AU", + "S": "CG", + "Y": "CU", + "K": "GU", + "V": "ACG", + "H": "ACU", + "D": "AGU", + "B": "CGU", + "X": "GAUC", + "N": "GAUC", + } + +ambiguous_dna_complement = { + "A": "T", + "C": "G", + "G": "C", + "T": "A", + "M": "K", + "R": "Y", + "W": "W", + "S": "S", + "Y": "R", + "K": "M", + "V": "B", + "H": "D", + "D": "H", + "B": "V", + "X": "X", + "N": "N", + } + +ambiguous_rna_complement = { + "A": "U", + "C": "G", + "G": "C", + "U": "A", + "M": "K", + "R": "Y", + "W": "W", + "S": "S", + "Y": "R", + "K": "M", + "V": "B", + "H": "D", + "D": "H", + "B": "V", + "X": "X", + "N": "N", + } + + +def _make_ranges(dict): + d = {} + for key, value in dict.items(): + d[key] = (value, value) + return d + +# From bioperl's SeqStats.pm +unambiguous_dna_weights = { + "A": 347., + "C": 323., + "G": 363., + "T": 322., + } +unambiguous_dna_weight_ranges = _make_ranges(unambiguous_dna_weights) + +unambiguous_rna_weights = { + "A": unambiguous_dna_weights["A"] + 16., # 16 for the oxygen + "C": unambiguous_dna_weights["C"] + 16., + "G": unambiguous_dna_weights["G"] + 16., + "U": 340., +} +unambiguous_rna_weight_ranges = _make_ranges(unambiguous_rna_weights) + +def _make_ambiguous_ranges(dict, weight_table): + range_d = {} + avg_d = {} + for letter, values in dict.items(): + #Following line is a quick hack to skip undefined weights for U and O + if len(values)==1 and values[0] not in weight_table : continue + weights = map(weight_table.get, values) + range_d[letter] = (min(weights), max(weights)) + total_w = 0.0 + for w in weights: + total_w = total_w + w + avg_d[letter] = total_w / len(weights) + return range_d, avg_d + +ambiguous_dna_weight_ranges, avg_ambiguous_dna_weights = \ + _make_ambiguous_ranges(ambiguous_dna_values, + unambiguous_dna_weights) + +ambiguous_rna_weight_ranges, avg_ambiguous_rna_weights = \ + _make_ambiguous_ranges(ambiguous_rna_values, + unambiguous_rna_weights) + +protein_weights = { + "A": 89.09, + "C": 121.16, + "D": 133.10, + "E": 147.13, + "F": 165.19, + "G": 75.07, + "H": 155.16, + "I": 131.18, + "K": 146.19, + "L": 131.18, + "M": 149.21, + "N": 132.12, + #"O": 0.0, # Needs to be recorded! + "P": 115.13, + "Q": 146.15, + "R": 174.20, + "S": 105.09, + "T": 119.12, + #"U": 168.05, # To be confirmed + "V": 117.15, + "W": 204.23, + "Y": 181.19 + } + +extended_protein_values = { + "A": "A", + "B": "ND", + "C": "C", + "D": "D", + "E": "E", + "F": "F", + "G": "G", + "H": "H", + "I": "I", + "J": "IL", + "K": "K", + "L": "L", + "M": "M", + "N": "N", + "O": "O", + "P": "P", + "Q": "Q", + "R": "R", + "S": "S", + "T": "T", + "U": "U", + "V": "V", + "W": "W", + "X": "ACDEFGHIKLMNPQRSTVWY", + #TODO - Include U and O in the possible values of X? + #This could alter the extended_protein_weight_ranges ... + "Y": "Y", + "Z": "QE", +} + +protein_weight_ranges = _make_ranges(protein_weights) + +extended_protein_weight_ranges, avg_extended_protein_weights = \ + _make_ambiguous_ranges(extended_protein_values, + protein_weights) + + + diff --git a/binaries/src/globplot/biopython-1.50/Bio/Data/IUPACData.pyc b/binaries/src/globplot/biopython-1.50/Bio/Data/IUPACData.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a748909c60fb213bf82bf7ce4bd8e34eb91a5380 GIT binary patch literal 3758 zcmcgvTUQfT6yB4Bgvd<-2q;!I1U(j#wGYJV``_?e?&3De;`<#6{Gg|z+?aI%;f1KVT=huYiQ{0tK zFhyhn4KfW+kQxwjlhhYNZn1N`#cfSY-+y{_ zhtwT0xzwvKMO5$AS26>y?h2{*>T8*SSKkPMSKkVOS4~3T6%(_$=9Nwea-~y*T*t1cnPRfiCG)hPtIY7+vlT7n?jJQ8$#e!zYuuUD+FHM5(2MogP8uB zS2`gVy+W@3cdw9%ApL?SY^k|-PuzxkgF=wB`$C}lfCFhItA(sq`hccwt6E_U=lj(| z>g5J$W(8nv!Y>p4vk3$n;?>$^wRTwzxetpoSINTVlU-c&0_UQW%z#W{Whm>(E;75T z4ycGY3Adv{P?4CBAyRQ6!=y%pAfizrDBqZn1gS?tP~yiz#;Y^vkndy$b^Kn)6H-rw zOpr9-GvZ_M1&BA zh15apP!mEBly3XlK|oOY zK%sWJhY_9b_S&gI+mo0hOnsmhNM$f%UXw!iNTGYBl$gO#m$HvcgtAC0!6m#J?QXzC zDn0pZYI6H!J;9Ue$f>VO4f(y2brCN~H11;as1Fg0qgOrwR>VcNb$zVzk)@`?} zruC+sk(Qud*5{hhb{r$LW3H%cSPN1in z#@{53Zk@(oC5>J>Z6e!CCrx|;@WFrWqH-S@7~{L&j|9K=QQ1ev+kPq;WS}82SMZAg z%HSirg-(2IjA%6BdU)u>OXK)1pElER9}ChiuX5E+uz;I-G5z)p&u;O$DdY>u-0PYIu3)fxp83AJeBOiH< zcB9YO4vZy2kwm2KH?-}I#-gF<$aWw)gsw@{|HSP{XrQ~ILo#}X(a3Npn%EAE4u>Km z_3FIVFk~a5Im0Vr<*~ZmqP*4!tVLrU8u3IZ7Comi6bnUTqOk=1C|1;aFS9K06wb%(oICx} zyI{>be9rnz<{XafI6H9Tg8z+M^s^kVhqEv^U)4<-tuJh0U$j2csWkLRbwy4{tu)mh zadOSYu1+<2`Nd^cjVk;a;6N@c?(8gWoBaV=*-eHl!;cI>> import DocSQL, MySQLdb, os +>>> db=MySQLdb.connect(passwd='', db='test') +>>> class CreatePeople(DocSQL.Create): +... \""" +... CREATE TEMPORARY TABLE people +... (id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, +... last_name TINYTEXT, +... first_name TINYTEXT) +... \""" +... +>>> CreatePeople(connection=db) +CreatePeople(message=Success) +""" + +__version__ = "$Revision: 1.13 $" +# $Source: /home/repository/biopython/biopython/Bio/DocSQL.py,v $ + +import exceptions +import sys + +from Bio import MissingExternalDependencyError + +try: + import MySQLdb +except: + raise MissingExternalDependencyError("Install MySQLdb if you want to use Bio.DocSQL.") + +connection = None + +class NoInsertionError(exceptions.Exception): + pass + +def _check_is_public(name): + if name[:6] == "_names": + raise AttributeError + +class QueryRow(list): + def __init__(self, cursor): + try: + row = cursor.fetchone() + super(QueryRow, self).__init__(row) + except TypeError: + raise StopIteration + + object.__setattr__(self, "_names", [x[0] for x in cursor.description]) # FIXME: legacy + object.__setattr__(self, "_names_hash", {}) + + for i, name in enumerate(self._names): + self._names_hash[name] = i + + def __getattr__(self, name): + _check_is_public(name) + try: + return self[self._names_hash[name]] + except (KeyError, AttributeError) : + raise AttributeError("'%s' object has no attribute '%s'" \ + % (self.__class__.__name__, name)) + + def __setattr__(self, name, value): + try: + self._names_hash + except AttributeError: + return object.__setattr__(self, name, value) + + _check_is_public(name) + try: + index = self._names_hash[name] + self[index] = value + except KeyError: + return object.__setattr__(self, name, value) + +class Query(object): + """ + SHOW TABLES + """ + MSG_FAILURE = "Failure" + MSG_SUCCESS = "Success" + message = "not executed" + error_message = "" + prefix = "" + suffix = "" + row_class = QueryRow + + def __init__(self, *args, **keywds): + try: + self.connection = keywds['connection'] + except KeyError: + self.connection = connection + try: + self.diagnostics = keywds['diagnostics'] + except KeyError: + self.diagnostics = 0 + + self.statement = self.prefix + self.__doc__ + self.suffix + self.params = args + + def __iter__(self): + return IterationCursor(self, self.connection) + + def __repr__(self): + return "%s(message=%s)" % (self.__class__.__name__, self.message) + + def cursor(self): + return iter(self).cursor + + def dump(self): + for item in self: + print item + +class QueryGeneric(Query): + def __init__(self, statement, *args, **keywds): + Query.__init__(self, *args, **keywds) + self.statement = statement, + +class IterationCursor(object): + def __init__(self, query, connection=connection): + if connection is None: + raise TypeError("database connection is None") + self.cursor = connection.cursor() + self.row_class = query.row_class + if query.diagnostics: + print >>sys.stderr, query.statement + print >>sys.stderr, query.params + self.cursor.execute(query.statement, query.params) + + def next(self): + return self.row_class(self.cursor) + +class QuerySingle(Query, QueryRow): + ignore_warnings = 0 + def __init__(self, *args, **keywds): + message = self.MSG_FAILURE + Query.__init__(self, *args, **keywds) + try: + self.single_cursor = Query.cursor(self) + except MySQLdb.Warning: + if not self.ignore_warnings: + raise + self.row_class.__init__(self, self.cursor()) + object.__setattr__(self, "message", self.MSG_SUCCESS) + + def cursor(self): + return self.single_cursor + +class QueryAll(list, Query): + def __init__(self, *args, **keywds): + Query.__init__(self, *args, **keywds) + list.__init__(self, map(self.process_row, self.cursor().fetchall())) + + def process_row(self, row): + return row + +class QueryAllFirstItem(QueryAll): + def process_row(self, row): + return row[0] + +class Create(QuerySingle): + def __init__(self, *args, **keywds): + try: + QuerySingle.__init__(self, *args, **keywds) + except StopIteration: + self.message = self.MSG_SUCCESS + +class Update(Create): + pass + +class Insert(Create): + MSG_INTEGRITY_ERROR = "Couldn't insert: %s. " + + def __init__(self, *args, **keywds): + try: + Create.__init__(self, *args, **keywds) + except MySQLdb.IntegrityError, error_data: + self.error_message += self.MSG_INTEGRITY_ERROR % error_data[1] + try: + self.total_count + except AttributeError: + self.total_count = 0 + + raise MySQLdb.IntegrityError(self.error_message) + + self.id = self.cursor().insert_id() + try: + self.total_count += self.cursor().rowcount + except AttributeError: + self.total_count = self.cursor().rowcount + + if self.cursor().rowcount == 0: + raise NoInsertionError + +def _test(*args, **keywds): + import doctest, sys + doctest.testmod(sys.modules[__name__], *args, **keywds) + +if __name__ == "__main__": + if __debug__: + _test() diff --git a/binaries/src/globplot/biopython-1.50/Bio/Encodings/IUPACEncoding.py b/binaries/src/globplot/biopython-1.50/Bio/Encodings/IUPACEncoding.py new file mode 100644 index 0000000..a4089a3 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Encodings/IUPACEncoding.py @@ -0,0 +1,126 @@ +# Set up the IUPAC alphabet properties + + +from Bio.PropertyManager import default_manager +from Bio import Alphabet +from Bio.Alphabet import IUPAC +from Bio.Data import IUPACData + +from Bio import Transcribe, Translate + +set_prop = default_manager.class_property + +# weight tables +set_prop[IUPAC.IUPACUnambiguousDNA]["weight_table"] = \ + IUPACData.unambiguous_dna_weights +set_prop[IUPAC.IUPACAmbiguousDNA]["weight_table"] = \ + IUPACData.avg_ambiguous_dna_weights +set_prop[IUPAC.IUPACUnambiguousRNA]["weight_table"] = \ + IUPACData.unambiguous_rna_weights +set_prop[IUPAC.IUPACAmbiguousRNA]["weight_table"] = \ + IUPACData.avg_ambiguous_rna_weights +set_prop[IUPAC.IUPACProtein]["weight_table"] = \ + IUPACData.protein_weights +set_prop[IUPAC.ExtendedIUPACProtein]["weight_table"] = \ + IUPACData.avg_extended_protein_weights + +set_prop[IUPAC.IUPACUnambiguousDNA]["weight_range_table"] = \ + IUPACData.unambiguous_dna_weight_ranges +set_prop[IUPAC.IUPACAmbiguousDNA]["weight_range_table"] = \ + IUPACData.ambiguous_dna_weight_ranges +set_prop[IUPAC.IUPACUnambiguousRNA]["weight_range_table"] = \ + IUPACData.unambiguous_rna_weight_ranges +set_prop[IUPAC.IUPACAmbiguousRNA]["weight_range_table"] = \ + IUPACData.ambiguous_rna_weight_ranges +set_prop[IUPAC.IUPACProtein]["weight_range_table"] = \ + IUPACData.protein_weight_ranges +set_prop[IUPAC.ExtendedIUPACProtein]["weight_range_table"] = \ + IUPACData.extended_protein_weight_ranges + + + +# transcriber objects + +set_prop[Alphabet.DNAAlphabet]["transcriber"] = \ + Transcribe.generic_transcriber + +set_prop[IUPAC.IUPACAmbiguousDNA]["transcriber"] = \ + Transcribe.ambiguous_transcriber + +set_prop[IUPAC.IUPACUnambiguousDNA]["transcriber"] = \ + Transcribe.unambiguous_transcriber + + +set_prop[Alphabet.RNAAlphabet]["transcriber"] = \ + Transcribe.generic_transcriber + +set_prop[IUPAC.IUPACAmbiguousRNA]["transcriber"] = \ + Transcribe.ambiguous_transcriber + +set_prop[IUPAC.IUPACUnambiguousRNA]["transcriber"] = \ + Transcribe.unambiguous_transcriber + + +# translator objects +for name, obj in Translate.unambiguous_dna_by_name.items(): + property = "translator.name." + name + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + set_prop[obj.table.protein_alphabet.__class__][property] = obj + +for name, obj in Translate.unambiguous_rna_by_name.items(): + property = "translator.name." + name + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + property = "rna_translator.name." + name + set_prop[obj.table.protein_alphabet.__class__][property] = obj + + +for id, obj in Translate.unambiguous_dna_by_id.items(): + property = "translator.id.%d" % id + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + set_prop[obj.table.protein_alphabet.__class__][property] = obj + if id == 1: + set_prop[obj.table.nucleotide_alphabet.__class__]["translator"] = obj + set_prop[obj.table.protein_alphabet.__class__]["translator"] = obj + + +for id, obj in Translate.unambiguous_rna_by_id.items(): + property = "translator.id.%d" % id + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + property = "rna_translator.id.%d" % id + set_prop[obj.table.protein_alphabet.__class__][property] = obj + if id == 1: + set_prop[obj.table.nucleotide_alphabet.__class__]["translator"] = obj + set_prop[obj.table.protein_alphabet.__class__]["rna_translator"] = obj + +# ambiguous translator objects +for name, obj in Translate.ambiguous_dna_by_name.items(): + property = "translator.name." + name + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + property = "ambiguous_translator.name." + name + set_prop[obj.table.protein_alphabet.__class__][property] = obj + +for name, obj in Translate.ambiguous_rna_by_name.items(): + property = "translator.name." + name + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + property = "ambiguous_rna_translator.name." + name + set_prop[obj.table.protein_alphabet.__class__][property] = obj + + +for id, obj in Translate.ambiguous_dna_by_id.items(): + property = "translator.id.%d" % id + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + property = "ambiguous_translator.id.%d" % id + set_prop[obj.table.protein_alphabet.__class__][property] = obj + if id == 1: + set_prop[obj.table.nucleotide_alphabet.__class__]["translator"] = obj + set_prop[obj.table.protein_alphabet.__class__]["ambiguous_translator"] = obj + + +for id, obj in Translate.ambiguous_rna_by_id.items(): + property = "translator.id.%d" % id + set_prop[obj.table.nucleotide_alphabet.__class__][property] = obj + property = "ambiguous_rna_translator.id.%d" % id + set_prop[obj.table.protein_alphabet.__class__][property] = obj + if id == 1: + set_prop[obj.table.nucleotide_alphabet.__class__]["translator"] = obj + set_prop[obj.table.protein_alphabet.__class__]["ambiguous_rna_translator"] = obj diff --git a/binaries/src/globplot/biopython-1.50/Bio/Encodings/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/Encodings/__init__.py new file mode 100644 index 0000000..a200bbe --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Encodings/__init__.py @@ -0,0 +1,3 @@ +# This is a Python module. +"""Properties for functionality such as transcription and translation. +""" diff --git a/binaries/src/globplot/biopython-1.50/Bio/Fasta/FastaAlign.py b/binaries/src/globplot/biopython-1.50/Bio/Fasta/FastaAlign.py new file mode 100644 index 0000000..735b502 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Fasta/FastaAlign.py @@ -0,0 +1,84 @@ +""" +Code to deal with alignments written in Fasta format (OBSOLETE). + +This module is considered obsolete and likely to be deprecated. Please use +Bio.AlignIO instead for reading and writing alignments in FASTA format. + +This mostly just uses the regular Fasta parsing stuff written by Jeff +to deal with all of the input and output formats. + +functions: +o parse_file() + +classes: +FastaAlignment""" +# standard library +import os + +# biopython +from Bio.Align.Generic import Alignment +from Bio import Alphabet +from Bio.Alphabet import IUPAC +from Bio import Fasta + +def parse_file(file_name, type = 'DNA'): + """Parse the given file into a FastaAlignment object. + + Arguments: + o file_name - The location of the file to parse. + o type - The type of information contained in the file. + """ + if type.upper() == 'DNA': + alphabet = IUPAC.ambiguous_dna + elif type.upper() == 'RNA': + alphabet = IUPAC.ambiguous_rna + elif type.upper() == 'PROTEIN': + alphabet = IUPAC.protein + else: + raise ValueError("Invalid type %s passed. Need DNA, RNA or PROTEIN" + % type) + + # create a new alignment object + fasta_align = FastaAlignment(Alphabet.Gapped(alphabet)) + + # now parse the file and fill up the alignment object + align_file = open(file_name, 'r') + + parser = Fasta.RecordParser() + iterator = Fasta.Iterator(align_file, parser) + + cur_align = iterator.next() + while cur_align: + fasta_align.add_sequence(cur_align.title, cur_align.sequence) + + cur_align = iterator.next() + + return fasta_align + +class FastaAlignment(Alignment): + """Work with the Fasta Alignment format. + + The fasta alignment format is basically the same as the regular ol' + Fasta format we know and love, except the sequences have gaps + (represented by -'s). + """ + def __init__(self, alphabet = Alphabet.Gapped(IUPAC.ambiguous_dna)): + Alignment.__init__(self, alphabet) + + def __str__(self): + """Print out a fasta version of the alignment info.""" + return_string = '' + for item in self._records: + new_f_record = Fasta.Record() + new_f_record.title = item.description + new_f_record.sequence = item.seq.data + + return_string = return_string + str(new_f_record) + os.linesep + os.linesep + + # have a extra newline, so strip two off and add one before returning + return return_string.rstrip() + os.linesep + + + + + diff --git a/binaries/src/globplot/biopython-1.50/Bio/Fasta/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/Fasta/__init__.py new file mode 100644 index 0000000..c49b45b --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Fasta/__init__.py @@ -0,0 +1,198 @@ +"""Utilities for working with FASTA-formatted sequences (OBSOLETE). + +Classes: +Record Holds FASTA sequence data. +Iterator Iterates over sequence data in a FASTA file. +RecordParser Parses FASTA sequence data into a Record object. +SequenceParser Parses FASTA sequence data into a SeqRecord object. + +For a long time this module was the most commonly used and best documented +FASTA parser in Biopython. However, we now recommend using Bio.SeqIO instead. + +In view of this, while you can continue to use Bio.Fasta for the moment, it is +considered to be a legacy module and should not be used if you are writing new +code. At some point Bio.Fasta may be officially deprecated (with warning +messages when used) before finally being removed. + +If you are already using Bio.Fasta with the SequenceParser to get SeqRecord +objects, then you should be able to switch to the more recent Bio.SeqIO module +very easily as that too uses SeqRecord objects. For example, + +from Bio import Fasta +handle = open("example.fas") +for seq_record in Fasta.Iterator(handle, Fasta.SequenceParser()) : + print seq_record.description + print seq_record.seq +handle.close() + +Using Bio.SeqIO instead this becomes: + +from Bio import SeqIO +handle = open("example.fas") +for seq_record in SeqIO.parse(handle, "fasta") : + print seq_record.description + print seq_record.seq +handle.close() + +Converting an existing code which uses the RecordParser is a little more +complicated as the Bio.Fasta.Record object differs from the SeqRecord. + +from Bio import Fasta +handle = open("example.fas") +for record in Fasta.Iterator(handle, Fasta.RecordParser()) : + #record is a Bio.Fasta.Record object + print record.title #The full title line as a string + print record.sequence #The sequence as a string +handle.close() + +Using Bio.SeqIO instead this becomes: + +from Bio import SeqIO +handle = open("example.fas") +for seq_record in SeqIO.parse(handle, "fasta") : + print seq_record.description #The full title line as a string + print seq_record.seq.tostring() #The sequence as a string +handle.close() + + + +""" +from Bio import Seq +from Bio import SeqRecord +from Bio import Alphabet + + +class Record: + """Holds information from a FASTA record. + + Members: + title Title line ('>' character not included). + sequence The sequence. + + """ + def __init__(self, colwidth=60): + """__init__(self, colwidth=60) + + Create a new Record. colwidth specifies the number of residues + to put on each line when generating FASTA format. + + """ + self.title = '' + self.sequence = '' + self._colwidth = colwidth + + def __str__(self): + s = [] + s.append('>%s' % self.title) + i = 0 + while i < len(self.sequence): + s.append(self.sequence[i:i+self._colwidth]) + i = i + self._colwidth + #Was having a problem getting the tests to pass on windows... + #return os.linesep.join(s) + return "\n".join(s) + +class Iterator: + """Returns one record at a time from a FASTA file. + """ + def __init__(self, handle, parser = None, debug = 0): + """Initialize a new iterator. + """ + self.handle = handle + self._parser = parser + self._debug = debug + + #Skip any text before the first record (e.g. blank lines) + while True : + line = handle.readline() + if not line or line[0] == ">" : + break + if debug : print "Skipping: " + line + self._lookahead = line + + def __iter__(self): + return iter(self.next, None) + + def next(self): + """Return the next record in the file""" + line = self._lookahead + if not line: + return None + assert line[0]==">", line + lines = [line.rstrip()] + line = self.handle.readline() + while line: + if line[0] == ">": break + if line[0] == "#" : + if self._debug : print "Ignoring comment line" + pass + else : + lines.append(line.rstrip()) + line = self.handle.readline() + self._lookahead = line + if self._debug : print "Debug: '%s' and '%s'" % (title, "".join(lines)) + if self._parser is None: + return "\n".join(lines) + else : + return self._parser.parse_string("\n".join(lines)) + +class RecordParser: + """Parses FASTA sequence data into a Fasta.Record object. + """ + def __init__(self, debug = 0): + pass + + def parse_string(self, text) : + text = text.replace("\r\n","\n") #Crude way of dealing with \r\n + assert text[0] == ">", text + text = text.split("\n>",1)[0] # Only do the first record if more than one + title, sequence = text.split("\n", 1) + title = title[1:] + rec = Record() + rec.title = title + rec.sequence = sequence.replace("\n","") + return rec + + def parse(self, handle): + return self.parse_string(handle.read()) + +class SequenceParser: + """Parses FASTA sequence data into a SeqRecord object. + """ + def __init__(self, alphabet = Alphabet.generic_alphabet, title2ids = None, + debug = 0): + """Initialize a Scanner and Sequence Consumer. + + Arguments: + o alphabet - The alphabet of the sequences to be parsed. If not + passed, this will be set as generic_alphabet. + o title2ids - A function that, when given the title of the FASTA + file (without the beginning >), will return the id, name and + description (in that order) for the record. If this is not given, + then the entire title line will be used as the description. + """ + self.alphabet = alphabet + self.title2ids = title2ids + + def parse_string(self, text) : + text = text.replace("\r\n","\n") #Crude way of dealing with \r\n + assert text[0] == ">", text + text = text.split("\n>",1)[0] # Only do the first record if more than one + title, sequence = text.split("\n", 1) + title = title[1:] + + seq = Seq.Seq(sequence.replace("\n",""), self.alphabet) + rec = SeqRecord.SeqRecord(seq) + + if self.title2ids: + seq_id, name, descr = self.title2ids(title) + rec.id = seq_id + rec.name = name + rec.description = descr + else: + rec.description = title + + return rec + + def parse(self, handle): + return self.parse_string(handle.read()) diff --git a/binaries/src/globplot/biopython-1.50/Bio/Fasta/__init__.pyc b/binaries/src/globplot/biopython-1.50/Bio/Fasta/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3634958984f04bbc5951f6c3173de780f8a869d3 GIT binary patch literal 7303 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 literal 0 HcmV?d00001 diff --git a/binaries/src/globplot/biopython-1.50/Bio/File.py b/binaries/src/globplot/biopython-1.50/Bio/File.py new file mode 100644 index 0000000..d616f42 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/File.py @@ -0,0 +1,180 @@ +# Copyright 1999 by Jeffrey Chang. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Code for more fancy file handles. + + +Classes: +UndoHandle File object decorator with support for undo-like operations. +StringHandle Wraps a file object around a string. +SGMLHandle File object that automatically strips SGML tags from data. + +SGMLStripper Object that strips SGML. This is now considered OBSOLETE, and + is likely to be deprecated in a future release of Biopython, + and later removed. + +""" +import os +import StringIO +import sgmllib + +class UndoHandle: + """A Python handle that adds functionality for saving lines. + + Saves lines in a LIFO fashion. + + Added methods: + saveline Save a line to be returned next time. + peekline Peek at the next line without consuming it. + + """ + def __init__(self, handle): + self._handle = handle + self._saved = [] + + def __iter__(self): + return self + + def next(self): + next = self.readline() + if not next: + raise StopIteration + return next + + def readlines(self, *args, **keywds): + lines = self._saved + self._handle.readlines(*args,**keywds) + self._saved = [] + return lines + + def readline(self, *args, **keywds): + if self._saved: + line = self._saved.pop(0) + else: + line = self._handle.readline(*args,**keywds) + return line + + def read(self, size=-1): + if size == -1: + saved = "".join(self._saved) + self._saved[:] = [] + else: + saved = '' + while size > 0 and self._saved: + if len(self._saved[0]) <= size: + size = size - len(self._saved[0]) + saved = saved + self._saved.pop(0) + else: + saved = saved + self._saved[0][:size] + self._saved[0] = self._saved[0][size:] + size = 0 + return saved + self._handle.read(size) + + def saveline(self, line): + if line: + self._saved = [line] + self._saved + + def peekline(self): + if self._saved: + line = self._saved[0] + else: + line = self._handle.readline() + self.saveline(line) + return line + + def tell(self): + lengths = map(len, self._saved) + sum = reduce(lambda x, y: x+y, lengths, 0) + return self._handle.tell() - sum + + def seek(self, *args): + self._saved = [] + self._handle.seek(*args) + + def __getattr__(self, attr): + return getattr(self._handle, attr) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self._handle.close() + + +# I could make this faster by using cStringIO. +# However, cStringIO (in v1.52) does not implement the +# readlines method. +StringHandle = StringIO.StringIO + + + +class SGMLHandle: + """A Python handle that automatically strips SGML tags from data (OBSOLETE). + + This module is now considered to be obsolete, and is likely to be + deprecated in a future release of Biopython, and later removed. + """ + def __init__(self, handle): + """SGMLStripper(handle) + + handle is a file handle to SGML-formatted data. + + """ + self._handle = handle + self._stripper = SGMLStripper() + + def read(self, *args, **keywds): + data = self._handle.read(*args, **keywds) + return self._stripper.strip(data) + + def readline(self, *args, **keywds): + line = self._handle.readline(*args, **keywds) + return self._stripper.strip(line) + + def readlines(self, *args, **keywds): + lines = self._handle.readlines(*args, **keywds) + for i in range(len(lines)): + lines[i] = self._stripper.strip(str) + return lines + + def __getattr__(self, attr): + return getattr(self._handle, attr) + + +class SGMLStripper: + class MyParser(sgmllib.SGMLParser): + def __init__(self): + sgmllib.SGMLParser.__init__(self) + self.data = '' + def handle_data(self, data): + self.data = self.data + data + + def __init__(self): + self._parser = SGMLStripper.MyParser() + + def strip(self, str): + """S.strip(str) -> string + + Strip the SGML tags from str. + + """ + if not str: # empty string, don't do anything. + return '' + # I need to make sure that I don't return an empty string if + # the buffer is not empty. This can happen if there's a newline + # character embedded within a tag. Thus, I'll first check to + # see if the last character is a newline. If it is, and it's stripped + # away, I'll add it back. + is_newline = str[-1] in ['\n', '\r'] + + self._parser.data = '' # clear the parser's data (don't reset) + self._parser.feed(str) + if self._parser.data: + str = self._parser.data + elif is_newline: + str = '\n' + else: + str = '' + return str + diff --git a/binaries/src/globplot/biopython-1.50/Bio/File.pyc b/binaries/src/globplot/biopython-1.50/Bio/File.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a68368c70c30ed23569c3824270994b7da43cec7 GIT binary patch literal 6206 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 0 ): + len_expected = args[ 0 ] + if( len_expected < 0 ): + len_expected = None + elif 'size' in keywds: + len_expected = keywds['size'] + else: + len_expected = None + return len_expected + + def filter( self, lines ): + filter_chain = self.filter_chain + filtered_text = '' + for line in lines: + for filter in filter_chain: + line = filter( *( line, ) ) + filtered_text += line + + return filtered_text + +def has_trailing_linefeed( line ): + if( line.endswith( chr( 13 ) ) or \ + line.endswith( chr( 10 ) ) ): + return 1 + else: + return 0 diff --git a/binaries/src/globplot/biopython-1.50/Bio/HotRand.py b/binaries/src/globplot/biopython-1.50/Bio/HotRand.py new file mode 100644 index 0000000..d15a64f --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/HotRand.py @@ -0,0 +1,77 @@ +# Copyright 2002 by Katharine Lindner. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""handles true random numbers supplied from the the web server of fourmilab. Based on atmospheric noise. The motivation is to support biosimulations that rely on random numbers. +""" + +import urllib + + +def hex_convert(text): + import warnings + warnings.warn("The function Bio.HotRand.hex_convert is deprecated. Instead of Bio.HotRand.hex_convert(text), please use int(text, 16) instead", DeprecationWarning) + return int(text, 16) + +def byte_concat( text ): + val = 0 + numbytes = len( text ) + for i in range( 0, numbytes ): + val = val * 256 + val = val + ord( text[ i ] ) + + return val + +class HotCache: + + def __init__( self ): +# self.url = 'http://www.fourmilab.ch/cgi-bin/uncgi/Hotbits?num=5000&min=1&max=6&col=1' + self.url = 'http://www.random.org/cgi-bin/randbyte?' + self.query = { 'nbytes' : 128, 'fmt' : 'h' } + self.fill_hot_cache() + + def fill_hot_cache( self ): + url = self.url + urllib.urlencode( self.query ) + fh = urllib.urlopen( url ) + self.hot_cache = fh.read() + fh.close() + + def next_num( self, num_digits = 4 ): + cache = self.hot_cache + numbytes = num_digits / 2 + if( len( cache ) % numbytes != 0 ): + print 'len_cache is %d' % len( cache ) + raise ValueError + if( cache == '' ): + self.fill_hot_cache() + cache = self.hot_cache + hexdigits = cache[ :numbytes ] + self.hot_cache = cache[ numbytes: ] + return byte_concat( hexdigits ) + + + +class HotRandom: + + def __init__( self ): + self.hot_cache = HotCache( ) + + def hot_rand( self, high, low = 0 ): + span = high - low + val = self.hot_cache.next_num() + val = ( span * val ) >> 16 + val = val + low + return val + + +if( __name__ == '__main__' ): + hot_random = HotRandom() + for j in range ( 0, 130 ): + print hot_random.hot_rand( 25 ) + nums = [ '0000', 'abcd', '1234', '5555', '4321', 'aaaa', 'ffff' ] + for num in nums: + print hex_convert( num ) + + + diff --git a/binaries/src/globplot/biopython-1.50/Bio/Index.py b/binaries/src/globplot/biopython-1.50/Bio/Index.py new file mode 100644 index 0000000..4562f0d --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Index.py @@ -0,0 +1,142 @@ +# Copyright 1999 by Jeffrey Chang. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Index.py + +This module provides a way to create indexes to text files. + +Classes: +Index Dictionary-like class used to store index information. + +_ShelveIndex An Index class based on the shelve module. +_InMemoryIndex An in-memory Index class. + +""" +import os +import array +import cPickle +import shelve + +class _ShelveIndex(dict): + """An index file wrapped around shelve. + + """ + # Without a good dbm module installed, this is pretty slow and + # generates large files. When generating an index on a FASTA- + # formatted file with 82000 sequences (37Mb), the + # index 'dat' file is 42Mb and 'dir' file is 8Mb. + + __version = 2 + __version_key = '__version' + + def __init__(self, indexname, truncate=None): + dict.__init__(self) + try: + if truncate: + # In python 1.52 and before, dumbdbm (under shelve) + # doesn't clear the old database. + files = [indexname + '.dir', + indexname + '.dat', + indexname + '.bak' + ] + for file in files: + if os.path.exists(file): + os.unlink(file) + raise Exception("open a new shelf") + self.data = shelve.open(indexname, flag='r') + except: + # No database exists. + self.data = shelve.open(indexname, flag='n') + self.data[self.__version_key] = self.__version + else: + # Check to make sure the database is the correct version. + version = self.data.get(self.__version_key, None) + if version is None: + raise IOError("Unrecognized index format") + elif version != self.__version: + raise IOError("Version %s doesn't match my version %s" \ + % (version, self.__version)) + + def __del__(self): + if self.__dict__.has_key('data'): + self.data.close() + +class _InMemoryIndex(dict): + """This creates an in-memory index file. + + """ + # File Format: + # version + # key value + # [...] + + __version = 3 + __version_key = '__version' + + def __init__(self, indexname, truncate=None): + self._indexname = indexname + dict.__init__(self) + self.__changed = 0 # the index hasn't changed + + # Remove the database if truncate is true. + if truncate and os.path.exists(indexname): + os.unlink(indexname) + self.__changed = 1 + + # Load the database if it exists + if os.path.exists(indexname): + handle = open(indexname) + version = self._toobj(handle.readline().rstrip()) + if version != self.__version: + raise IOError("Version %s doesn't match my version %s" \ + % (version, self.__version)) + for line in handle: + key, value = line.split() + key, value = self._toobj(key), self._toobj(value) + self[key] = value + self.__changed = 0 + + def update(self, dict): + self.__changed = 1 + dict.update(self, dict) + def __setitem__(self, key, value): + self.__changed = 1 + dict.__setitem__(self, key, value) + def __delitem__(self, key): + self.__changed = 1 + dict.__delitem__(self, key) + def clear(self): + self.__changed = 1 + dict.clear(self) + + def __del__(self): + if self.__changed: + handle = open(self._indexname, 'w') + handle.write("%s\n" % self._tostr(self.__version)) + for key, value in self.items(): + handle.write("%s %s\n" % + (self._tostr(key), self._tostr(value))) + handle.close() + + def _tostr(self, obj): + # I need a representation of the object that's saveable to + # a file that uses whitespace as delimiters. Thus, I'm + # going to pickle the object, and then convert each character of + # the string to its ASCII integer value. Then, I'm going to convert + # the integers into strings and join them together with commas. + # It's not the most efficient way of storing things, but it's + # relatively fast. + s = cPickle.dumps(obj) + intlist = array.array('b', s) + strlist = map(str, intlist) + return ','.join(strlist) + + def _toobj(self, str): + intlist = map(int, str.split(',')) + intlist = array.array('b', intlist) + strlist = map(chr, intlist) + return cPickle.loads(''.join(strlist)) + +Index = _InMemoryIndex diff --git a/binaries/src/globplot/biopython-1.50/Bio/LogisticRegression.py b/binaries/src/globplot/biopython-1.50/Bio/LogisticRegression.py new file mode 100644 index 0000000..7bf13de --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/LogisticRegression.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +""" +This module provides code for doing logistic regressions. + + +Classes: +LogisticRegression Holds information for a LogisticRegression classifier. + + +Functions: +train Train a new classifier. +calculate Calculate the probabilities of each class, given an observation. +classify Classify an observation into a class. +""" + +#TODO - Remove this work around once we drop python 2.3 support +try: + set = set +except NameError: + from sets import Set as set + +#from numpy import * +#from numpy.linalg import * +import numpy +import numpy.linalg + +class LogisticRegression: + """Holds information necessary to do logistic regression + classification. + + Members: + beta List of the weights for each dimension. + + """ + def __init__(self): + """LogisticRegression()""" + self.beta = [] + +def train(xs, ys, update_fn=None, typecode=None): + """train(xs, ys[, update_fn]) -> LogisticRegression + + Train a logistic regression classifier on a training set. xs is a + list of observations and ys is a list of the class assignments, + which should be 0 or 1. xs and ys should contain the same number + of elements. update_fn is an optional callback function that + takes as parameters that iteration number and log likelihood. + + """ + if len(xs) != len(ys): + raise ValueError("xs and ys should be the same length.") + classes = set(ys) + if classes != set([0, 1]): + raise ValueError("Classes should be 0's and 1's") + if typecode is None: + typecode = 'd' + + # Dimensionality of the data is the dimensionality of the + # observations plus a constant dimension. + N, ndims = len(xs), len(xs[0]) + 1 + if N==0 or ndims==1: + raise ValueError("No observations or observation of 0 dimension.") + + # Make an X array, with a constant first dimension. + X = numpy.ones((N, ndims), typecode) + X[:, 1:] = xs + Xt = numpy.transpose(X) + y = numpy.asarray(ys, typecode) + + # Initialize the beta parameter to 0. + beta = numpy.zeros(ndims, typecode) + + MAX_ITERATIONS = 500 + CONVERGE_THRESHOLD = 0.01 + stepsize = 1.0 + # Now iterate using Newton-Raphson until the log-likelihoods + # converge. + iter = 0 + old_beta = old_llik = None + while iter < MAX_ITERATIONS: + # Calculate the probabilities. p = e^(beta X) / (1+e^(beta X)) + ebetaX = numpy.exp(numpy.dot(beta, Xt)) + p = ebetaX / (1+ebetaX) + + # Find the log likelihood score and see if I've converged. + logp = y*numpy.log(p) + (1-y)*numpy.log(1-p) + llik = sum(logp) + if update_fn is not None: + update_fn(iter, llik) + # Check to see if the likelihood decreased. If it did, then + # restore the old beta parameters and half the step size. + if llik < old_llik: + stepsize = stepsize / 2.0 + beta = old_beta + # If I've converged, then stop. + if old_llik is not None and numpy.fabs(llik-old_llik) <= CONVERGE_THRESHOLD: + break + old_llik, old_beta = llik, beta + iter += 1 + + W = numpy.identity(N) * p + Xtyp = numpy.dot(Xt, y-p) # Calculate the first derivative. + XtWX = numpy.dot(numpy.dot(Xt, W), X) # Calculate the second derivative. + #u, s, vt = singular_value_decomposition(XtWX) + #print "U", u + #print "S", s + delta = numpy.linalg.solve(XtWX, Xtyp) + if numpy.fabs(stepsize-1.0) > 0.001: + delta = delta * stepsize + beta = beta + delta # Update beta. + else: + raise RuntimeError("Didn't converge.") + + lr = LogisticRegression() + lr.beta = map(float, beta) # Convert back to regular array. + return lr + +def calculate(lr, x): + """calculate(lr, x) -> list of probabilities + + Calculate the probability for each class. lr is a + LogisticRegression object. x is the observed data. Returns a + list of the probability that it fits each class. + + """ + # Insert a constant term for x. + x = numpy.asarray([1.0] + x) + # Calculate the probability. p = e^(beta X) / (1+e^(beta X)) + ebetaX = numpy.exp(numpy.dot(lr.beta, x)) + p = ebetaX / (1+ebetaX) + return [1-p, p] + +def classify(lr, x): + """classify(lr, x) -> 1 or 0 + + Classify an observation into a class. + + """ + probs = calculate(lr, x) + if probs[0] > probs[1]: + return 0 + return 1 diff --git a/binaries/src/globplot/biopython-1.50/Bio/ParserSupport.py b/binaries/src/globplot/biopython-1.50/Bio/ParserSupport.py new file mode 100644 index 0000000..88e9a23 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/ParserSupport.py @@ -0,0 +1,426 @@ +# Copyright 1999 by Jeffrey Chang. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Code to support writing parsers. + + + +Classes: +AbstractParser Base class for parsers. +AbstractConsumer Base class of all Consumers. +TaggingConsumer Consumer that tags output with its event. For debugging +SGMLStrippingConsumer Consumer that strips SGML tags from output. +EventGenerator Generate Biopython Events from Martel XML output + (note that Martel is now DEPRECATED) + +Functions: +safe_readline Read a line from a handle, with check for EOF. +safe_peekline Peek at next line, with check for EOF. +read_and_call Read a line from a handle and pass it to a method. +read_and_call_while Read many lines, as long as a condition is met. +read_and_call_until Read many lines, until a condition is met. +attempt_read_and_call Like read_and_call, but forgiving of errors. +is_blank_line Test whether a line is blank. + +""" + +import sys +import traceback +from types import * + +from Bio import File + +# XML from python 2.0 +try: + from xml.sax import handler + xml_support = 1 +except ImportError: + sys.stderr.write("Warning: Could not import SAX for dealing with XML.\n" + + "This causes problems with some ParserSupport modules\n") + xml_support = 0 + +class AbstractParser: + """Base class for other parsers. + + """ + def parse(self, handle): + raise NotImplementedError("Please implement in a derived class") + + def parse_str(self, string): + return self.parse(File.StringHandle(string)) + + def parse_file(self, filename): + h = open(filename) + try: + retval = self.parse(h) + finally: + h.close() + return retval + +class AbstractConsumer: + """Base class for other Consumers. + + Derive Consumers from this class and implement appropriate + methods for each event that you want to receive. + + """ + def _unhandled_section(self): + pass + def _unhandled(self, data): + pass + def __getattr__(self, attr): + if attr[:6] == 'start_' or attr[:4] == 'end_': + method = self._unhandled_section + else: + method = self._unhandled + return method + +class TaggingConsumer(AbstractConsumer): + """A Consumer that tags the data stream with the event and + prints it to a handle. Useful for debugging. + + """ + def __init__(self, handle=None, colwidth=15, maxwidth=80): + """TaggingConsumer(handle=sys.stdout, colwidth=15, maxwidth=80)""" + # I can't assign sys.stdout to handle in the argument list. + # If I do that, handle will be assigned the value of sys.stdout + # the first time this function is called. This will fail if + # the user has assigned sys.stdout to some other file, which may + # be closed or invalid at a later time. + if handle is None: + handle = sys.stdout + self._handle = handle + self._colwidth = colwidth + self._maxwidth = maxwidth + + def unhandled_section(self): + self._print_name('unhandled_section') + + def unhandled(self, data): + self._print_name('unhandled', data) + + def _print_name(self, name, data=None): + if data is None: + # Write the name of a section. + self._handle.write("%s %s\n" % ("*"*self._colwidth, name)) + else: + # Write the tag and line. + self._handle.write("%-*s: %s\n" % ( + self._colwidth, name[:self._colwidth], + data[:self._maxwidth-self._colwidth-2].rstrip())) + + def __getattr__(self, attr): + if attr[:6] == 'start_' or attr[:4] == 'end_': + method = lambda a=attr, s=self: s._print_name(a) + else: + method = lambda x, a=attr, s=self: s._print_name(a, x) + return method + +class SGMLStrippingConsumer: + """A consumer that strips off SGML tags. + + This is meant to be used as a decorator for other consumers. + + """ + def __init__(self, consumer): + if type(consumer) is not InstanceType: + raise ValueError("consumer should be an instance") + self._consumer = consumer + self._prev_attr = None + self._stripper = File.SGMLStripper() + + def _apply_clean_data(self, data): + clean = self._stripper.strip(data) + self._prev_attr(clean) + + def __getattr__(self, name): + if name in ['_prev_attr', '_stripper']: + return getattr(self, name) + attr = getattr(self._consumer, name) + # If this is not a method, then return it as is. + if type(attr) is not MethodType: + return attr + # If it's a section method, then return it. + if name[:6] == 'start_' or name[:4] == 'end_': + return attr + # Otherwise, it's an info event, and return my method. + self._prev_attr = attr + return self._apply_clean_data + +# onle use the Event Generator if XML handling is okay +if xml_support: + class EventGenerator(handler.ContentHandler): + """Handler to generate events associated with a Martel parsed file. + + This acts like a normal SAX handler, and accepts XML generated by + Martel during parsing. These events are then converted into + 'Biopython events', which can then be caught by a standard + biopython consumer. + + Note that Martel is now DEPRECATED. + """ + def __init__(self, consumer, interest_tags, callback_finalizer = None, + exempt_tags = []): + """Initialize to begin catching and firing off events. + + Arguments: + o consumer - The consumer that we'll send Biopython events to. + + o interest_tags - A listing of all the tags we are interested in. + + o callback_finalizer - A function to deal with the collected + information before passing it on to the consumer. By default + the collected information is a list of all of the lines read + for a particular tag -- if there are multiple tags in a row + like: + + Spam + More Spam + + In this case the list of information would be: + + ['Spam', 'More Spam'] + + This list of lines will be passed to the callback finalizer if + it is present. Otherwise the consumer will be called with the + list of content information. + + o exempt_tags - A listing of particular tags that are exempt from + being processed by the callback_finalizer. This allows you to + use a finalizer to deal with most tags, but leave those you don't + want touched. + """ + self._consumer = consumer + self.interest_tags = interest_tags + self._finalizer = callback_finalizer + self._exempt_tags = exempt_tags + + # a dictionary of content for each tag of interest + # the information for each tag is held as a list of the lines. + # This allows us to collect information from multiple tags + # in a row, and return it all at once. + self.info = {} + for tag in self.interest_tags: + self.info[tag] = [] + + # the previous tag we were collecting information for. + # We set a delay in sending info to the consumer so that we can + # collect a bunch of tags in a row and append all of the info + # together. + self._previous_tag = '' + + # the current character information for a tag + self._cur_content = [] + # whether we should be collecting information + self._collect_characters = 0 + + def startElement(self, name, attrs): + """Determine if we should collect characters from this tag. + """ + if name in self.interest_tags: + self._collect_characters = 1 + + def characters(self, content): + """Extract the information if we are interested in it. + """ + if self._collect_characters: + self._cur_content.append(content) + + def endElement(self, name): + """Send the information to the consumer. + + Once we've got the end element we've collected up all of the + character information we need, and we need to send this on to + the consumer to do something with it. + + We have a delay of one tag on doing this, so that we can collect + all of the info from multiple calls to the same element at once. + """ + # only deal with the tag if it is something we are + # interested in and potentially have information for + if self._collect_characters: + # add all of the information collected inside this tag + self.info[name].append("".join(self._cur_content)) + # reset our information and flags + self._cur_content = [] + self._collect_characters = 0 + + # if we are at a new tag, pass on the info from the last tag + if self._previous_tag and self._previous_tag != name: + self._make_callback(self._previous_tag) + + # set this tag as the next to be passed + self._previous_tag = name + + def _make_callback(self, name): + """Call the callback function with the info with the given name. + """ + # strip off whitespace and call the consumer + callback_function = getattr(self._consumer, name) + + # --- pass back the information + # if there is a finalizer, use that + if self._finalizer is not None and name not in self._exempt_tags: + info_to_pass = self._finalizer(self.info[name]) + # otherwise pass back the entire list of information + else: + info_to_pass = self.info[name] + + callback_function(info_to_pass) + + # reset the information for the tag + self.info[name] = [] + + def endDocument(self): + """Make sure all of our information has been passed. + + This just flushes out any stored tags that need to be passed. + """ + if self._previous_tag: + self._make_callback(self._previous_tag) + +def read_and_call(uhandle, method, **keywds): + """read_and_call(uhandle, method[, start][, end][, contains][, blank][, has_re]) + + Read a line from uhandle, check it, and pass it to the method. + Raises a ValueError if the line does not pass the checks. + + start, end, contains, blank, and has_re specify optional conditions + that the line must pass. start and end specifies what the line must + begin or end with (not counting EOL characters). contains + specifies a substring that must be found in the line. If blank + is a true value, then the line must be blank. has_re should be + a regular expression object with a pattern that the line must match + somewhere. + + """ + line = safe_readline(uhandle) + errmsg = _fails_conditions(*(line,), **keywds) + if errmsg is not None: + raise ValueError(errmsg) + method(line) + +def read_and_call_while(uhandle, method, **keywds): + """read_and_call_while(uhandle, method[, start][, end][, contains][, blank][, has_re]) -> number of lines + + Read a line from uhandle and pass it to the method as long as + some condition is true. Returns the number of lines that were read. + + See the docstring for read_and_call for a description of the parameters. + + """ + nlines = 0 + while 1: + line = safe_readline(uhandle) + # If I've failed the condition, then stop reading the line. + if _fails_conditions(*(line,), **keywds): + uhandle.saveline(line) + break + method(line) + nlines = nlines + 1 + return nlines + +def read_and_call_until(uhandle, method, **keywds): + """read_and_call_until(uhandle, method, + start=None, end=None, contains=None, blank=None) -> number of lines + + Read a line from uhandle and pass it to the method until + some condition is true. Returns the number of lines that were read. + + See the docstring for read_and_call for a description of the parameters. + + """ + nlines = 0 + while 1: + line = safe_readline(uhandle) + # If I've met the condition, then stop reading the line. + if not _fails_conditions(*(line,), **keywds): + uhandle.saveline(line) + break + method(line) + nlines = nlines + 1 + return nlines + +def attempt_read_and_call(uhandle, method, **keywds): + """attempt_read_and_call(uhandle, method, **keywds) -> boolean + + Similar to read_and_call, but returns a boolean specifying + whether the line has passed the checks. Does not raise + exceptions. + + See docs for read_and_call for a description of the function + arguments. + + """ + line = safe_readline(uhandle) + passed = not _fails_conditions(*(line,), **keywds) + if passed: + method(line) + else: + uhandle.saveline(line) + return passed + +def _fails_conditions(line, start=None, end=None, contains=None, blank=None, + has_re=None): + if start is not None: + if line[:len(start)] != start: + return "Line does not start with '%s':\n%s" % (start, line) + if end is not None: + if line.rstrip()[-len(end):] != end: + return "Line does not end with '%s':\n%s" % (end, line) + if contains is not None: + if line.find(contains) == -1: + return "Line does not contain '%s':\n%s" % (contains, line) + if blank is not None: + if blank: + if not is_blank_line(line): + return "Expected blank line, but got:\n%s" % line + else: + if is_blank_line(line): + return "Expected non-blank line, but got a blank one" + if has_re is not None: + if has_re.search(line) is None: + return "Line does not match regex '%s':\n%s" % ( + has_re.pattern, line) + return None + +def is_blank_line(line, allow_spaces=0): + """is_blank_line(line, allow_spaces=0) -> boolean + + Return whether a line is blank. allow_spaces specifies whether to + allow whitespaces in a blank line. A true value signifies that a + line containing whitespaces as well as end-of-line characters + should be considered blank. + + """ + if not line: + return 1 + if allow_spaces: + return line.rstrip() == '' + return line[0] == '\n' or line[0] == '\r' + +def safe_readline(handle): + """safe_readline(handle) -> line + + Read a line from an UndoHandle and return it. If there are no more + lines to read, I will raise a ValueError. + + """ + line = handle.readline() + if not line: + raise ValueError("Unexpected end of stream.") + return line + +def safe_peekline(handle): + """safe_peekline(handle) -> line + + Peek at the next line in an UndoHandle and return it. If there are no + more lines to peek, I will raise a ValueError. + + """ + line = handle.peekline() + if not line: + raise ValueError("Unexpected end of stream.") + return line diff --git a/binaries/src/globplot/biopython-1.50/Bio/Parsers/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/Parsers/__init__.py new file mode 100644 index 0000000..53f3d1b --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Parsers/__init__.py @@ -0,0 +1,2 @@ +"""Third party and other parsers useful internally to Biopython. +""" diff --git a/binaries/src/globplot/biopython-1.50/Bio/Parsers/spark.py b/binaries/src/globplot/biopython-1.50/Bio/Parsers/spark.py new file mode 100644 index 0000000..be547e7 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Parsers/spark.py @@ -0,0 +1,565 @@ +# Copyright (c) 1998-2000 John Aycock +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__version__ = 'SPARK-0.6.1' + +import re +import sys + +def _namelist(instance): + namelist, namedict, classlist = [], {}, [instance.__class__] + for c in classlist: + for b in c.__bases__: + classlist.append(b) + for name in dir(c): + if name not in namedict: + namelist.append(name) + namedict[name] = 1 + return namelist + +class GenericScanner: + def __init__(self): + pattern = self.reflect() + self.re = re.compile(pattern, re.VERBOSE) + + self.index2func = {} + for name, number in self.re.groupindex.items(): + self.index2func[number-1] = getattr(self, 't_' + name) + + def makeRE(self, name): + doc = getattr(self, name).__doc__ + rv = '(?P<%s>%s)' % (name[2:], doc) + return rv + + def reflect(self): + rv = [] + for name in _namelist(self): + if name[:2] == 't_' and name != 't_default': + rv.append(self.makeRE(name)) + + rv.append(self.makeRE('t_default')) + return '|'.join(rv) + + def error(self, s, pos): + print "Lexical error at position %s" % pos + raise SystemExit + + def tokenize(self, s): + pos = 0 + n = len(s) + while pos < n: + m = self.re.match(s, pos) + if m is None: + self.error(s, pos) + + groups = m.groups() + for i in range(len(groups)): + if groups[i] and i in self.index2func: + self.index2func[i](groups[i]) + pos = m.end() + + def t_default(self, s): + r'( . | \n )+' + pass + +class GenericParser: + def __init__(self, start): + self.rules = {} + self.rule2func = {} + self.rule2name = {} + self.collectRules() + self.startRule = self.augment(start) + self.ruleschanged = 1 + + _START = 'START' + _EOF = 'EOF' + + # + # A hook for GenericASTBuilder and GenericASTMatcher. + # + def preprocess(self, rule, func): return rule, func + + def addRule(self, doc, func): + rules = doc.split() + + index = [] + for i in range(len(rules)): + if rules[i] == '::=': + index.append(i-1) + index.append(len(rules)) + + for i in range(len(index)-1): + lhs = rules[index[i]] + rhs = rules[index[i]+2:index[i+1]] + rule = (lhs, tuple(rhs)) + + rule, fn = self.preprocess(rule, func) + + if lhs in self.rules: + self.rules[lhs].append(rule) + else: + self.rules[lhs] = [ rule ] + self.rule2func[rule] = fn + self.rule2name[rule] = func.__name__[2:] + self.ruleschanged = 1 + + def collectRules(self): + for name in _namelist(self): + if name[:2] == 'p_': + func = getattr(self, name) + doc = func.__doc__ + self.addRule(doc, func) + + def augment(self, start): + # + # Tempting though it is, this isn't made into a call + # to self.addRule() because the start rule shouldn't + # be subject to preprocessing. + # + startRule = (self._START, ( start, self._EOF )) + self.rule2func[startRule] = lambda args: args[0] + self.rules[self._START] = [ startRule ] + self.rule2name[startRule] = '' + return startRule + + def makeFIRST(self): + union = {} + self.first = {} + + for rulelist in self.rules.values(): + for lhs, rhs in rulelist: + if lhs not in self.first: + self.first[lhs] = {} + + if len(rhs) == 0: + self.first[lhs][None] = 1 + continue + + sym = rhs[0] + if sym not in self.rules: + self.first[lhs][sym] = 1 + else: + union[(sym, lhs)] = 1 + changes = 1 + while changes: + changes = 0 + for src, dest in union.keys(): + destlen = len(self.first[dest]) + self.first[dest].update(self.first[src]) + if len(self.first[dest]) != destlen: + changes = 1 + + # + # An Earley parser, as per J. Earley, "An Efficient Context-Free + # Parsing Algorithm", CACM 13(2), pp. 94-102. Also J. C. Earley, + # "An Efficient Context-Free Parsing Algorithm", Ph.D. thesis, + # Carnegie-Mellon University, August 1968, p. 27. + # + + def typestring(self, token): + return None + + def error(self, token): + print "Syntax error at or near `%s' token" % token + raise SystemExit + + def parse(self, tokens): + tree = {} + tokens.append(self._EOF) + states = { 0: [ (self.startRule, 0, 0) ] } + + if self.ruleschanged: + self.makeFIRST() + + for i in xrange(len(tokens)): + states[i+1] = [] + + if states[i] == []: + break + self.buildState(tokens[i], states, i, tree) + + #_dump(tokens, states) + + if i < len(tokens)-1 or states[i+1] != [(self.startRule, 2, 0)]: + del tokens[-1] + self.error(tokens[i-1]) + rv = self.buildTree(tokens, tree, ((self.startRule, 2, 0), i+1)) + del tokens[-1] + return rv + + def buildState(self, token, states, i, tree): + needsCompletion = {} + state = states[i] + predicted = {} + + for item in state: + rule, pos, parent = item + lhs, rhs = rule + + # + # A -> a . (completer) + # + if pos == len(rhs): + if len(rhs) == 0: + needsCompletion[lhs] = (item, i) + + for pitem in states[parent]: + if pitem is item: + break + + prule, ppos, pparent = pitem + plhs, prhs = prule + + if prhs[ppos:ppos+1] == (lhs,): + new = (prule, + ppos+1, + pparent) + if new not in state: + state.append(new) + tree[(new, i)] = [(item, i)] + else: + tree[(new, i)].append((item, i)) + continue + + nextSym = rhs[pos] + + # + # A -> a . B (predictor) + # + if nextSym in self.rules: + # + # Work on completer step some more; for rules + # with empty RHS, the "parent state" is the + # current state we're adding Earley items to, + # so the Earley items the completer step needs + # may not all be present when it runs. + # + if nextSym in needsCompletion: + new = (rule, pos+1, parent) + olditem_i = needsCompletion[nextSym] + if new not in state: + state.append(new) + tree[(new, i)] = [olditem_i] + else: + tree[(new, i)].append(olditem_i) + + # + # Has this been predicted already? + # + if nextSym in predicted: + continue + predicted[nextSym] = 1 + + ttype = token is not self._EOF and \ + self.typestring(token) or \ + None + if ttype is not None: + # + # Even smarter predictor, when the + # token's type is known. The code is + # grungy, but runs pretty fast. Three + # cases are looked for: rules with + # empty RHS; first symbol on RHS is a + # terminal; first symbol on RHS is a + # nonterminal (and isn't nullable). + # + for prule in self.rules[nextSym]: + new = (prule, 0, i) + prhs = prule[1] + if len(prhs) == 0: + state.append(new) + continue + prhs0 = prhs[0] + if prhs0 not in self.rules: + if prhs0 != ttype: + continue + else: + state.append(new) + continue + first = self.first[prhs0] + if None not in first and \ + ttype not in first: + continue + state.append(new) + continue + + for prule in self.rules[nextSym]: + # + # Smarter predictor, as per Grune & + # Jacobs' _Parsing Techniques_. Not + # as good as FIRST sets though. + # + prhs = prule[1] + if len(prhs) > 0 and \ + prhs[0] not in self.rules and \ + token != prhs[0]: + continue + state.append((prule, 0, i)) + + # + # A -> a . c (scanner) + # + elif token == nextSym: + #assert new not in states[i+1] + states[i+1].append((rule, pos+1, parent)) + + def buildTree(self, tokens, tree, root): + stack = [] + self.buildTree_r(stack, tokens, -1, tree, root) + return stack[0] + + def buildTree_r(self, stack, tokens, tokpos, tree, root): + (rule, pos, parent), state = root + + while pos > 0: + want = ((rule, pos, parent), state) + if want not in tree: + # + # Since pos > 0, it didn't come from closure, + # and if it isn't in tree[], then there must + # be a terminal symbol to the left of the dot. + # (It must be from a "scanner" step.) + # + pos = pos - 1 + state = state - 1 + stack.insert(0, tokens[tokpos]) + tokpos = tokpos - 1 + else: + # + # There's a NT to the left of the dot. + # Follow the tree pointer recursively (>1 + # tree pointers from it indicates ambiguity). + # Since the item must have come about from a + # "completer" step, the state where the item + # came from must be the parent state of the + # item the tree pointer points to. + # + children = tree[want] + if len(children) > 1: + child = self.ambiguity(children) + else: + child = children[0] + + tokpos = self.buildTree_r(stack, + tokens, tokpos, + tree, child) + pos = pos - 1 + (crule, cpos, cparent), cstate = child + state = cparent + + lhs, rhs = rule + result = self.rule2func[rule](stack[:len(rhs)]) + stack[:len(rhs)] = [result] + return tokpos + + def ambiguity(self, children): + # + # XXX - problem here and in collectRules() if the same + # rule appears in >1 method. But in that case the + # user probably gets what they deserve :-) Also + # undefined results if rules causing the ambiguity + # appear in the same method. + # + sortlist = [] + name2index = {} + for i in range(len(children)): + ((rule, pos, parent), index) = children[i] + lhs, rhs = rule + name = self.rule2name[rule] + sortlist.append((len(rhs), name)) + name2index[name] = i + sortlist.sort() + list = map(lambda (a,b): b, sortlist) + return children[name2index[self.resolve(list)]] + + def resolve(self, list): + # + # Resolve ambiguity in favor of the shortest RHS. + # Since we walk the tree from the top down, this + # should effectively resolve in favor of a "shift". + # + return list[0] + +# +# GenericASTBuilder automagically constructs a concrete/abstract syntax tree +# for a given input. The extra argument is a class (not an instance!) +# which supports the "__setslice__" and "__len__" methods. +# +# XXX - silently overrides any user code in methods. +# + +class GenericASTBuilder(GenericParser): + def __init__(self, AST, start): + GenericParser.__init__(self, start) + self.AST = AST + + def preprocess(self, rule, func): + rebind = lambda lhs, self=self: \ + lambda args, lhs=lhs, self=self: \ + self.buildASTNode(args, lhs) + lhs, rhs = rule + return rule, rebind(lhs) + + def buildASTNode(self, args, lhs): + children = [] + for arg in args: + if isinstance(arg, self.AST): + children.append(arg) + else: + children.append(self.terminal(arg)) + return self.nonterminal(lhs, children) + + def terminal(self, token): return token + + def nonterminal(self, type, args): + rv = self.AST(type) + rv[:len(args)] = args + return rv + +# +# GenericASTTraversal is a Visitor pattern according to Design Patterns. For +# each node it attempts to invoke the method n_, falling +# back onto the default() method if the n_* can't be found. The preorder +# traversal also looks for an exit hook named n__exit (no default +# routine is called if it's not found). To prematurely halt traversal +# of a subtree, call the prune() method -- this only makes sense for a +# preorder traversal. Node type is determined via the typestring() method. +# + +class GenericASTTraversalPruningException: + pass + +class GenericASTTraversal: + def __init__(self, ast): + self.ast = ast + + def typestring(self, node): + return node.type + + def prune(self): + raise GenericASTTraversalPruningException + + def preorder(self, node=None): + if node is None: + node = self.ast + + try: + name = 'n_' + self.typestring(node) + if hasattr(self, name): + func = getattr(self, name) + func(node) + else: + self.default(node) + except GenericASTTraversalPruningException: + return + + for kid in node: + self.preorder(kid) + + name = name + '_exit' + if hasattr(self, name): + func = getattr(self, name) + func(node) + + def postorder(self, node=None): + if node is None: + node = self.ast + + for kid in node: + self.postorder(kid) + + name = 'n_' + self.typestring(node) + if hasattr(self, name): + func = getattr(self, name) + func(node) + else: + self.default(node) + + + def default(self, node): + pass + +# +# GenericASTMatcher. AST nodes must have "__getitem__" and "__cmp__" +# implemented. +# +# XXX - makes assumptions about how GenericParser walks the parse tree. +# + +class GenericASTMatcher(GenericParser): + def __init__(self, start, ast): + GenericParser.__init__(self, start) + self.ast = ast + + def preprocess(self, rule, func): + rebind = lambda func, self=self: \ + lambda args, func=func, self=self: \ + self.foundMatch(args, func) + lhs, rhs = rule + rhslist = list(rhs) + rhslist.reverse() + + return (lhs, tuple(rhslist)), rebind(func) + + def foundMatch(self, args, func): + func(args[-1]) + return args[-1] + + def match_r(self, node): + self.input.insert(0, node) + children = 0 + + for child in node: + if children == 0: + self.input.insert(0, '(') + children = children + 1 + self.match_r(child) + + if children > 0: + self.input.insert(0, ')') + + def match(self, ast=None): + if ast is None: + ast = self.ast + self.input = [] + + self.match_r(ast) + self.parse(self.input) + + def resolve(self, list): + # + # Resolve ambiguity in favor of the longest RHS. + # + return list[-1] + +def _dump(tokens, states): + for i in range(len(states)): + print 'state', i + for (lhs, rhs), pos, parent in states[i]: + print '\t', lhs, '::=', + print ' '.join(rhs[:pos]), + print '.', + print ' '.join(rhs[pos:]), + print ',', parent + if i < len(tokens): + print + print 'token', str(tokens[i]) + print diff --git a/binaries/src/globplot/biopython-1.50/Bio/PropertyManager.py b/binaries/src/globplot/biopython-1.50/Bio/PropertyManager.py new file mode 100644 index 0000000..05c27f7 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/PropertyManager.py @@ -0,0 +1,83 @@ +# Stores properties associated with the class of an object. + + +# Would it be nice to have support for more than one resolver per +# class? In the meanwhile, they could collude using a dispatch +# object. + +# Do you need access to the actual resolver? + +# Resolvers get the sequence because they may do a per-object lookup. + +# Could cache search results for better performance. + + +# Dictionary which creates dictionary elements, so lookups never fail. +# The new elements are always dictionaries. +class CreateDict(dict): + def __getitem__(self, key): + return self.setdefault(key,{}) + +class PropertyManager: + def __init__(self): + self.class_property = CreateDict() + self.class_property_resolver = CreateDict() + self.class_resolver = {} + + def resolve(self, obj, property): + try: + klass = obj.__class__ + except AttributeError: + raise KeyError("built-in instance") + + return self.resolve_class(klass, property) + + def resolve_class(self, klass, property): + # Hopefully, we'll find the hit right away + try: + return self.class_property[klass][property] + except KeyError: + pass + + # Is there a property resolver? + try: + return self.class_property_resolver[klass][property]( + self, klass, property) + except KeyError: + pass + + # What about the class resolver? + try: + return self.class_resolver[klass](self, klass, property) + except KeyError: + pass + + # That failed, so we walk up the class tree, depth-first and + # left-to-right (same as Python). For each class, check if + # the property exists, then check if the property resolver + # exists, and finally, check for the class resolver. + + bases = list(klass.__bases__) + while bases: + base = bases.pop() + try: + return self.class_property[base][property] + except KeyError: + pass + try: + return self.class_property_resolver[base][property]( + self, klass, property) + except KeyError: + pass + try: + return self.class_resolver[base](self, klass, property) + except KeyError: + pass + + # this is why the search is depth-first/right-left + bases[:0] = list(base.__bases__) + raise KeyError("cannot find property %s for class %s" \ + % (property, klass)) + + +default_manager = PropertyManager() diff --git a/binaries/src/globplot/biopython-1.50/Bio/PropertyManager.pyc b/binaries/src/globplot/biopython-1.50/Bio/PropertyManager.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2581ef5d399c232476b6e43ec338e6e6c56f4e14 GIT binary patch literal 1831 zcma)7O>Z1U5UrjWd)FpT2nzh&D>ObBd}+3PkRwo2RVB-I&*^N-$r*SQW&{3asf{wTticX9(6)OB{v!Ct=@%^8)x%L z2;6+(7HT%7e#(^|TGNGZA@wh+sq_8W%TQPTqA2^m8i)Sl!asR>{QZMZ9)4P9zx0Pi zHCu#>s>H%)ANP-os{cb>&0HN8PqQ+6=IV#oe+%ZUedaPVJR#t` zVUY6}8+|=N^Y_u=_?5zLqu1n^_#+oKAw5=Ep!4deq8x5xnolzC?Q98Bc>jM(wsyXn zyaa?B_0^IlS8Z(|Q8YpucC0PRB3Qc**>9s0a|^#bhQxsAjfU|E!eWo40d-!*a#kKm z0#-cVm8v5KAE*OzUm5w$NEwOwku=+u%UCYKY0`|C43w_Jc`*qOin3djK4fL?dUQrf zRt=wr9Zalk!nT1vydVS`-Pa-1#c&?nle(^I4dOeu&`E>X9@Lk?HzrhPg`TFVAutuw z4mD_{2!vbuL4JmkD}$HdaYvs~_D z)P0P)AAew)Yx#gv5;xWO-lfDVHrJ}u6vH+)-b2%8zioE5cG?4d9U99Be>F`RqMlaD iGB_PL:ATF18F4 AL021637 Arabidopsis thaliana DNA chromosome 4, BAC clone +# F18F4 (ESSAII project). 2/98 +# Length = 93,646 +# +# Minus Strand HSPs: +# +# Score = 226 (33.9 bits), Expect = 0.80, P = 0.55 +# Identities = 98/142 (69%), Positives = 98/142 (69%), Strand = Minus / Plus +# [...lines deleted...] +# Query: 2486 ATATCAAGCAATTTGATAAGATCTAG 2461 +# A AT A C ATT GA AAGATC AG +# Sbjct: 85387 AGATTTACCTATT-GAGAAGATCAAG 85411 + +# computed from the strings +class _SeqLength: + def __init__(self, length, identical, positives, gaps): + self.length = length + self.identical = identical + self.positives = positives + self.gaps = gaps + def __len__(self): + return self.length + def __getattr__(self, name): + if name == "frac_identical": + return float(self.identical) / self.length + elif name == "frac_positives": + return float(self.positives) / self.length + raise AttributeError(name) + + +class HomologySeq(_SeqLength): + def __init__(self, seq, identical, positives, gaps): + _SeqLength.__init__(self, len(seq), identical, positives, gaps) + self.seq = seq + +class HSPSeq(_SeqLength): + def __init__(self, name, seq, location, identical, positives, gaps): + _SeqLength.__init__(self, len(seq), identical, positives, gaps) + self.name = name + self.seq = seq + self.location = location + + +class HSP(_SeqLength): + def __init__(self, + query_seq, # ATATCAAGCAATTTGATAAGATCTAG + homology_seq, # A AT A C ATT GA AAGATC AG + subject_seq, # AGATTTACCTATT-GAGAAGATCAAG + + query_location, # (2486, 2461, negative strand) + subject_location, # (85387, 85411) + + query_name, # Query (or None) + subject_name, # Sbjct (or None) + + algorithm, # an Algorithm + info, # contains Key/value pairs + homology_gaps = None, # Is this needed? + ): + assert len(query_seq) == len(homology_seq) == len(subject_seq), \ + (query_seq, homology_seq, subject_seq) + self.algorithm = algorithm + + query_gaps = query_seq.count("-") + subject_gaps = subject_seq.count("-") + if homology_gaps is None: + homology_gaps = query_gaps + subject_gaps + self.info = info + + identical = info["identical"] + # bioperl calls this 'conserved' + positives = info.get("positives", identical) + + _SeqLength.__init__(self, len(query_seq), identical, + positives, homology_gaps) + + self.query = HSPSeq(name = query_name, + seq = query_seq, + location = query_location, + identical = identical, + positives = positives, + gaps = query_gaps) + + self.subject = HSPSeq(name = subject_name, + seq = subject_seq, + location = subject_location, + identical = identical, + positives = positives, + gaps = subject_gaps) + self.homology = HomologySeq(seq = homology_seq, + identical = identical, + positives = positives, + gaps = homology_gaps) diff --git a/binaries/src/globplot/biopython-1.50/Bio/Seq.py b/binaries/src/globplot/biopython-1.50/Bio/Seq.py new file mode 100644 index 0000000..d32d8e7 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Seq.py @@ -0,0 +1,1633 @@ +# Copyright 2000-2002 Brad Chapman. +# Copyright 2004-2005 by M de Hoon. +# Copyright 2007-2009 by Peter Cock. +# All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +"""Provides objects to represent biological sequences with alphabets. + +See also U{http://biopython.org/wiki/Seq} and the chapter in our tutorial: + - U{http://biopython.org/DIST/docs/tutorial/Tutorial.html} + - U{http://biopython.org/DIST/docs/tutorial/Tutorial.pdf} +""" +__docformat__ ="epytext en" #Don't just use plain text in epydoc API pages! + +import string #for maketrans only +import array +import sys + +#TODO - Remove this work around once we drop python 2.3 support +try: + set = set +except NameError: + from sets import Set as set + +import Alphabet +from Alphabet import IUPAC +from Data.IUPACData import ambiguous_dna_complement, ambiguous_rna_complement +from Bio.Data import CodonTable + +def _maketrans(complement_mapping) : + """Makes a python string translation table (PRIVATE). + + Arguments: + - complement_mapping - a dictionary such as ambiguous_dna_complement + and ambiguous_rna_complement from Data.IUPACData. + + Returns a translation table (a string of length 256) for use with the + python string's translate method to use in a (reverse) complement. + + Compatible with lower case and upper case sequences. + + For internal use only. + """ + before = ''.join(complement_mapping.keys()) + after = ''.join(complement_mapping.values()) + before = before + before.lower() + after = after + after.lower() + return string.maketrans(before, after) + +_dna_complement_table = _maketrans(ambiguous_dna_complement) +_rna_complement_table = _maketrans(ambiguous_rna_complement) + +class Seq(object): + """A read-only sequence object (essentially a string with an alphabet). + + Like normal python strings, our basic sequence object is immutable. + This prevents you from doing my_seq[5] = "A" for example, but does allow + Seq objects to be used as dictionary keys. + + The Seq object provides a number of string like methods (such as count, + find, split and strip), which are alphabet aware where appropriate. + + The Seq object also provides some biological methods, such as complement, + reverse_complement, transcribe, back_transcribe and translate (which are + not applicable to sequences with a protein alphabet). + """ + def __init__(self, data, alphabet = Alphabet.generic_alphabet): + """Create a Seq object. + + Arguments: + - seq - Sequence, required (string) + - alphabet - Optional argument, an Alphabet object from Bio.Alphabet + + You will typically use Bio.SeqIO to read in sequences from files as + SeqRecord objects, whose sequence will be exposed as a Seq object via + the seq property. + + However, will often want to create your own Seq objects directly: + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import IUPAC + >>> my_seq = Seq("MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF", + ... IUPAC.protein) + >>> my_seq + Seq('MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF', IUPACProtein()) + >>> print my_seq + MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF + """ + # Enforce string storage + assert (type(data) == type("") or # must use a string + type(data) == type(u"")) # but can be a unicode string + self._data = data + self.alphabet = alphabet # Seq API requirement + + # A data property is/was a Seq API requirement + def _set_data(self, value) : + #TODO - In the next release, actually raise an exception? + #The Seq object is like a python string, it should be read only! + import warnings + warnings.warn("Writing to the Seq object's .data propery is deprecated.", + DeprecationWarning) + self._data = value + data = property(fget= lambda self : str(self), + fset=_set_data, + doc="Sequence as a string (DEPRECATED)") + + def __repr__(self): + """Returns a (truncated) representation of the sequence for debugging.""" + if len(self) > 60 : + #Shows the last three letters as it is often useful to see if there + #is a stop codon at the end of a sequence. + #Note total length is 54+3+3=60 + return "%s('%s...%s', %s)" % (self.__class__.__name__, + str(self)[:54], str(self)[-3:], + repr(self.alphabet)) + else : + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self.data), + repr(self.alphabet)) + def __str__(self): + """Returns the full sequence as a python string. + + Note that Biopython 1.44 and earlier would give a truncated + version of repr(my_seq) for str(my_seq). If you are writing code + which need to be backwards compatible with old Biopython, you + should continue to use my_seq.tostring() rather than str(my_seq). + """ + return self._data + + """ + TODO - Work out why this breaks test_Restriction.py + (Comparing Seq objects would be nice to have. May need to think about + hashes and the in operator for when have list/dictionary of Seq objects...) + def __cmp__(self, other): + if hasattr(other, "alphabet") : + #other should be a Seq or a MutableSeq + if not Alphabet._check_type_compatible([self.alphabet, + other.alphabet]) : + raise TypeError("Incompatable alphabets %s and %s" \ + % (repr(self.alphabet), repr(other.alphabet))) + #They should be the same sequence type (or one of them is generic) + return cmp(str(self), str(other)) + elif isinstance(other, basestring) : + return cmp(str(self), other) + else : + raise TypeError + """ + + def __len__(self): return len(self._data) # Seq API requirement + + def __getitem__(self, index) : # Seq API requirement + #Note since Python 2.0, __getslice__ is deprecated + #and __getitem__ is used instead. + #See http://docs.python.org/ref/sequence-methods.html + if isinstance(index, int) : + #Return a single letter as a string + return self._data[index] + else : + #Return the (sub)sequence as another Seq object + return Seq(self._data[index], self.alphabet) + + def __add__(self, other): + """Add another sequence or string to this sequence.""" + if hasattr(other, "alphabet") : + #other should be a Seq or a MutableSeq + if not Alphabet._check_type_compatible([self.alphabet, + other.alphabet]) : + raise TypeError("Incompatable alphabets %s and %s" \ + % (repr(self.alphabet), repr(other.alphabet))) + #They should be the same sequence type (or one of them is generic) + a = Alphabet._consensus_alphabet([self.alphabet, other.alphabet]) + return self.__class__(str(self) + str(other), a) + elif isinstance(other, basestring) : + #other is a plain string - use the current alphabet + return self.__class__(str(self) + other, self.alphabet) + else : + raise TypeError + + def __radd__(self, other): + if hasattr(other, "alphabet") : + #other should be a Seq or a MutableSeq + if not Alphabet._check_type_compatible([self.alphabet, + other.alphabet]) : + raise TypeError("Incompatable alphabets %s and %s" \ + % (repr(self.alphabet), repr(other.alphabet))) + #They should be the same sequence type (or one of them is generic) + a = Alphabet._consensus_alphabet([self.alphabet, other.alphabet]) + return self.__class__(str(other) + str(self), a) + elif isinstance(other, basestring) : + #other is a plain string - use the current alphabet + return self.__class__(other + str(self), self.alphabet) + else : + raise TypeError + + def tostring(self): # Seq API requirement + """Returns the full sequence as a python string. + + Although not formally deprecated, you are now encouraged to use + str(my_seq) instead of my_seq.tostring().""" + return str(self) + + def tomutable(self): # Needed? Or use a function? + """Returns the full sequence as a MutableSeq object. + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import IUPAC + >>> my_seq = Seq("MKQHKAMIVALIVICITAVVAAL", + ... IUPAC.protein) + >>> my_seq + Seq('MKQHKAMIVALIVICITAVVAAL', IUPACProtein()) + >>> my_seq.tomutable() + MutableSeq('MKQHKAMIVALIVICITAVVAAL', IUPACProtein()) + + Note that the alphabet is preserved. + """ + return MutableSeq(str(self), self.alphabet) + + def _get_seq_str_and_check_alphabet(self, other_sequence) : + """string/Seq/MutableSeq to string, checking alphabet (PRIVATE). + + For a string argument, returns the string. + + For a Seq or MutableSeq, it checks the alphabet is compatible + (raising an exception if it isn't), and then returns a string. + """ + try : + other_alpha = other_sequence.alphabet + except AttributeError : + #Assume other_sequence is a string + return other_sequence + + #Other should be a Seq or a MutableSeq + if not Alphabet._check_type_compatible([self.alphabet, other_alpha]) : + raise TypeError("Incompatable alphabets %s and %s" \ + % (repr(self.alphabet), repr(other_alpha))) + #Return as a string + return str(other_sequence) + + def count(self, sub, start=0, end=sys.maxint): + """Non-overlapping count method, like that of a python string. + + This behaves like the python string method of the same name, + which does a non-overlapping count! + + Returns an integer, the number of occurrences of substring + argument sub in the (sub)sequence given by [start:end]. + Optional arguments start and end are interpreted as in slice + notation. + + Arguments: + - sub - a string or another Seq object to look for + - start - optional integer, slice start + - end - optional integer, slice end + + e.g. + + >>> from Bio.Seq import Seq + >>> my_seq = Seq("AAAATGA") + >>> print my_seq.count("A") + 5 + >>> print my_seq.count("ATG") + 1 + >>> print my_seq.count(Seq("AT")) + 1 + >>> print my_seq.count("AT", 2, -1) + 1 + + HOWEVER, please note because python strings and Seq objects (and + MutableSeq objects) do a non-overlapping search, this may not give + the answer you expect: + + >>> "AAAA".count("AA") + 2 + >>> print Seq("AAAA").count("AA") + 2 + + A non-overlapping search would give the answer as three! + """ + #If it has one, check the alphabet: + sub_str = self._get_seq_str_and_check_alphabet(sub) + return str(self).count(sub_str, start, end) + + def find(self, sub, start=0, end=sys.maxint): + """Find method, like that of a python string. + + This behaves like the python string method of the same name. + + Returns an integer, the index of the first occurrence of substring + argument sub in the (sub)sequence given by [start:end]. + + Arguments: + - sub - a string or another Seq object to look for + - start - optional integer, slice start + - end - optional integer, slice end + + Returns -1 if the subsequence is NOT found. + + e.g. Locating the first typical start codon, AUG, in an RNA sequence: + + >>> from Bio.Seq import Seq + >>> my_rna = Seq("GUCAUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAGUUG") + >>> my_rna.find("AUG") + 3 + """ + #If it has one, check the alphabet: + sub_str = self._get_seq_str_and_check_alphabet(sub) + return str(self).find(sub_str, start, end) + + def rfind(self, sub, start=0, end=sys.maxint): + """Find from right method, like that of a python string. + + This behaves like the python string method of the same name. + + Returns an integer, the index of the last (right most) occurrence of + substring argument sub in the (sub)sequence given by [start:end]. + + Arguments: + - sub - a string or another Seq object to look for + - start - optional integer, slice start + - end - optional integer, slice end + + Returns -1 if the subsequence is NOT found. + + e.g. Locating the last typical start codon, AUG, in an RNA sequence: + + >>> from Bio.Seq import Seq + >>> my_rna = Seq("GUCAUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAGUUG") + >>> my_rna.rfind("AUG") + 15 + """ + #If it has one, check the alphabet: + sub_str = self._get_seq_str_and_check_alphabet(sub) + return str(self).rfind(sub_str, start, end) + + def startswith(self, prefix, start=0, end=sys.maxint) : + """Does the Seq start with the given prefix? Returns True/False. + + This behaves like the python string method of the same name. + + Return True if the sequence starts with the specified prefix + (a string or another Seq object), False otherwise. + With optional start, test sequence beginning at that position. + With optional end, stop comparing sequence at that position. + prefix can also be a tuple of strings to try. e.g. + + >>> from Bio.Seq import Seq + >>> my_rna = Seq("GUCAUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAGUUG") + >>> my_rna.startswith("GUC") + True + >>> my_rna.startswith("AUG") + False + >>> my_rna.startswith("AUG", 3) + True + >>> my_rna.startswith(("UCC","UCA","UCG"),1) + True + """ + #If it has one, check the alphabet: + if isinstance(prefix, tuple) : + #TODO - Once we drop support for Python 2.4, instead of this + #loop offload to the string method (requires Python 2.5+). + #Check all the alphabets first... + prefix_strings = [self._get_seq_str_and_check_alphabet(p) \ + for p in prefix] + for prefix_str in prefix_strings : + if str(self).startswith(prefix_str, start, end) : + return True + return False + else : + prefix_str = self._get_seq_str_and_check_alphabet(prefix) + return str(self).startswith(prefix_str, start, end) + + def endswith(self, suffix, start=0, end=sys.maxint) : + """Does the Seq end with the given suffix? Returns True/False. + + This behaves like the python string method of the same name. + + Return True if the sequence ends with the specified suffix + (a string or another Seq object), False otherwise. + With optional start, test sequence beginning at that position. + With optional end, stop comparing sequence at that position. + suffix can also be a tuple of strings to try. e.g. + + >>> from Bio.Seq import Seq + >>> my_rna = Seq("GUCAUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAGUUG") + >>> my_rna.endswith("UUG") + True + >>> my_rna.endswith("AUG") + False + >>> my_rna.endswith("AUG", 0, 18) + True + >>> my_rna.endswith(("UCC","UCA","UUG")) + True + """ + #If it has one, check the alphabet: + if isinstance(suffix, tuple) : + #TODO - Once we drop support for Python 2.4, instead of this + #loop offload to the string method (requires Python 2.5+). + #Check all the alphabets first... + suffix_strings = [self._get_seq_str_and_check_alphabet(p) \ + for p in suffix] + for suffix_str in suffix_strings : + if str(self).endswith(suffix_str, start, end) : + return True + return False + else : + suffix_str = self._get_seq_str_and_check_alphabet(suffix) + return str(self).endswith(suffix_str, start, end) + + + def split(self, sep=None, maxsplit=-1) : + """Split method, like that of a python string. + + This behaves like the python string method of the same name. + + Return a list of the 'words' in the string (as Seq objects), + using sep as the delimiter string. If maxsplit is given, at + most maxsplit splits are done. If maxsplit is ommited, all + splits are made. + + Following the python string method, sep will by default be any + white space (tabs, spaces, newlines) but this is unlikely to + apply to biological sequences. + + e.g. + + >>> from Bio.Seq import Seq + >>> my_rna = Seq("GUCAUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAGUUG") + >>> my_aa = my_rna.translate() + >>> my_aa + Seq('VMAIVMGR*KGAR*L', HasStopCodon(ExtendedIUPACProtein(), '*')) + >>> my_aa.split("*") + [Seq('VMAIVMGR', HasStopCodon(ExtendedIUPACProtein(), '*')), Seq('KGAR', HasStopCodon(ExtendedIUPACProtein(), '*')), Seq('L', HasStopCodon(ExtendedIUPACProtein(), '*'))] + >>> my_aa.split("*",1) + [Seq('VMAIVMGR', HasStopCodon(ExtendedIUPACProtein(), '*')), Seq('KGAR*L', HasStopCodon(ExtendedIUPACProtein(), '*'))] + + See also the rsplit method: + + >>> my_aa.rsplit("*",1) + [Seq('VMAIVMGR*KGAR', HasStopCodon(ExtendedIUPACProtein(), '*')), Seq('L', HasStopCodon(ExtendedIUPACProtein(), '*'))] + """ + #If it has one, check the alphabet: + sep_str = self._get_seq_str_and_check_alphabet(sep) + #TODO - If the sep is the defined stop symbol, or gap char, + #should we adjust the alphabet? + return [Seq(part, self.alphabet) \ + for part in str(self).split(sep_str, maxsplit)] + + def rsplit(self, sep=None, maxsplit=-1) : + """Right split method, like that of a python string. + + This behaves like the python string method of the same name. + + Return a list of the 'words' in the string (as Seq objects), + using sep as the delimiter string. If maxsplit is given, at + most maxsplit splits are done COUNTING FROM THE RIGHT. + If maxsplit is ommited, all splits are made. + + Following the python string method, sep will by default be any + white space (tabs, spaces, newlines) but this is unlikely to + apply to biological sequences. + + e.g. print my_seq.rsplit("*",1) + + See also the split method. + """ + #If it has one, check the alphabet: + sep_str = self._get_seq_str_and_check_alphabet(sep) + try : + return [Seq(part, self.alphabet) \ + for part in str(self).rsplit(sep_str, maxsplit)] + except AttributeError : + #Python 2.3 doesn't have a string rsplit method, which we can + #word around by reversing the sequence, using (left) split, + #and then reversing the answer. Not very efficient! + words = [Seq(word[::-1], self.alphabet) for word \ + in str(self)[::-1].split(sep_str[::-1], maxsplit)] + words.reverse() + return words + + def strip(self, chars=None) : + """Returns a new Seq object with leading and trailing ends stripped. + + This behaves like the python string method of the same name. + + Optional argument chars defines which characters to remove. If + ommitted or None (default) then as for the python string method, + this defaults to removing any white space. + + e.g. print my_seq.strip("-") + + See also the lstrip and rstrip methods. + """ + #If it has one, check the alphabet: + strip_str = self._get_seq_str_and_check_alphabet(chars) + return Seq(str(self).strip(strip_str), self.alphabet) + + def lstrip(self, chars=None) : + """Returns a new Seq object with leading (left) end stripped. + + This behaves like the python string method of the same name. + + Optional argument chars defines which characters to remove. If + ommitted or None (default) then as for the python string method, + this defaults to removing any white space. + + e.g. print my_seq.lstrip("-") + + See also the strip and rstrip methods. + """ + #If it has one, check the alphabet: + strip_str = self._get_seq_str_and_check_alphabet(chars) + return Seq(str(self).lstrip(strip_str), self.alphabet) + + def rstrip(self, chars=None) : + """Returns a new Seq object with trailing (right) end stripped. + + This behaves like the python string method of the same name. + + Optional argument chars defines which characters to remove. If + ommitted or None (default) then as for the python string method, + this defaults to removing any white space. + + e.g. Removing a nucleotide sequence's polyadenylation (poly-A tail): + + >>> from Bio.Alphabet import IUPAC + >>> from Bio.Seq import Seq + >>> my_seq = Seq("CGGTACGCTTATGTCACGTAGAAAAAA", IUPAC.unambiguous_dna) + >>> my_seq + Seq('CGGTACGCTTATGTCACGTAGAAAAAA', IUPACUnambiguousDNA()) + >>> my_seq.rstrip("A") + Seq('CGGTACGCTTATGTCACGTAG', IUPACUnambiguousDNA()) + + See also the strip and lstrip methods. + """ + #If it has one, check the alphabet: + strip_str = self._get_seq_str_and_check_alphabet(chars) + return Seq(str(self).rstrip(strip_str), self.alphabet) + + def complement(self): + """Returns the complement sequence. New Seq object. + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import IUPAC + >>> my_dna = Seq("CCCCCGATAG", IUPAC.unambiguous_dna) + >>> my_dna + Seq('CCCCCGATAG', IUPACUnambiguousDNA()) + >>> my_dna.complement() + Seq('GGGGGCTATC', IUPACUnambiguousDNA()) + + You can of course used mixed case sequences, + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import generic_dna + >>> my_dna = Seq("CCCCCgatA-GD", generic_dna) + >>> my_dna + Seq('CCCCCgatA-GD', DNAAlphabet()) + >>> my_dna.complement() + Seq('GGGGGctaT-CH', DNAAlphabet()) + + Note in the above example, ambiguous character D denotes + G, A or T so its complement is H (for C, T or A). + + Trying to complement a protein sequence raises an exception. + + >>> my_protein = Seq("MAIVMGR", IUPAC.protein) + >>> my_protein.complement() + Traceback (most recent call last): + ... + ValueError: Proteins do not have complements! + """ + base = Alphabet._get_base_alphabet(self.alphabet) + if isinstance(base, Alphabet.ProteinAlphabet) : + raise ValueError("Proteins do not have complements!") + if isinstance(base, Alphabet.DNAAlphabet) : + ttable = _dna_complement_table + elif isinstance(base, Alphabet.RNAAlphabet) : + ttable = _rna_complement_table + elif ('U' in self._data or 'u' in self._data) \ + and ('T' in self._data or 't' in self._data): + #TODO - Handle this cleanly? + raise ValueError("Mixed RNA/DNA found") + elif 'U' in self._data or 'u' in self._data: + ttable = _rna_complement_table + else: + ttable = _dna_complement_table + #Much faster on really long sequences than the previous loop based one. + #thx to Michael Palmer, University of Waterloo + return Seq(str(self).translate(ttable), self.alphabet) + + def reverse_complement(self): + """Returns the reverse complement sequence. New Seq object. + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import IUPAC + >>> my_dna = Seq("CCCCCGATAGNR", IUPAC.ambiguous_dna) + >>> my_dna + Seq('CCCCCGATAGNR', IUPACAmbiguousDNA()) + >>> my_dna.reverse_complement() + Seq('YNCTATCGGGGG', IUPACAmbiguousDNA()) + + Note in the above example, since R = G or A, its complement + is Y (which denotes C or T). + + You can of course used mixed case sequences, + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import generic_dna + >>> my_dna = Seq("CCCCCgatA-G", generic_dna) + >>> my_dna + Seq('CCCCCgatA-G', DNAAlphabet()) + >>> my_dna.reverse_complement() + Seq('C-TatcGGGGG', DNAAlphabet()) + + Trying to complement a protein sequence raises an exception: + + >>> my_protein = Seq("MAIVMGR", IUPAC.protein) + >>> my_protein.reverse_complement() + Traceback (most recent call last): + ... + ValueError: Proteins do not have complements! + """ + #Use -1 stride/step to reverse the complement + return self.complement()[::-1] + + def transcribe(self): + """Returns the RNA sequence from a DNA sequence. New Seq object. + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import IUPAC + >>> coding_dna = Seq("ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG", + ... IUPAC.unambiguous_dna) + >>> coding_dna + Seq('ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG', IUPACUnambiguousDNA()) + >>> coding_dna.transcribe() + Seq('AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG', IUPACUnambiguousRNA()) + + Trying to transcribe a protein or RNA sequence raises an exception: + + >>> my_protein = Seq("MAIVMGR", IUPAC.protein) + >>> my_protein.transcribe() + Traceback (most recent call last): + ... + ValueError: Proteins cannot be transcribed! + """ + base = Alphabet._get_base_alphabet(self.alphabet) + if isinstance(base, Alphabet.ProteinAlphabet) : + raise ValueError("Proteins cannot be transcribed!") + if isinstance(base, Alphabet.RNAAlphabet) : + raise ValueError("RNA cannot be transcribed!") + + if self.alphabet==IUPAC.unambiguous_dna: + alphabet = IUPAC.unambiguous_rna + elif self.alphabet==IUPAC.ambiguous_dna: + alphabet = IUPAC.ambiguous_rna + else: + alphabet = Alphabet.generic_rna + return Seq(str(self).replace('T','U').replace('t','u'), alphabet) + + def back_transcribe(self): + """Returns the DNA sequence from an RNA sequence. New Seq object. + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import IUPAC + >>> messenger_rna = Seq("AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG", + ... IUPAC.unambiguous_rna) + >>> messenger_rna + Seq('AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG', IUPACUnambiguousRNA()) + >>> messenger_rna.back_transcribe() + Seq('ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG', IUPACUnambiguousDNA()) + + Trying to back-transcribe a protein or DNA sequence raises an + exception: + + >>> my_protein = Seq("MAIVMGR", IUPAC.protein) + >>> my_protein.back_transcribe() + Traceback (most recent call last): + ... + ValueError: Proteins cannot be back transcribed! + """ + base = Alphabet._get_base_alphabet(self.alphabet) + if isinstance(base, Alphabet.ProteinAlphabet) : + raise ValueError("Proteins cannot be back transcribed!") + if isinstance(base, Alphabet.DNAAlphabet) : + raise ValueError("DNA cannot be back transcribed!") + + if self.alphabet==IUPAC.unambiguous_rna: + alphabet = IUPAC.unambiguous_dna + elif self.alphabet==IUPAC.ambiguous_rna: + alphabet = IUPAC.ambiguous_dna + else: + alphabet = Alphabet.generic_dna + return Seq(str(self).replace("U", "T").replace("u", "t"), alphabet) + + def translate(self, table="Standard", stop_symbol="*", to_stop=False): + """Turns a nucleotide sequence into a protein sequence. New Seq object. + + This method will translate DNA or RNA sequences, and those with a + nucleotide or generic alphabet. Trying to translate a protein + sequence raises an exception. + + Arguments: + - table - Which codon table to use? This can be either a name + (string) or an NCBI identifier (integer). This defaults + to the "Standard" table. + - stop_symbol - Single character string, what to use for terminators. + This defaults to the asterisk, "*". + - to_stop - Boolean, defaults to False meaning do a full translation + continuing on past any stop codons (translated as the + specified stop_symbol). If True, translation is + terminated at the first in frame stop codon (and the + stop_symbol is not appended to the returned protein + sequence). + + e.g. Using the standard table: + + >>> coding_dna = Seq("GTGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG") + >>> coding_dna.translate() + Seq('VAIVMGR*KGAR*', HasStopCodon(ExtendedIUPACProtein(), '*')) + >>> coding_dna.translate(stop_symbol="@") + Seq('VAIVMGR@KGAR@', HasStopCodon(ExtendedIUPACProtein(), '@')) + >>> coding_dna.translate(to_stop=True) + Seq('VAIVMGR', ExtendedIUPACProtein()) + + Now using NCBI table 2, where TGA is not a stop codon: + + >>> coding_dna.translate(table=2) + Seq('VAIVMGRWKGAR*', HasStopCodon(ExtendedIUPACProtein(), '*')) + >>> coding_dna.translate(table=2, to_stop=True) + Seq('VAIVMGRWKGAR', ExtendedIUPACProtein()) + + If the sequence has no in-frame stop codon, then the to_stop argument + has no effect: + + >>> coding_dna2 = Seq("TTGGCCATTGTAATGGGCCGC") + >>> coding_dna2.translate() + Seq('LAIVMGR', ExtendedIUPACProtein()) + >>> coding_dna2.translate(to_stop=True) + Seq('LAIVMGR', ExtendedIUPACProtein()) + + NOTE - Ambiguous codons like "TAN" or "NNN" could be an amino acid + or a stop codon. These are translated as "X". Any invalid codon + (e.g. "TA?" or "T-A") will throw a TranslationError. + + NOTE - Does NOT support gapped sequences. + + NOTE - This does NOT behave like the python string's translate + method. For that use str(my_seq).translate(...) instead. + """ + try: + table_id = int(table) + except ValueError: + table_id = None + if isinstance(table, str) and len(table)==256 : + raise ValueError("The Seq object translate method DOES NOT take " \ + + "a 256 character string mapping table like " \ + + "the python string object's translate method. " \ + + "Use str(my_seq).translate(...) instead.") + if isinstance(Alphabet._get_base_alphabet(self.alphabet), + Alphabet.ProteinAlphabet) : + raise ValueError("Proteins cannot be translated!") + if self.alphabet==IUPAC.unambiguous_dna: + #Will use standard IUPAC protein alphabet, no need for X + if table_id is None: + codon_table = CodonTable.unambiguous_dna_by_name[table] + else: + codon_table = CodonTable.unambiguous_dna_by_id[table_id] + #elif self.alphabet==IUPAC.ambiguous_dna: + # if table_id is None: + # codon_table = CodonTable.ambiguous_dna_by_name[table] + # else: + # codon_table = CodonTable.ambiguous_dna_by_id[table_id] + elif self.alphabet==IUPAC.unambiguous_rna: + #Will use standard IUPAC protein alphabet, no need for X + if table_id is None: + codon_table = CodonTable.unambiguous_rna_by_name[table] + else: + codon_table = CodonTable.unambiguous_rna_by_id[table_id] + #elif self.alphabet==IUPAC.ambiguous_rna: + # if table_id is None: + # codon_table = CodonTable.ambiguous_rna_by_name[table] + # else: + # codon_table = CodonTable.ambiguous_rna_by_id[table_id] + else: + #This will use the extend IUPAC protein alphabet with X etc. + #The same table can be used for RNA or DNA (we use this for + #translating strings). + if table_id is None: + codon_table = CodonTable.ambiguous_generic_by_name[table] + else: + codon_table = CodonTable.ambiguous_generic_by_id[table_id] + protein = _translate_str(str(self), codon_table, stop_symbol, to_stop) + if stop_symbol in protein : + alphabet = Alphabet.HasStopCodon(codon_table.protein_alphabet, + stop_symbol = stop_symbol) + else : + alphabet = codon_table.protein_alphabet + return Seq(protein, alphabet) + +class UnknownSeq(Seq): + """A read-only sequence object of known length but unknown contents. + + If you have an unknown sequence, you can represent this with a normal + Seq object, for example: + + >>> my_seq = Seq("N"*5) + >>> my_seq + Seq('NNNNN', Alphabet()) + >>> len(my_seq) + 5 + >>> print my_seq + NNNNN + + However, this is rather wasteful of memory (especially for large + sequences), which is where this class is most usefull: + + >>> unk_five = UnknownSeq(5) + >>> unk_five + UnknownSeq(5, alphabet = Alphabet(), character = '?') + >>> len(unk_five) + 5 + >>> print(unk_five) + ????? + + You can add unknown sequence together, provided their alphabets and + characters are compatible, and get another memory saving UnknownSeq: + + >>> unk_four = UnknownSeq(4) + >>> unk_four + UnknownSeq(4, alphabet = Alphabet(), character = '?') + >>> unk_four + unk_five + UnknownSeq(9, alphabet = Alphabet(), character = '?') + + If the alphabet or characters don't match up, the addition gives an + ordinary Seq object: + + >>> unk_nnnn = UnknownSeq(4, character = "N") + >>> unk_nnnn + UnknownSeq(4, alphabet = Alphabet(), character = 'N') + >>> unk_nnnn + unk_four + Seq('NNNN????', Alphabet()) + + Combining with a real Seq gives a new Seq object: + + >>> known_seq = Seq("ACGT") + >>> unk_four + known_seq + Seq('????ACGT', Alphabet()) + >>> known_seq + unk_four + Seq('ACGT????', Alphabet()) + """ + def __init__(self, length, alphabet = Alphabet.generic_alphabet, character = None) : + """Create a new UnknownSeq object. + + If character is ommited, it is determed from the alphabet, "N" for + nucleotides, "X" for proteins, and "?" otherwise. + """ + self._length = int(length) + if self._length < 0 : + #TODO - Block zero length UnknownSeq? You can just use a Seq! + raise ValueError("Length must not be negative.") + self.alphabet = alphabet + if character : + if len(character) != 1 : + raise ValueError("character argument should be a single letter string.") + self._character = character + else : + base = Alphabet._get_base_alphabet(alphabet) + #TODO? Check the case of the letters in the alphabet? + #We may have to use "n" instead of "N" etc. + if isinstance(base, Alphabet.NucleotideAlphabet) : + self._character = "N" + elif isinstance(base, Alphabet.ProteinAlphabet) : + self._character = "X" + else : + self._character = "?" + + def __len__(self) : + """Returns the stated length of the unknown sequence.""" + return self._length + + def __str__(self) : + """Returns the unknown sequence as full string of the given length.""" + return self._character * self._length + + def __repr__(self): + return "UnknownSeq(%i, alphabet = %s, character = %s)" \ + % (self._length, repr(self.alphabet), repr(self._character)) + + def __add__(self, other) : + if isinstance(other, UnknownSeq) \ + and other._character == self._character : + #TODO - Check the alphabets match + return UnknownSeq(len(self)+len(other), + self.alphabet, self._character) + #Offload to the base class... + return Seq(str(self), self.alphabet) + other + + def __radd__(self, other) : + if isinstance(other, UnknownSeq) \ + and other._character == self._character : + #TODO - Check the alphabets match + return UnknownSeq(len(self)+len(other), + self.alphabet, self._character) + #Offload to the base class... + return other + Seq(str(self), self.alphabet) + + def __getitem__(self, index): + if isinstance(index, int) : + #TODO - Check the bounds without wasting memory + return str(self)[index] + else : + #TODO - Work out the length without wasting memory + return UnknownSeq(len(("#"*self._length)[index]), + self.alphabet, self._character) + + def count(self, sub, start=0, end=sys.maxint): + """Non-overlapping count method, like that of a python string. + + This behaves like the python string (and Seq object) method of the + same name, which does a non-overlapping count! + + Returns an integer, the number of occurrences of substring + argument sub in the (sub)sequence given by [start:end]. + Optional arguments start and end are interpreted as in slice + notation. + + Arguments: + - sub - a string or another Seq object to look for + - start - optional integer, slice start + - end - optional integer, slice end + + >>> "NNNN".count("N") + 4 + >>> Seq("NNNN").count("N") + 4 + >>> UnknownSeq(4, character="N").count("N") + 4 + >>> UnknownSeq(4, character="N").count("A") + 0 + >>> UnknownSeq(4, character="N").count("AA") + 0 + + HOWEVER, please note because that python strings and Seq objects (and + MutableSeq objects) do a non-overlapping search, this may not give + the answer you expect: + + >>> UnknownSeq(4, character="N").count("NN") + 2 + >>> UnknownSeq(4, character="N").count("NNN") + 1 + """ + sub_str = self._get_seq_str_and_check_alphabet(sub) + if len(sub_str) == 1 : + if str(sub_str) == self._character : + if start==0 and end >= self._length : + return self._length + else : + #This could be done more cleverly... + return str(self).count(sub_str, start, end) + else : + return 0 + else : + if set(sub_str) == set(self._character) : + if start==0 and end >= self._length : + return self._length // len(sub_str) + else : + #This could be done more cleverly... + return str(self).count(sub_str, start, end) + else : + return 0 + + def complement(self) : + """The complement of an unknown nucleotide equals itself. + + >>> my_nuc = UnknownSeq(8) + >>> my_nuc + UnknownSeq(8, alphabet = Alphabet(), character = '?') + >>> print my_nuc + ???????? + >>> my_nuc.complement() + UnknownSeq(8, alphabet = Alphabet(), character = '?') + >>> print my_nuc.complement() + ???????? + """ + if isinstance(Alphabet._get_base_alphabet(self.alphabet), + Alphabet.ProteinAlphabet) : + raise ValueError("Proteins do not have complements!") + return self + + def reverse_complement(self) : + """The reverse complement of an unknown nucleotide equals itself. + + >>> my_nuc = UnknownSeq(10) + >>> my_nuc + UnknownSeq(10, alphabet = Alphabet(), character = '?') + >>> print my_nuc + ?????????? + >>> my_nuc.reverse_complement() + UnknownSeq(10, alphabet = Alphabet(), character = '?') + >>> print my_nuc.reverse_complement() + ?????????? + """ + if isinstance(Alphabet._get_base_alphabet(self.alphabet), + Alphabet.ProteinAlphabet) : + raise ValueError("Proteins do not have complements!") + return self + + def transcribe(self) : + """Returns unknown RNA sequence from an unknown DNA sequence. + + >>> my_dna = UnknownSeq(10, character="N") + >>> my_dna + UnknownSeq(10, alphabet = Alphabet(), character = 'N') + >>> print my_dna + NNNNNNNNNN + >>> my_rna = my_dna.transcribe() + >>> my_rna + UnknownSeq(10, alphabet = RNAAlphabet(), character = 'N') + >>> print my_rna + NNNNNNNNNN + """ + #Offload the alphabet stuff + s = Seq(self._character, self.alphabet).transcribe() + return UnknownSeq(self._length, s.alphabet, self._character) + + def back_transcribe(self) : + """Returns unknown DNA sequence from an unknown RNA sequence. + + >>> my_rna = UnknownSeq(20, character="N") + >>> my_rna + UnknownSeq(20, alphabet = Alphabet(), character = 'N') + >>> print my_rna + NNNNNNNNNNNNNNNNNNNN + >>> my_dna = my_rna.back_transcribe() + >>> my_dna + UnknownSeq(20, alphabet = DNAAlphabet(), character = 'N') + >>> print my_dna + NNNNNNNNNNNNNNNNNNNN + """ + #Offload the alphabet stuff + s = Seq(self._character, self.alphabet).back_transcribe() + return UnknownSeq(self._length, s.alphabet, self._character) + + def translate(self, **kwargs) : + """Translate an unknown nucleotide sequence into an unknown protein. + + e.g. + + >>> my_seq = UnknownSeq(11, character="N") + >>> print my_seq + NNNNNNNNNNN + >>> my_protein = my_seq.translate() + >>> my_protein + UnknownSeq(3, alphabet = ProteinAlphabet(), character = 'X') + >>> print my_protein + XXX + + In comparison, using a normal Seq object: + + >>> my_seq = Seq("NNNNNNNNNNN") + >>> print my_seq + NNNNNNNNNNN + >>> my_protein = my_seq.translate() + >>> my_protein + Seq('XXX', ExtendedIUPACProtein()) + >>> print my_protein + XXX + + """ + if isinstance(Alphabet._get_base_alphabet(self.alphabet), + Alphabet.ProteinAlphabet) : + raise ValueError("Proteins cannot be translated!") + return UnknownSeq(self._length//3, Alphabet.generic_protein, "X") + + +class MutableSeq(object): + """An editable sequence object (with an alphabet). + + Unlike normal python strings and our basic sequence object (the Seq class) + which are immuatable, the MutableSeq lets you edit the sequence in place. + However, this means you cannot use a MutableSeq object as a dictionary key. + + >>> from Bio.Seq import MutableSeq + >>> from Bio.Alphabet import generic_dna + >>> my_seq = MutableSeq("ACTCGTCGTCG", generic_dna) + >>> my_seq + MutableSeq('ACTCGTCGTCG', DNAAlphabet()) + >>> my_seq[5] + 'T' + >>> my_seq[5] = "A" + >>> my_seq + MutableSeq('ACTCGACGTCG', DNAAlphabet()) + >>> my_seq[5] + 'A' + >>> my_seq[5:8] = "NNN" + >>> my_seq + MutableSeq('ACTCGNNNTCG', DNAAlphabet()) + >>> len(my_seq) + 11 + + Note that the MutableSeq object does not support as many string-like + or biological methods as the Seq object. + """ + def __init__(self, data, alphabet = Alphabet.generic_alphabet): + if type(data) == type(""): + self.data = array.array("c", data) + else: + self.data = data # assumes the input is an array + self.alphabet = alphabet + + def __repr__(self): + """Returns a (truncated) representation of the sequence for debugging.""" + if len(self) > 60 : + #Shows the last three letters as it is often useful to see if there + #is a stop codon at the end of a sequence. + #Note total length is 54+3+3=60 + return "%s('%s...%s', %s)" % (self.__class__.__name__, + str(self[:54]), str(self[-3:]), + repr(self.alphabet)) + else : + return "%s('%s', %s)" % (self.__class__.__name__, + str(self), + repr(self.alphabet)) + + def __str__(self): + """Returns the full sequence as a python string. + + Note that Biopython 1.44 and earlier would give a truncated + version of repr(my_seq) for str(my_seq). If you are writing code + which needs to be backwards compatible with old Biopython, you + should continue to use my_seq.tostring() rather than str(my_seq). + """ + #See test_GAQueens.py for an historic usage of a non-string alphabet! + return "".join(self.data) + + def __cmp__(self, other): + """Compare the sequence for to another sequence or a string. + + If compared to another sequence the alphabets must be compatible. + Comparing DNA to RNA, or Nucleotide to Protein will raise an + exception. + + Otherwise only the sequence itself is compared, not the precise + alphabet. + + This method indirectly supports ==, < , etc.""" + if hasattr(other, "alphabet") : + #other should be a Seq or a MutableSeq + if not Alphabet._check_type_compatible([self.alphabet, + other.alphabet]) : + raise TypeError("Incompatable alphabets %s and %s" \ + % (repr(self.alphabet), repr(other.alphabet))) + #They should be the same sequence type (or one of them is generic) + if isinstance(other, MutableSeq): + #See test_GAQueens.py for an historic usage of a non-string + #alphabet! Comparing the arrays supports this. + return cmp(self.data, other.data) + else : + return cmp(str(self), str(other)) + elif isinstance(other, basestring) : + return cmp(str(self), other) + else : + raise TypeError + + def __len__(self): return len(self.data) + + def __getitem__(self, index) : + #Note since Python 2.0, __getslice__ is deprecated + #and __getitem__ is used instead. + #See http://docs.python.org/ref/sequence-methods.html + if isinstance(index, int) : + #Return a single letter as a string + return self.data[index] + else : + #Return the (sub)sequence as another Seq object + return MutableSeq(self.data[index], self.alphabet) + + def __setitem__(self, index, value): + #Note since Python 2.0, __setslice__ is deprecated + #and __setitem__ is used instead. + #See http://docs.python.org/ref/sequence-methods.html + if isinstance(index, int) : + #Replacing a single letter with a new string + self.data[index] = value + else : + #Replacing a sub-sequence + if isinstance(value, MutableSeq): + self.data[index] = value.data + elif isinstance(value, type(self.data)): + self.data[index] = value + else: + self.data[index] = array.array("c", str(value)) + + def __delitem__(self, index): + #Note since Python 2.0, __delslice__ is deprecated + #and __delitem__ is used instead. + #See http://docs.python.org/ref/sequence-methods.html + + #Could be deleting a single letter, or a slice + del self.data[index] + + def __add__(self, other): + """Add another sequence or string to this sequence. + + Returns a new MutableSeq object.""" + if hasattr(other, "alphabet") : + #other should be a Seq or a MutableSeq + if not Alphabet._check_type_compatible([self.alphabet, + other.alphabet]) : + raise TypeError("Incompatable alphabets %s and %s" \ + % (repr(self.alphabet), repr(other.alphabet))) + #They should be the same sequence type (or one of them is generic) + a = Alphabet._consensus_alphabet([self.alphabet, other.alphabet]) + if isinstance(other, MutableSeq): + #See test_GAQueens.py for an historic usage of a non-string + #alphabet! Adding the arrays should support this. + return self.__class__(self.data + other.data, a) + else : + return self.__class__(str(self) + str(other), a) + elif isinstance(other, basestring) : + #other is a plain string - use the current alphabet + return self.__class__(str(self) + str(other), self.alphabet) + else : + raise TypeError + + def __radd__(self, other): + if hasattr(other, "alphabet") : + #other should be a Seq or a MutableSeq + if not Alphabet._check_type_compatible([self.alphabet, + other.alphabet]) : + raise TypeError("Incompatable alphabets %s and %s" \ + % (repr(self.alphabet), repr(other.alphabet))) + #They should be the same sequence type (or one of them is generic) + a = Alphabet._consensus_alphabet([self.alphabet, other.alphabet]) + if isinstance(other, MutableSeq): + #See test_GAQueens.py for an historic usage of a non-string + #alphabet! Adding the arrays should support this. + return self.__class__(other.data + self.data, a) + else : + return self.__class__(str(other) + str(self), a) + elif isinstance(other, basestring) : + #other is a plain string - use the current alphabet + return self.__class__(str(other) + str(self), self.alphabet) + else : + raise TypeError + + def append(self, c): + self.data.append(c) + + def insert(self, i, c): + self.data.insert(i, c) + + def pop(self, i = (-1)): + c = self.data[i] + del self.data[i] + return c + + def remove(self, item): + for i in range(len(self.data)): + if self.data[i] == item: + del self.data[i] + return + raise ValueError("MutableSeq.remove(x): x not in list") + + def count(self, sub, start=0, end=sys.maxint): + """Non-overlapping count method, like that of a python string. + + This behaves like the python string method of the same name, + which does a non-overlapping count! + + Returns an integer, the number of occurrences of substring + argument sub in the (sub)sequence given by [start:end]. + Optional arguments start and end are interpreted as in slice + notation. + + Arguments: + - sub - a string or another Seq object to look for + - start - optional integer, slice start + - end - optional integer, slice end + + e.g. + + >>> from Bio.Seq import MutableSeq + >>> my_mseq = MutableSeq("AAAATGA") + >>> print my_mseq.count("A") + 5 + >>> print my_mseq.count("ATG") + 1 + >>> print my_mseq.count(Seq("AT")) + 1 + >>> print my_mseq.count("AT", 2, -1) + 1 + + HOWEVER, please note because that python strings, Seq objects and + MutableSeq objects do a non-overlapping search, this may not give + the answer you expect: + + >>> "AAAA".count("AA") + 2 + >>> print MutableSeq("AAAA").count("AA") + 2 + + A non-overlapping search would give the answer as three! + """ + try : + #TODO - Should we check the alphabet? + search = sub.tostring() + except AttributeError : + search = sub + + if not isinstance(search, basestring) : + raise TypeError("expected a string, Seq or MutableSeq") + + if len(search) == 1 : + #Try and be efficient and work directly from the array. + count = 0 + for c in self.data[start:end]: + if c == search: count += 1 + return count + else : + #TODO - Can we do this more efficiently? + return self.tostring().count(search, start, end) + + def index(self, item): + for i in range(len(self.data)): + if self.data[i] == item: + return i + raise ValueError("MutableSeq.index(x): x not in list") + + def reverse(self): + """Modify the mutable sequence to reverse itself. + + No return value. + """ + self.data.reverse() + + def complement(self): + """Modify the mutable sequence to take on its complement. + + Trying to complement a protein sequence raises an exception. + + No return value. + """ + if isinstance(Alphabet._get_base_alphabet(self.alphabet), + Alphabet.ProteinAlphabet) : + raise ValueError("Proteins do not have complements!") + if self.alphabet in (IUPAC.ambiguous_dna, IUPAC.unambiguous_dna): + d = ambiguous_dna_complement + elif self.alphabet in (IUPAC.ambiguous_rna, IUPAC.unambiguous_rna): + d = ambiguous_rna_complement + elif 'U' in self.data and 'T' in self.data : + #TODO - Handle this cleanly? + raise ValueError("Mixed RNA/DNA found") + elif 'U' in self.data: + d = ambiguous_rna_complement + else: + d = ambiguous_dna_complement + c = dict([(x.lower(), y.lower()) for x,y in d.iteritems()]) + d.update(c) + self.data = map(lambda c: d[c], self.data) + self.data = array.array('c', self.data) + + def reverse_complement(self): + """Modify the mutable sequence to take on its reverse complement. + + Trying to reverse complement a protein sequence raises an exception. + + No return value. + """ + self.complement() + self.data.reverse() + + ## Sorting a sequence makes no sense. + # def sort(self, *args): self.data.sort(*args) + + def extend(self, other): + if isinstance(other, MutableSeq): + for c in other.data: + self.data.append(c) + else: + for c in other: + self.data.append(c) + + def tostring(self): + """Returns the full sequence as a python string. + + Although not formally deprecated, you are now encouraged to use + str(my_seq) instead of my_seq.tostring(). + + Because str(my_seq) will give you the full sequence as a python string, + there is often no need to make an explicit conversion. For example, + + print "ID={%s}, sequence={%s}" % (my_name, my_seq) + + On Biopython 1.44 or older you would have to have done this: + + print "ID={%s}, sequence={%s}" % (my_name, my_seq.tostring()) + """ + return "".join(self.data) + + def toseq(self): + """Returns the full sequence as a new immutable Seq object. + + >>> from Bio.Seq import Seq + >>> from Bio.Alphabet import IUPAC + >>> my_mseq = MutableSeq("MKQHKAMIVALIVICITAVVAAL", \ + IUPAC.protein) + >>> my_mseq + MutableSeq('MKQHKAMIVALIVICITAVVAAL', IUPACProtein()) + >>> my_mseq.toseq() + Seq('MKQHKAMIVALIVICITAVVAAL', IUPACProtein()) + + Note that the alphabet is preserved. + """ + return Seq("".join(self.data), self.alphabet) + +# The transcribe, backward_transcribe, and translate functions are +# user-friendly versions of the corresponding functions in Bio.Transcribe +# and Bio.Translate. The functions work both on Seq objects, and on strings. + +def transcribe(dna): + """Transcribes a DNA sequence into RNA. + + If given a string, returns a new string object. + + Given a Seq or MutableSeq, returns a new Seq object with an RNA alphabet. + + Trying to transcribe a protein or RNA sequence raises an exception. + + e.g. + + >>> transcribe("ACTGN") + 'ACUGN' + """ + if isinstance(dna, Seq) : + return dna.transcribe() + elif isinstance(dna, MutableSeq): + return dna.toseq().transcribe() + else: + return dna.replace('T','U').replace('t','u') + +def back_transcribe(rna): + """Back-transcribes an RNA sequence into DNA. + + If given a string, returns a new string object. + + Given a Seq or MutableSeq, returns a new Seq object with an RNA alphabet. + + Trying to transcribe a protein or DNA sequence raises an exception. + + e.g. + + >>> back_transcribe("ACUGN") + 'ACTGN' + """ + if isinstance(rna, Seq) : + return rna.back_transcribe() + elif isinstance(rna, MutableSeq): + return rna.toseq().back_transcribe() + else: + return rna.replace('U','T').replace('u','t') + +def _translate_str(sequence, table, stop_symbol="*", + to_stop=False, pos_stop="X") : + """Helper function to translate a nucleotide string (PRIVATE). + + Arguments: + - sequence - a string + - table - a CodonTable object (NOT a table name or id number) + - stop_symbol - a single character string, what to use for terminators. + - to_stop - boolean, should translation terminate at the first + in frame stop codon? If there is no in-frame stop codon + then translation continues to the end. + - pos_stop - a single character string for a possible stop codon + (e.g. TAN or NNN) + + Returns a string. + + e.g. + + >>> from Bio.Data import CodonTable + >>> table = CodonTable.ambiguous_dna_by_id[1] + >>> _translate_str("AAA", table) + 'K' + >>> _translate_str("TAR", table) + '*' + >>> _translate_str("TAN", table) + 'X' + >>> _translate_str("TAN", table, pos_stop="@") + '@' + >>> _translate_str("TA?", table) + Traceback (most recent call last): + ... + TranslationError: Codon 'TA?' is invalid + """ + sequence = sequence.upper() + amino_acids = [] + forward_table = table.forward_table + stop_codons = table.stop_codons + if table.nucleotide_alphabet.letters is not None : + valid_letters = set(table.nucleotide_alphabet.letters.upper()) + else : + #Assume the worst case, ambiguous DNA or RNA: + valid_letters = set(IUPAC.ambiguous_dna.letters.upper() + \ + IUPAC.ambiguous_rna.letters.upper()) + + n = len(sequence) + for i in xrange(0,n-n%3,3) : + codon = sequence[i:i+3] + try : + amino_acids.append(forward_table[codon]) + except (KeyError, CodonTable.TranslationError) : + #Todo? Treat "---" as a special case (gapped translation) + if codon in table.stop_codons : + if to_stop : break + amino_acids.append(stop_symbol) + elif valid_letters.issuperset(set(codon)) : + #Possible stop codon (e.g. NNN or TAN) + amino_acids.append(pos_stop) + else : + raise CodonTable.TranslationError(\ + "Codon '%s' is invalid" % codon) + return "".join(amino_acids) + +def translate(sequence, table="Standard", stop_symbol="*", to_stop=False): + """Translate a nucleotide sequence into amino acids. + + If given a string, returns a new string object. Given a Seq or + MutableSeq, returns a Seq object with a protein alphabet. + + Arguments: + - table - Which codon table to use? This can be either a name + (string) or an NCBI identifier (integer). Defaults + to the "Standard" table. + - stop_symbol - Single character string, what to use for any + terminators, defaults to the asterisk, "*". + - to_stop - Boolean, defaults to False meaning do a full + translation continuing on past any stop codons + (translated as the specified stop_symbol). If + True, translation is terminated at the first in + frame stop codon (and the stop_symbol is not + appended to the returned protein sequence). + + A simple string example using the default (standard) genetic code: + + >>> coding_dna = "GTGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG" + >>> translate(coding_dna) + 'VAIVMGR*KGAR*' + >>> translate(coding_dna, stop_symbol="@") + 'VAIVMGR@KGAR@' + >>> translate(coding_dna, to_stop=True) + 'VAIVMGR' + + Now using NCBI table 2, where TGA is not a stop codon: + + >>> translate(coding_dna, table=2) + 'VAIVMGRWKGAR*' + >>> translate(coding_dna, table=2, to_stop=True) + 'VAIVMGRWKGAR' + + Note that if the sequence has no in-frame stop codon, then the to_stop + argument has no effect: + + >>> coding_dna2 = "GTGGCCATTGTAATGGGCCGC" + >>> translate(coding_dna2) + 'VAIVMGR' + >>> translate(coding_dna2, to_stop=True) + 'VAIVMGR' + + NOTE - Ambiguous codons like "TAN" or "NNN" could be an amino acid + or a stop codon. These are translated as "X". Any invalid codon + (e.g. "TA?" or "T-A") will throw a TranslationError. + + NOTE - Does NOT support gapped sequences. + + It will however translate either DNA or RNA. + """ + if isinstance(sequence, Seq) : + return sequence.translate(table, stop_symbol, to_stop) + elif isinstance(sequence, MutableSeq): + #Return a Seq object + return sequence.toseq().translate(table, stop_symbol, to_stop) + else: + #Assume its a string, return a string + try : + codon_table = CodonTable.ambiguous_generic_by_id[int(table)] + except ValueError : + codon_table = CodonTable.ambiguous_generic_by_name[table] + return _translate_str(sequence, codon_table, stop_symbol, to_stop) + +def reverse_complement(sequence): + """Returns the reverse complement sequence of a nucleotide string. + + If given a string, returns a new string object. + Given a Seq or a MutableSeq, returns a new Seq object with the same alphabet. + + Supports unambiguous and ambiguous nucleotide sequences. + + e.g. + + >>> reverse_complement("ACTG-NH") + 'DN-CAGT' + """ + if isinstance(sequence, Seq) : + #Return a Seq + return sequence.reverse_complement() + elif isinstance(sequence, MutableSeq) : + #Return a Seq + #Don't use the MutableSeq reverse_complement method as it is 'in place'. + return sequence.toseq().reverse_complement() + + #Assume its a string. + #In order to avoid some code duplication, the old code would turn the string + #into a Seq, use the reverse_complement method, and convert back to a string. + #This worked, but is over five times slower on short sequences! + if ('U' in sequence or 'u' in sequence) \ + and ('T' in sequence or 't' in sequence): + raise ValueError("Mixed RNA/DNA found") + elif 'U' in sequence or 'u' in sequence: + ttable = _rna_complement_table + else: + ttable = _dna_complement_table + return sequence.translate(ttable)[::-1] + +def _test(): + """Run the Bio.Seq module's doctests.""" + print "Runing doctests..." + import doctest + doctest.testmod() + print "Done" + +if __name__ == "__main__": + _test() diff --git a/binaries/src/globplot/biopython-1.50/Bio/Seq.pyc b/binaries/src/globplot/biopython-1.50/Bio/Seq.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b16921e6abdcb144a25202cc8dba58137b0b0e6e GIT binary patch literal 54182 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

  • Ib>gy9E@s*qUwI}Bsm;a56Gt@QT3qQ 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 Hcjo^CFrKze literal 0 HcmV?d00001 diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqFeature.py b/binaries/src/globplot/biopython-1.50/Bio/SeqFeature.py new file mode 100644 index 0000000..aa44e69 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqFeature.py @@ -0,0 +1,490 @@ +"""Represent a Sequence Feature holding info about a part of a sequence. + +This is heavily modeled after the Biocorba SeqFeature objects, and +may be pretty biased towards GenBank stuff since I'm writing it +for the GenBank parser output... + +What's here: + +Base class to hold a Feature. +---------------------------- +classes: +o SeqFeature + +Hold information about a Reference. +---------------------------------- + +This is an attempt to create a General class to hold Reference type +information. + +classes: +o Reference + +Specify locations of a feature on a Sequence. +--------------------------------------------- + +This aims to handle, in Ewan's words, 'the dreaded fuzziness issue' in +much the same way as Biocorba. This has the advantages of allowing us +to handle fuzzy stuff in case anyone needs it, and also be compatible +with Biocorba. + +classes: +o FeatureLocation - Specify the start and end location of a feature. + +o ExactPosition - Specify the position as being exact. +o WithinPosition - Specify a position occuring within some range. +o BetweenPosition - Specify a position occuring between a range. +o BeforePosition - Specify the position as being found before some base. +o AfterPosition - Specify the position as being found after some base. +""" + +class SeqFeature: + """Represent a Sequence Feature on an object. + + Attributes: + o location - the location of the feature on the sequence + o type - the specified type of the feature (ie. CDS, exon, repeat...) + o location_operator - a string specifying how this SeqFeature may + be related to others. For example, in the example GenBank feature + shown below, the location_operator would be "join" + o strand - A value specifying on which strand (of a DNA sequence, for + instance) the feature deals with. 1 indicates the plus strand, -1 + indicates the minus strand, 0 indicates both strands, and None indicates + that strand doesn't apply (ie. for proteins) or is not known. + o id - A string identifier for the feature. + o ref - A reference to another sequence. This could be an accession + number for some different sequence. + o ref_db - A different database for the reference accession number. + o qualifier - A dictionary of qualifiers on the feature. These are + analagous to the qualifiers from a GenBank feature table. The keys of + the dictionary are qualifier names, the values are the qualifier + values. + o sub_features - Additional SeqFeatures which fall under this 'parent' + feature. For instance, if we having something like: + + CDS join(1..10,30..40,50..60) + + The the top level feature would be a CDS from 1 to 60, and the sub + features would be of 'CDS_join' type and would be from 1 to 10, 30 to + 40 and 50 to 60, respectively. + """ + def __init__(self, location = None, type = '', location_operator = '', + strand = None, id = "", + qualifiers = {}, sub_features = [], + ref = None, ref_db = None): + """Initialize a SeqFeature on a Sequence. + """ + self.location = location + + self.type = type + self.location_operator = location_operator + self.strand = strand + self.id = id + # XXX right now sub_features and qualifiers cannot be set + # from the initializer because this causes all kinds + # of recursive import problems. I can't understand why this is + # at all :-< + self.qualifiers = {} + self.sub_features = [] + self.ref = ref + self.ref_db = ref_db + + def __repr__(self): + """A string representation of the record for debugging.""" + answer = "%s(%s" % (self.__class__, repr(self.location)) + if self.type : + answer += ", type=%s" % repr(self.type) + if self.location_operator : + answer += ", location_operator=%s" % repr(self.location_operator) + if self.strand : + answer += ", strand=%s" % repr(self.strand) + if self.id and self.id != "" : + answer += ", id=%s" % repr(self.id) + if self.ref : + answer += ", ref=%s" % repr(self.ref) + if self.ref_db : + answer += ", ref_db=%s" % repr(self.ref_db) + answer += ")" + return answer + + def __str__(self): + """A readable summary of the feature intended to be printed to screen. + """ + out = "type: %s\n" % self.type + out += "location: %s\n" % self.location + out += "ref: %s:%s\n" % (self.ref, self.ref_db) + out += "strand: %s\n" % self.strand + out += "qualifiers: \n" + qualifier_keys = self.qualifiers.keys() + qualifier_keys.sort() + for qual_key in qualifier_keys: + out += "\tKey: %s, Value: %s\n" % (qual_key, + self.qualifiers[qual_key]) + if len(self.sub_features) != 0: + out += "Sub-Features\n" + for sub_feature in self.sub_features: + out +="%s\n" % sub_feature + + return out + + def _shift(self, offset) : + """Returns a copy of the feature with its location shifted (PRIVATE). + + The annotation qaulifiers are copied.""" + answer = SeqFeature(location = self.location._shift(offset), + type = self.type, + location_operator = self.location_operator, + strand = self.strand, + id = self.id, + #qualifiers = dict(self.qualifiers.iteritems()), + #sub_features = [f._shift(offset) for f in self.sub_features], + ref = self.ref, + ref_db = self.ref_db) + #TODO - Sort out the use of sub_feature and qualifiers in __init___ + answer.sub_features = [f._shift(offset) for f in self.sub_features] + answer.qualifiers = dict(self.qualifiers.iteritems()) + return answer + +# --- References + +# TODO -- Will this hold PubMed and Medline information decently? +class Reference: + """Represent a Generic Reference object. + + Attributes: + o location - A list of Location objects specifying regions of + the sequence that the references correspond to. If no locations are + specified, the entire sequence is assumed. + o authors - A big old string, or a list split by author, of authors + for the reference. + o title - The title of the reference. + o journal - Journal the reference was published in. + o medline_id - A medline reference for the article. + o pubmed_id - A pubmed reference for the article. + o comment - A place to stick any comments about the reference. + """ + def __init__(self): + self.location = [] + self.authors = '' + self.consrtm = '' + self.title = '' + self.journal = '' + self.medline_id = '' + self.pubmed_id = '' + self.comment = '' + + def __str__(self): + """Output an informative string for debugging. + """ + out = "" + for single_location in self.location: + out += "location: %s\n" % single_location + out += "authors: %s\n" % self.authors + if self.consrtm: + out += "consrtm: %s\n" % self.consrtm + out += "title: %s\n" % self.title + out += "journal: %s\n" % self.journal + out += "medline id: %s\n" % self.medline_id + out += "pubmed id: %s\n" % self.pubmed_id + out += "comment: %s\n" % self.comment + + return out + +# --- Handling feature locations + +class FeatureLocation: + """Specify the location of a feature along a sequence. + + This attempts to deal with fuzziness of position ends, but also + make it easy to get the start and end in the 'normal' case (no + fuzziness). + + You should access the start and end attributes with + your_location.start and your_location.end. If the start and + end are exact, this will return the positions, if not, we'll return + the approriate Fuzzy class with info about the position and fuzziness. + + Note that the start and end location numbering follow Python's scheme, + thus a GenBank entry of 123..150 (one based counting) becomes a location + of [122:150] (zero based counting). + """ + def __init__(self, start, end): + """Specify the start and end of a sequence feature. + + start and end arguments specify the values where the feature begins + and ends. These can either by any of the *Position objects that + inherit from AbstractPosition, or can just be integers specifying the + position. In the case of integers, the values are assumed to be + exact and are converted in ExactPosition arguments. This is meant + to make it easy to deal with non-fuzzy ends. + """ + if isinstance(start, AbstractPosition): + self._start = start + else: + self._start = ExactPosition(start) + + if isinstance(end, AbstractPosition): + self._end = end + else: + self._end = ExactPosition(end) + + def __str__(self): + """Returns a representation of the location (with python counting). + + For the simple case this uses the python splicing syntax, [122:150] + (zero based counting) which GenBank would call 123..150 (one based + counting). + """ + return "[%s:%s]" % (self._start, self._end) + + def __repr__(self): + """A string representation of the location for debugging.""" + return "%s(%s,%s)" \ + % (self.__class__, repr(self.start), repr(self.end)) + + def _shift(self, offset) : + """Returns a copy of the location shifted by the offset (PRIVATE).""" + return FeatureLocation(start = self._start._shift(offset), + end = self._end._shift(offset)) + + def __getattr__(self, attr): + """Make it easy to get non-fuzzy starts and ends. + + We override get_attribute here so that in non-fuzzy cases we + can just return the start and end position without any hassle. + + To get fuzzy start and ends, just ask for item.start and + item.end. To get non-fuzzy attributes (ie. the position only) + ask for 'item.nofuzzy_start', 'item.nofuzzy_end'. These should return + the largest range of the fuzzy position. So something like: + (10.20)..(30.40) should return 10 for start, and 40 for end. + + The special tricky case where is when we have a single between position + argument like 2^3 for the range. We want nofuzzy_start and nofuzzy_end + to give a reasonable approximation of what this really means, which + is an empty string -- so the same position for both. Doing a special + case here sucks, but there is really not a general rule you can apply + to this. + """ + #TODO - these are not currently implemented as properties, this means + #they do not show up via dir(...) + if attr == 'start': + return self._start + elif attr == 'end': + return self._end + elif attr == 'nofuzzy_start': + if ((self._start == self._end) and isinstance(self._start, + BetweenPosition)): + return self._start.position + else: + return min(self._start.position, + self._start.position + self._start.extension) + elif attr == 'nofuzzy_end': + if ((self._start == self._end) and isinstance(self._start, + BetweenPosition)): + return self._end.position + else: + return max(self._end.position, + self._end.position + self._end.extension) + else: + raise AttributeError("Cannot evaluate attribute %s." % attr) + +class AbstractPosition: + """Abstract base class representing a position. + """ + def __init__(self, position, extension): + self.position = position + self.extension = extension + + def __repr__(self) : + """String representation of the location for debugging.""" + return "%s(%s,%s)" \ + % (self.__class__, repr(self.position), repr(self.extension)) + + def __cmp__(self, other): + """A simple comparison function for positions. + + This is very simple-minded and just compares the position attribute + of the features; extensions are not considered at all. This could + potentially be expanded to try to take advantage of extensions. + """ + assert isinstance(other, AbstractPosition), \ + "We can only do comparisons between Biopython Position objects." + + return cmp(self.position, other.position) + + def _shift(self, offset) : + #We want this to maintain the subclass when called from a subclass + return self.__class__(self.position + offset, self.extension) + +class ExactPosition(AbstractPosition): + """Specify the specific position of a boundary. + + o position - The position of the boundary. + o extension - An optional argument which must be zero since we don't + have an extension. The argument is provided so that the same number of + arguments can be passed to all position types. + + In this case, there is no fuzziness associated with the position. + """ + def __init__(self, position, extension = 0): + if extension != 0: + raise AttributeError("Non-zero extension %s for exact position." + % extension) + AbstractPosition.__init__(self, position, 0) + + def __repr__(self) : + """String representation of the ExactPosition location for debugging.""" + assert self.extension == 0 + return "%s(%s)" % (self.__class__, repr(self.position)) + + def __str__(self): + return str(self.position) + +class WithinPosition(AbstractPosition): + """Specify the position of a boundary within some coordinates. + + Arguments: + o position - The start position of the boundary + o extension - The range to which the boundary can extend. + + This allows dealing with a position like ((1.4)..100). This + indicates that the start of the sequence is somewhere between 1 + and 4. To represent that with this class we would set position as + 1 and extension as 3. + """ + def __init__(self, position, extension = 0): + AbstractPosition.__init__(self, position, extension) + + def __str__(self): + return "(%s.%s)" % (self.position, self.position + self.extension) + +class BetweenPosition(AbstractPosition): + """Specify the position of a boundary between two coordinates. + + Arguments: + o position - The start position of the boundary. + o extension - The range to the other position of a boundary. + + This specifies a coordinate which is found between the two positions. + So this allows us to deal with a position like ((1^2)..100). To + represent that with this class we set position as 1 and the + extension as 1. + """ + def __init__(self, position, extension = 0): + AbstractPosition.__init__(self, position, extension) + + def __str__(self): + return "(%s^%s)" % (self.position, self.position + self.extension) + +class BeforePosition(AbstractPosition): + """Specify a position where the actual location occurs before it. + + Arguments: + o position - The upper boundary of where the location can occur. + o extension - An optional argument which must be zero since we don't + have an extension. The argument is provided so that the same number of + arguments can be passed to all position types. + + This is used to specify positions like (<10..100) where the location + occurs somewhere before position 10. + """ + def __init__(self, position, extension = 0): + if extension != 0: + raise AttributeError("Non-zero extension %s for exact position." + % extension) + AbstractPosition.__init__(self, position, 0) + + def __repr__(self) : + """A string representation of the location for debugging.""" + assert self.extension == 0 + return "%s(%s)" % (self.__class__, repr(self.position)) + + def __str__(self): + return "<%s" % self.position + +class AfterPosition(AbstractPosition): + """Specify a position where the actual location is found after it. + + Arguments: + o position - The lower boundary of where the location can occur. + o extension - An optional argument which must be zero since we don't + have an extension. The argument is provided so that the same number of + arguments can be passed to all position types. + + This is used to specify positions like (>10..100) where the location + occurs somewhere after position 10. + """ + def __init__(self, position, extension = 0): + if extension != 0: + raise AttributeError("Non-zero extension %s for exact position." + % extension) + AbstractPosition.__init__(self, position, 0) + + def __repr__(self) : + """A string representation of the location for debugging.""" + assert self.extension == 0 + return "%s(%s)" % (self.__class__, repr(self.position)) + + def __str__(self): + return ">%s" % self.position + +class OneOfPosition(AbstractPosition): + """Specify a position where the location can be multiple positions. + + This models the GenBank 'one-of(1888,1901)' function, and tries + to make this fit within the Biopython Position models. In our case + the position of the "one-of" is set as the lowest choice, and the + extension is the range to the highest choice. + """ + def __init__(self, position_list): + """Initialize with a set of posssible positions. + + position_list is a list of AbstractPosition derived objects, + specifying possible locations. + """ + # unique attribute for this type of positions + self.position_choices = position_list + # find the smallest and largest position in the choices + smallest = None + largest = None + for position_choice in self.position_choices: + assert isinstance(position_choice, AbstractPosition), \ + "Expected position objects, got %r" % position_choice + if smallest is None and largest is None: + smallest = position_choice.position + largest = position_choice.position + elif position_choice.position > largest: + largest = position_choice.position + elif position_choice.position < smallest: + smallest = position_choice.position + # initialize with our definition of position and extension + AbstractPosition.__init__(self, smallest, largest - smallest) + + def __repr__(self) : + """String representation of the OneOfPosition location for debugging.""" + return "%s(%s)" % (self.__class__, repr(self.position_choices)) + + def __str__(self): + out = "one-of(" + for position in self.position_choices: + out += "%s," % position + # replace the last comma with the closing parenthesis + out = out[:-1] + ")" + return out + +class PositionGap: + """Simple class to hold information about a gap between positions. + """ + def __init__(self, gap_size): + """Intialize with a position object containing the gap information. + """ + self.gap_size = gap_size + + def __repr__(self) : + """A string representation of the position gap for debugging.""" + return "%s(%s)" % (self.__class__, repr(self.gap_size)) + + def __str__(self): + out = "gap(%s)" % self.gap_size + return out diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/AceIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/AceIO.py new file mode 100644 index 0000000..1670fb3 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/AceIO.py @@ -0,0 +1,63 @@ +# Copyright 2008 by Peter Cock. All rights reserved. +# +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Bio.SeqIO support for the "ace" file format. + +You are expected to use this module via the Bio.SeqIO functions. +See also the Bio.Sequencing.Ace module which offers more than just accessing +the contig consensus sequences in an ACE file as SeqRecord objects.""" + +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord +from Bio.Alphabet import generic_nucleotide, generic_dna, generic_rna, Gapped +from Bio.Sequencing import Ace + +#This is a generator function! +def AceIterator(handle) : + """Returns SeqRecord objects from an ACE file. + + This uses the Bio.Sequencing.Ace module to do the hard work. Note that + by iterating over the file in a single pass, we are forced to ignore any + WA, CT, RT or WR footer tags.""" + + for ace_contig in Ace.parse(handle) : + #Convert the ACE contig record into a SeqRecord... + consensus_seq_str = ace_contig.sequence + #Assume its DNA unless there is a U in it, + if "U" in consensus_seq_str : + if "T" in consensus_seq_str : + #Very odd! Error? + alpha = generic_ncleotide + else : + alpha = generic_rna + else : + alpha = generic_dna + + if "*" in consensus_seq_str : + #For consistency with most other file formats, map + #any * gaps into 0 gaps. + assert "-" not in consensus_seq_str + consensus_seq = Seq(consensus_seq_str.replace("*","-"), + Gapped(alpha, gap_char="-")) + else : + consensus_seq = Seq(consensus_seq_str, alpha) + + #TODO - Consensus base quality (BQ lines). Note that any gaps + #(* character) in the consensus does not get a quality entry. + #This really needs Biopython support for per-letter-annotation. + + #TODO? - Base segments (BS lines) which indicates which read + #phrap has chosen to be the consensus at a particular position. + #Perhaps as SeqFeature objects? + + #TODO - Supporting reads (RD lines, plus perhaps QA and DS lines) + #Perhaps as SeqFeature objects? + + seq_record = SeqRecord(consensus_seq, + id = ace_contig.name, + name = ace_contig.name) + yield seq_record + #All done diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/FastaIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/FastaIO.py new file mode 100644 index 0000000..38437ba --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/FastaIO.py @@ -0,0 +1,208 @@ +# Copyright 2006-2009 by Peter Cock. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +# +# This module is for reading and writing FASTA format files as SeqRecord +# objects. The code is partly inspired by earlier Biopython modules, +# Bio.Fasta.* and the now deprecated Bio.SeqIO.FASTA + +"""Bio.SeqIO support for the "fasta" (aka FastA or Pearson) file format. + +You are expected to use this module via the Bio.SeqIO functions.""" + +from Bio.Alphabet import single_letter_alphabet +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord +from Interfaces import SequentialSequenceWriter + +#This is a generator function! +def FastaIterator(handle, alphabet = single_letter_alphabet, title2ids = None) : + """Generator function to iterate over Fasta records (as SeqRecord objects). + + handle - input file + alphabet - optional alphabet + title2ids - A function that, when given the title of the FASTA + file (without the beginning >), will return the id, name and + description (in that order) for the record as a tuple of strings. + + If this is not given, then the entire title line will be used + as the description, and the first word as the id and name. + + Note that use of title2ids matches that of Bio.Fasta.SequenceParser + but the defaults are slightly different. + """ + #Skip any text before the first record (e.g. blank lines, comments) + while True : + line = handle.readline() + if line == "" : return #Premature end of file, or just empty? + if line[0] == ">" : + break + + while True : + if line[0]!=">" : + raise ValueError("Records in Fasta files should start with '>' character") + if title2ids : + id, name, descr = title2ids(line[1:].rstrip()) + else : + descr = line[1:].rstrip() + id = descr.split()[0] + name = id + + lines = [] + line = handle.readline() + while True: + if not line : break + if line[0] == ">": break + #Remove trailing whitespace, and any internal spaces + #(and any embedded \r which are possible in mangled files + #when not opened in universal read lines mode) + lines.append(line.rstrip().replace(" ","").replace("\r","")) + line = handle.readline() + + #Return the record and then continue... + yield SeqRecord(Seq("".join(lines), alphabet), + id = id, name = name, description = descr) + + if not line : return #StopIteration + assert False, "Should not reach this line" + +class FastaWriter(SequentialSequenceWriter): + """Class to write Fasta format files.""" + def __init__(self, handle, wrap=60, record2title=None): + """Create a Fasta writer. + + handle - Handle to an output file, e.g. as returned + by open(filename, "w") + wrap - Optional line length used to wrap sequence lines. + Defaults to wrapping the sequence at 60 characters + Use zero (or None) for no wrapping, giving a single + long line for the sequence. + record2title - Optional function to return the text to be + used for the title line of each record. By default the + a combination of the record.id and record.description + is used. If the record.description starts with the + record.id, then just the record.description is used. + + You can either use: + + myWriter = FastaWriter(open(filename,"w")) + writer.write_file(myRecords) + + Or, follow the sequential file writer system, for example: + + myWriter = FastaWriter(open(filename,"w")) + writer.write_header() # does nothing for Fasta files + ... + Multiple calls to writer.write_record() and/or writer.write_records() + ... + writer.write_footer() # does nothing for Fasta files + writer.close() + """ + SequentialSequenceWriter.__init__(self, handle) + #self.handle = handle + self.wrap = None + if wrap : + if wrap < 1 : + raise ValueError + self.wrap = wrap + self.record2title = record2title + + def write_record(self, record): + """Write a single Fasta record to the file.""" + assert self._header_written + assert not self._footer_written + self._record_written = True + + if self.record2title : + title=self.clean(self.record2title(record)) + else : + id = self.clean(record.id) + description = self.clean(record.description) + + #if description[:len(id)]==id : + if description and description.split(None,1)[0]==id : + #The description includes the id at the start + title = description + else : + title = "%s %s" % (id, description) + + assert "\n" not in title + assert "\r" not in title + self.handle.write(">%s\n" % title) + + data = self._get_seq_string(record) #Catches sequence being None + + assert "\n" not in data + assert "\r" not in data + + if self.wrap : + for i in range(0, len(data), self.wrap): + self.handle.write(data[i:i+self.wrap] + "\n") + else : + self.handle.write(data + "\n") + +if __name__ == "__main__" : + print "Running quick self test" + + import os + from Bio.Alphabet import generic_protein, generic_nucleotide + + #Download the files from here: + #ftp://ftp.ncbi.nlm.nih.gov/genomes/Bacteria/Nanoarchaeum_equitans + fna_filename = "NC_005213.fna" + faa_filename = "NC_005213.faa" + + def genbank_name_function(text) : + text, descr = text.split(None,1) + id = text.split("|")[3] + name = id.split(".",1)[0] + return id, name, descr + + def print_record(record) : + #See also bug 2057 + #http://bugzilla.open-bio.org/show_bug.cgi?id=2057 + print "ID:" + record.id + print "Name:" + record.name + print "Descr:" + record.description + print record.seq + for feature in record.annotations : + print '/%s=%s' % (feature, record.annotations[feature]) + if record.dbxrefs : + print "Database cross references:" + for x in record.dbxrefs : print " - %s" % x + + if os.path.isfile(fna_filename) : + print "--------" + print "FastaIterator (single sequence)" + iterator = FastaIterator(open(fna_filename, "r"), alphabet=generic_nucleotide, title2ids=genbank_name_function) + count=0 + for record in iterator : + count=count+1 + print_record(record) + assert count == 1 + print str(record.__class__) + + if os.path.isfile(faa_filename) : + print "--------" + print "FastaIterator (multiple sequences)" + iterator = FastaIterator(open(faa_filename, "r"), alphabet=generic_protein, title2ids=genbank_name_function) + count=0 + for record in iterator : + count=count+1 + print_record(record) + break + assert count>0 + print str(record.__class__) + + from cStringIO import StringIO + print "--------" + print "FastaIterator (empty input file)" + #Just to make sure no errors happen + iterator = FastaIterator(StringIO("")) + count = 0 + for record in iterator : + count = count+1 + assert count==0 + + print "Done" diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/IgIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/IgIO.py new file mode 100644 index 0000000..57b7aa1 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/IgIO.py @@ -0,0 +1,97 @@ +# Copyright 2008 by Peter Cock. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +# +# This module is for reading and writing IntelliGenetics format files as +# SeqRecord objects. This file format appears to be the same as the MASE +# multiple sequence alignment format. + +"""Bio.SeqIO support for the "ig" (IntelliGenetics or MASE) file format. + +You are expected to use this module via the Bio.SeqIO functions.""" + +from Bio.Alphabet import single_letter_alphabet +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord + +#This is a generator function! +def IgIterator(handle, alphabet = single_letter_alphabet) : + """Iterate over IntelliGenetics records (as SeqRecord objects). + + handle - input file + alphabet - optional alphabet + + The optional free format file header lines (which start with two + semi-colons) are ignored. + + The free format commentary lines at the start of each record (which + start with a semi-colon) are recorded as a single string with embedded + new line characters in the SeqRecord's annotations dictionary under the + key 'comment'. + """ + #Skip any file header text before the first record (;; lines) + while True : + line = handle.readline() + if not line : break #Premature end of file, or just empty? + if not line.startswith(";;") : break + + while line : + #Now iterate over the records + if line[0]!=";" : + raise ValueError( \ + "Records should start with ';' and not:\n%s" % repr(line)) + + #Try and agree with SeqRecord convention from the GenBank parser, + #(and followed in the SwissProt parser) which stores the comments + #as a long string with newlines under annotations key 'comment'. + + #Note some examples use "; ..." and others ";..." + comment_lines = [] + while line.startswith(";") : + #TODO - Extract identifier from lines like "LOCUS\tB_SF2"? + comment_lines.append(line[1:].strip()) + line = handle.readline() + title = line.rstrip() + + seq_lines = [] + while True: + line = handle.readline() + if not line : break + if line[0] == ";": break + #Remove trailing whitespace, and any internal spaces + seq_lines.append(line.rstrip().replace(" ","")) + seq_str = "".join(seq_lines) + if seq_str.endswith("1") : + #Remove the optional terminator (digit one) + seq_str = seq_str[:-1] + if "1" in seq_str : + raise ValueError("Potential terminator digit one found within sequence.") + + #Return the record and then continue... + record= SeqRecord(Seq(seq_str, alphabet), + id = title, name = title) + record.annotations['comment'] = "\n".join(comment_lines) + yield record + + #We should be at the end of the file now + assert not line + +if __name__ == "__main__" : + print "Running quick self test" + + import os + path = "../../Tests/IntelliGenetics/" + if os.path.isdir(path) : + for filename in os.listdir(path) : + if os.path.splitext(filename)[-1] == ".txt" : + print + print filename + print "-"*len(filename) + handle = open(os.path.join(path, filename)) + for record in IgIterator(handle) : + print record.id, len(record) + handle.close() + print "Done" + else : + print "Could not find input files" diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/InsdcIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/InsdcIO.py new file mode 100644 index 0000000..f2215ed --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/InsdcIO.py @@ -0,0 +1,377 @@ +# Copyright 2007-2009 by Peter Cock. All rights reserved. +# +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package.. + +"""Bio.SeqIO support for the "genbank" and "embl" file formats. + +You are expected to use this module via the Bio.SeqIO functions. +Note that internally this module calls Bio.GenBank to do the actual +parsing of both GenBank and EMBL files. + +See also: + +International Nucleotide Sequence Database Collaboration +http://www.insdc.org/ + +GenBank +http://www.ncbi.nlm.nih.gov/Genbank/ + +EMBL Nucleotide Sequence Database +http://www.ebi.ac.uk/embl/ + +DDBJ (DNA Data Bank of Japan) +http://www.ddbj.nig.ac.jp/ +""" + +from Bio.Seq import UnknownSeq +from Bio.GenBank.Scanner import GenBankScanner, EmblScanner +from Bio import Alphabet +from Interfaces import SequentialSequenceWriter + +# NOTE +# ==== +# The "brains" for parsing GenBank and EMBL files (and any +# other flat file variants from the INSDC in future) is in +# Bio.GenBank.Scanner (plus the _FeatureConsumer in Bio.GenBank) + +def GenBankIterator(handle) : + """Breaks up a Genbank file into SeqRecord objects. + + Every section from the LOCUS line to the terminating // becomes + a single SeqRecord with associated annotation and features. + + Note that for genomes or chromosomes, there is typically only + one record.""" + #This calls a generator function: + return GenBankScanner(debug=0).parse_records(handle) + +def EmblIterator(handle) : + """Breaks up an EMBL file into SeqRecord objects. + + Every section from the LOCUS line to the terminating // becomes + a single SeqRecord with associated annotation and features. + + Note that for genomes or chromosomes, there is typically only + one record.""" + #This calls a generator function: + return EmblScanner(debug=0).parse_records(handle) + +def GenBankCdsFeatureIterator(handle, alphabet=Alphabet.generic_protein) : + """Breaks up a Genbank file into SeqRecord objects for each CDS feature. + + Every section from the LOCUS line to the terminating // can contain + many CDS features. These are returned as with the stated amino acid + translation sequence (if given). + """ + #This calls a generator function: + return GenBankScanner(debug=0).parse_cds_features(handle, alphabet) + +def EmblCdsFeatureIterator(handle, alphabet=Alphabet.generic_protein) : + """Breaks up a EMBL file into SeqRecord objects for each CDS feature. + + Every section from the LOCUS line to the terminating // can contain + many CDS features. These are returned as with the stated amino acid + translation sequence (if given). + """ + #This calls a generator function: + return EmblScanner(debug=0).parse_cds_features(handle, alphabet) + +class GenBankWriter(SequentialSequenceWriter) : + HEADER_WIDTH = 12 + MAX_WIDTH = 80 + + def _write_single_line(self, tag, text) : + "Used in the the 'header' of each GenBank record.""" + assert len(tag) < self.HEADER_WIDTH + assert len(text) < self.MAX_WIDTH - self.HEADER_WIDTH, \ + "Annotation %s too long for %s line" % (repr(text), tag) + self.handle.write("%s%s\n" % (tag.ljust(self.HEADER_WIDTH), + text.replace("\n"," "))) + + def _write_multi_line(self, tag, text) : + "Used in the the 'header' of each GenBank record.""" + #TODO - Do the line spliting while preserving white space? + max_len = self.MAX_WIDTH - self.HEADER_WIDTH + assert len(tag) < self.HEADER_WIDTH + text = text.strip() + if len(text) < max_len : + self._write_single_line(tag, text) + return + + words = text.split() + assert max([len(w) for w in words]) < max_len, \ + "Your description cannot be broken into nice lines!" + text = "" + while words and len(text) + 1 + len(words[0]) < max_len : + text += " " + words.pop(0) + text = text.strip() + assert len(text) < max_len + self._write_single_line(tag, text) + while words : + text = "" + while words and len(text) + 1 + len(words[0]) < max_len : + text += " " + words.pop(0) + text = text.strip() + assert len(text) < max_len + self._write_single_line("", text) + assert not words + + def _write_the_first_line(self, record) : + """Write the LOCUS line.""" + + locus = record.name + if not locus or locus == "" : + locus = record.id + if not locus or locus == "" : + locus = self._get_annotation_str(record, "accession", just_first=True) + if len(locus) > 16 : + raise ValueError("Locus identifier %s is too long" % repr(locus)) + + if len(record) > 99999999999 : + #Currently GenBank only officially support up to 350000, but + #the length field can take eleven digits + raise ValueError("Sequence too long!") + + #Get the base alphabet (underneath any Gapped or StopCodon encoding) + a = Alphabet._get_base_alphabet(record.seq.alphabet) + if not isinstance(a, Alphabet.Alphabet) : + raise TypeError("Invalid alphabet") + elif isinstance(a, Alphabet.ProteinAlphabet) : + units = "bp" + elif isinstance(a, Alphabet.NucleotideAlphabet) : + units = "aa" + else : + #Must be something like NucleotideAlphabet or + #just the generic Alphabet (default for fasta files) + raise ValueError("Need a Nucleotide or Protein alphabet") + + #Get the molecule type + #TODO - record this explicitly in the parser? + if isinstance(a, Alphabet.ProteinAlphabet) : + mol_type = "" + elif isinstance(a, Alphabet.DNAAlphabet) : + mol_type = "DNA" + elif isinstance(a, Alphabet.RNAAlphabet) : + mol_type = "RNA" + else : + #Must be something like NucleotideAlphabet or + #just the generic Alphabet (default for fasta files) + raise ValueError("Need a DNA, RNA or Protein alphabet") + + try : + division = record.annotations["data_file_division"] + except KeyError : + division = "UNK" + if division not in ["PRI","ROD","MAM","VRT","INV","PLN","BCT", + "VRL","PHG","SYN","UNA","EST","PAT","STS", + "GSS","HTG","HTC","ENV"] : + division = "UNK" + + assert len(units) == 2 + assert len(division) == 3 + #TODO - date + #TODO - mol_type + line = "LOCUS %s %s %s %s %s 01-JAN-1980\n" \ + % (locus.ljust(16), + str(len(record)).rjust(11), + units, + mol_type.ljust(6), + division) + assert len(line) == 79+1, repr(line) #plus one for new line + + assert line[12:28].rstrip() == locus, \ + 'LOCUS line does not contain the locus at the expected position:\n' + line + assert line[28:29] == " " + assert line[29:40].lstrip() == str(len(record)), \ + 'LOCUS line does not contain the length at the expected position:\n' + line + + #Tests copied from Bio.GenBank.Scanner + assert line[40:44] in [' bp ', ' aa '] , \ + 'LOCUS line does not contain size units at expected position:\n' + line + assert line[44:47] in [' ', 'ss-', 'ds-', 'ms-'], \ + 'LOCUS line does not have valid strand type (Single stranded, ...):\n' + line + assert line[47:54].strip() == "" \ + or line[47:54].strip().find('DNA') != -1 \ + or line[47:54].strip().find('RNA') != -1, \ + 'LOCUS line does not contain valid sequence type (DNA, RNA, ...):\n' + line + assert line[54:55] == ' ', \ + 'LOCUS line does not contain space at position 55:\n' + line + assert line[55:63].strip() in ['','linear','circular'], \ + 'LOCUS line does not contain valid entry (linear, circular, ...):\n' + line + assert line[63:64] == ' ', \ + 'LOCUS line does not contain space at position 64:\n' + line + assert line[67:68] == ' ', \ + 'LOCUS line does not contain space at position 68:\n' + line + assert line[70:71] == '-', \ + 'LOCUS line does not contain - at position 71 in date:\n' + line + assert line[74:75] == '-', \ + 'LOCUS line does not contain - at position 75 in date:\n' + line + + self.handle.write(line) + + def _get_annotation_str(self, record, key, default=".", just_first=False) : + """Get an annotation dictionary entry (as a string). + + Some entries are lists, in which case if just_first=True the first entry + is returned. If just_first=False (default) this verifies there is only + one entry before returning it.""" + try : + answer = record.annotations[key] + except KeyError : + return default + if isinstance(answer, list) : + if not just_first : assert len(answer) == 1 + return str(answer[0]) + else : + return str(answer) + + def _write_sequence(self, record): + #Loosely based on code from Howard Salis + #TODO - Force lower case? + LETTERS_PER_LINE = 60 + SEQUENCE_INDENT = 9 + + if isinstance(record.seq, UnknownSeq) : + #We have already recorded the length, and there is no need + #to record a long sequence of NNNNNNN...NNN or whatever. + return + + data = self._get_seq_string(record) #Catches sequence being None + seq_len = len(data) + for line_number in range(0,seq_len,LETTERS_PER_LINE): + self.handle.write(str(line_number+1).rjust(SEQUENCE_INDENT)) + for words in range(line_number,min(line_number+LETTERS_PER_LINE,seq_len),10): + self.handle.write(" %s" % data[words:words+10]) + self.handle.write("\n") + + def write_record(self, record): + """Write a single record to the output file.""" + handle = self.handle + self._write_the_first_line(record) + + accession = self._get_annotation_str(record, "accession", + record.id.split(".",1)[0], + just_first=True) + acc_with_version = accession + if record.id.startswith(accession+".") : + try : + acc_with_version = "%s.%i" \ + % (accession, int(record.id.split(".",1)[1])) + except ValueError : + pass + gi = self._get_annotation_str(record, "gi", just_first=True) + + descr = record.description + if descr == "" : descr = "." + self._write_multi_line("DEFINITION", descr) + + self._write_single_line("ACCESSION", accession) + if gi != "." : + self._write_single_line("VERSION", "%s GI:%s" % (acc_with_version,gi)) + else : + self._write_single_line("VERSION", "%s" % (acc_with_version)) + + try : + #List of strings + keywords = "; ".join(record.annotations["keywords"]) + except KeyError : + keywords = "." + self._write_multi_line("KEYWORDS", keywords) + + self._write_multi_line("SOURCE", \ + self._get_annotation_str(record, "source")) + #The ORGANISM line MUST be a single line, as any continuation is the taxonomy + org = self._get_annotation_str(record, "organism") + if len(org) > self.MAX_WIDTH - self.HEADER_WIDTH : + org = org[:self.MAX_WIDTH - self.HEADER_WIDTH-4]+"..." + self._write_single_line(" ORGANISM", org) + try : + #List of strings + taxonomy = "; ".join(record.annotations["taxonomy"]) + except KeyError : + taxonomy = "." + self._write_multi_line("", taxonomy) + + #TODO - References... + handle.write("FEATURES Location/Qualifiers\n") + for feature in record.features : + self._write_feature(feature) + handle.write("ORIGIN\n") + self._write_sequence(record) + handle.write("//\n") + + def _write_feature(self, feature): + """Write a single SeqFeature object to features table. + + Not implemented yet, but this stub exists in the short term to + facilitate working on writing GenBank files with a sub-class.""" + #TODO - Features... + pass + +if __name__ == "__main__" : + print "Quick self test" + import os + from StringIO import StringIO + + def check_genbank_writer(records) : + handle = StringIO() + GenBankWriter(handle).write_file(records) + handle.seek(0) + + records2 = list(GenBankIterator(handle)) + + assert len(records) == len(records2) + for r1, r2 in zip(records, records2) : + #The SwissProt parser may leave \n in the description... + assert r1.description.replace("\n", " ") == r2.description + assert r1.id == r2.id + assert r1.name == r2.name + assert str(r1.seq) == str(r2.seq) + for key in ["gi", "keywords", "source", "taxonomy"] : + if key in r1.annotations : + assert r1.annotations[key] == r2.annotations[key], key + for key in ["organism"] : + if key in r1.annotations : + v1 = r1.annotations[key] + v2 = r2.annotations[key] + assert isinstance(v1, str) and isinstance(v2, str) + #SwissProt organism can be too long to record in GenBank format + assert v1 == v2 or \ + (v2.endswith("...") and v1.startswith(v2[:-3])), key + + for filename in os.listdir("../../Tests/GenBank") : + if not filename.endswith(".gbk") and not filename.endswith(".gb") : + continue + print filename + + handle = open("../../Tests/GenBank/%s" % filename) + records = list(GenBankIterator(handle)) + handle.close() + + check_genbank_writer(records) + + for filename in os.listdir("../../Tests/EMBL") : + if not filename.endswith(".embl") : + continue + print filename + + handle = open("../../Tests/EMBL/%s" % filename) + records = list(EmblIterator(handle)) + handle.close() + + check_genbank_writer(records) + + from Bio import SeqIO + for filename in os.listdir("../../Tests/SwissProt") : + if not filename.startswith("sp") : + continue + print filename + + handle = open("../../Tests/SwissProt/%s" % filename) + records = list(SeqIO.parse(handle,"swiss")) + handle.close() + + check_genbank_writer(records) + diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/Interfaces.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/Interfaces.py new file mode 100644 index 0000000..3d28298 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/Interfaces.py @@ -0,0 +1,275 @@ +# Copyright 2006-2008 by Peter Cock. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +""" +Bio.SeqIO support module (not for general use). + +Unless you are writing a new parser or writer for Bio.SeqIO, you should not +use this module. It provides base classes to try and simplify things. +""" + +from Bio.Alphabet import generic_alphabet + +class SequenceIterator : + """Base class for building SeqRecord iterators. + + You should write a next() method to return SeqRecord + objects. You may wish to redefine the __init__ + method as well. + """ + def __init__(self, handle, alphabet=generic_alphabet) : + """Create a SequenceIterator object. + + handle - input file + alphabet - optional, e.g. Bio.Alphabet.generic_protein + + Note when subclassing: + - there should be a single non-optional argument, + the handle. + - you do not have to require an alphabet. + - you can add additional optional arguments.""" + self.handle = handle + self.alphabet = alphabet + ##################################################### + # You may want to subclass this, for example # + # to read through the file to find the first record,# + # or if additional arguments are required. # + ##################################################### + + def next(self) : + """Return the next record in the file. + + This method should be replaced by any derived class to do something useful.""" + raise NotImplementedError("This object should be subclassed") + ##################################################### + # You SHOULD subclass this, to split the file up # + # into your individual records, and convert these # + # into useful objects, e.g. return SeqRecord object # + ##################################################### + + def __iter__(self): + """Iterate over the entries as a SeqRecord objects. + + Example usage for Fasta files: + + myFile = open("example.fasta","r") + myFastaReader = FastaIterator(myFile) + for record in myFastaReader : + print record.id + print record.seq + myFile.close()""" + return iter(self.next, None) + +class InterlacedSequenceIterator(SequenceIterator) : + """Base class for any iterator of a non-sequential file type. + + This object is not intended for use directly. + + When writing a parser for any interlaced sequence file where the whole + file must be read in order to extract any single record, then you should + subclass this object. + + All you need to do is to define your own: + (1) __init__ method to parse the file and call self.move_start() + (2) __len__ method to return the number of records + (3) __getitem__ to return any requested record. + + This class will then provide the iterator methods including next(), but relies + on knowing the total number of records and tracking the pending record index in + as self._n + + It is up to the subclassed object to decide if it wants to generate a cache of + SeqRecords when initialised, or simply use its own lists and dicts and create + SeqRecords on request. + """ + + def __init__(self) : + """Create the object. + + This method should be replaced by any derived class to do something useful.""" + #We assume that your implementation of __init__ will ensure self._n=0 + self.move_start() + raise NotImplementedError("This object method should be subclassed") + ##################################################### + # You SHOULD subclass this # + ##################################################### + + def __len__(self) : + """Return the number of records. + + This method should be replaced by any derived class to do something useful.""" + raise NotImplementedError("This object method should be subclassed") + ##################################################### + # You SHOULD subclass this # + ##################################################### + + def __getitem__(self, i) : + """Return the requested record. + + This method should be replaced by any derived class to do something + useful. + + It should NOT touch the value of self._n""" + raise NotImplementedError("This object method should be subclassed") + ##################################################### + # You SHOULD subclass this # + ##################################################### + + def move_start(self) : + self._n = 0 + + def next(self) : + next_record = self._n + if next_record < len(self) : + self._n = next_record+1 + return self[next_record] + else : + #StopIteration + return None + + def __iter__(self): + return iter(self.next, None) + +class SequenceWriter: + """This class should be subclassed. + + Interlaced file formats (e.g. Clustal) should subclass directly. + + Sequential file formats (e.g. Fasta, GenBank) should subclass + the SequentialSequenceWriter class instead. + """ + def __init__(self, handle): + """Creates the writer object. + + Use the method write_file() to actually record your sequence records.""" + self.handle = handle + + def _get_seq_string(self, record): + """Use this to catch errors like the sequence being None.""" + try : + #The tostring() method is part of the Seq API, we could instead + #use str(record.seq) but that would give a string "None" if the + #sequence was None, and unpredicatable output if an unexpected + #object was present. + return record.seq.tostring() + except AttributeError : + if record.seq is None : + #We could silently treat this as an empty sequence, Seq(""), + #but that would be an implict assumption we should avoid. + raise TypeError("SeqRecord (id=%s) has None for its sequence." \ + % record.id) + else : + raise TypeError("SeqRecord (id=%s) has an invalid sequence." \ + % record.id) + + def clean(self, text) : + """Use this to avoid getting newlines in the output.""" + answer = text + for x in ["\n", "\r"] : + answer = answer.replace(x, " ") + return answer.replace(" ", " ") + + def write_file(self, records) : + """Use this to write an entire file containing the given records. + + records - A list or iterator returning SeqRecord objects + + Should return the number of records (as an integer). + + This method can only be called once.""" + #Note when implementing this, you should close the file at the end. + raise NotImplementedError("This object should be subclassed") + ##################################################### + # You SHOULD subclass this # + ##################################################### + +class SequentialSequenceWriter(SequenceWriter): + """This class should be subclassed. + + It is intended for sequential file formats with an (optional) + header, repeated records, and an (optional) footer. + + In this case (as with interlaced file formats), the user may + simply call the write_file() method and be done. + + However, they may also call the write_header(), followed + by multiple calls to write_record() and/or write_records() + followed finally by write_footer(). + + Users must call write_header() and write_footer() even when + the file format concerned doesn't have a header or footer. + This is to try and make life as easy as possible when + switching the output format. + + Note that write_header() cannot require any assumptions about + the number of records. + """ + def __init__(self, handle): + self.handle = handle + self._header_written = False + self._record_written = False + self._footer_written = False + + def write_header(self) : + assert not self._header_written, "You have aleady called write_header()" + assert not self._record_written, "You have aleady called write_record() or write_records()" + assert not self._footer_written, "You have aleady called write_footer()" + self._header_written = True + + def write_footer(self) : + assert self._header_written, "You must call write_header() first" + assert self._record_written, "You have not called write_record() or write_records() yet" + assert not self._footer_written, "You have aleady called write_footer()" + self._footer_written = True + + def write_record(self, record): + """Write a single record to the output file. + + record - a SeqRecord object + + Once you have called write_header() you can call write_record() + and/or write_records() as many times as needed. Then call + write_footer() and close().""" + assert self._header_written, "You must call write_header() first" + assert not self._footer_written, "You have already called write_footer()" + self._record_written = True + raise NotImplementedError("This object should be subclassed") + ##################################################### + # You SHOULD subclass this # + ##################################################### + + def write_records(self, records): + """Write multiple record to the output file. + + records - A list or iterator returning SeqRecord objects + + Once you have called write_header() you can call write_record() + and/or write_records() as many times as needed. Then call + write_footer() and close(). + + Returns the number of records written. + """ + #Default implementation: + assert self._header_written, "You must call write_header() first" + assert not self._footer_written, "You have already called write_footer()" + count = 0 + for record in records : + self.write_record(record) + count += 1 + #Mark as true, even if there where no records + self._record_written = True + return count + + def write_file(self, records) : + """Use this to write an entire file containing the given records. + + records - A list or iterator returning SeqRecord objects + + This method can only be called once. Returns the number of records + written. + """ + self.write_header() + count = self.write_records(records) + self.write_footer() + return count diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/PhdIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/PhdIO.py new file mode 100644 index 0000000..29bfe14 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/PhdIO.py @@ -0,0 +1,43 @@ +# Copyright 2008 by Peter Cock. All rights reserved. +# +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Bio.SeqIO support for the "phd" file format. + +PHD files are output by PHRED and used by PHRAP and CONSED. + +You are expected to use this module via the Bio.SeqIO functions. +See also the underlying Bio.Sequencing.Phd module.""" + +from Bio.SeqRecord import SeqRecord +from Bio.Sequencing import Phd + +#This is a generator function! +def PhdIterator(handle) : + """Returns SeqRecord objects from a PHD file. + + This uses the Bio.Sequencing.Phy module to do the hard work. + """ + + phd_records = Phd.parse(handle) + for phd_record in phd_records: + #Convert the PHY record into a SeqRecord... + seq_record = SeqRecord(phd_record.seq, + id = phd_record.file_name, + name = phd_record.file_name) + #Just re-use the comments dictionary as the SeqRecord's annotations + seq_record.annotations = phd_record.comments + yield seq_record + #All done + +if __name__ == "__main__" : + print "Quick self test" + handle = open("../../Tests/Phd/Phd1") + for record in PhdIterator(handle) : + print record + handle.close() + print "Done" + + diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/PirIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/PirIO.py new file mode 100644 index 0000000..953076c --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/PirIO.py @@ -0,0 +1,182 @@ +# Copyright 2008 by Peter Cock. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +# +# This module is for reading and writing PIR or NBRF format files as +# SeqRecord objects. The code is based on Bio.SeqIO.FastaIO + +"""Bio.SeqIO support for the "pir" (aka PIR or NBRF) file format. + +You are expected to use this module via the Bio.SeqIO functions, or if +the file contains a sequence alignment, optionally via Bio.AlignIO instead. + +This format was introduced for the Protein Information Resource (PIR), a +project of the National Biomedical Research Foundation (NBRF). The PIR +database itself is now part of UniProt. + +The file format is described online at: +http://www.ebi.ac.uk/help/pir_frame.html +http://www.cmbi.kun.nl/bioinf/tools/crab_pir.html (currently down) + +An example file in this format would be: + +>P1;CRAB_ANAPL +ALPHA CRYSTALLIN B CHAIN (ALPHA(B)-CRYSTALLIN). + MDITIHNPLI RRPLFSWLAP SRIFDQIFGE HLQESELLPA SPSLSPFLMR + SPIFRMPSWL ETGLSEMRLE KDKFSVNLDV KHFSPEELKV KVLGDMVEIH + GKHEERQDEH GFIAREFNRK YRIPADVDPL TITSSLSLDG VLTVSAPRKQ + SDVPERSIPI TREEKPAIAG AQRK* + +>P1;CRAB_BOVIN +ALPHA CRYSTALLIN B CHAIN (ALPHA(B)-CRYSTALLIN). + MDIAIHHPWI RRPFFPFHSP SRLFDQFFGE HLLESDLFPA STSLSPFYLR + PPSFLRAPSW IDTGLSEMRL EKDRFSVNLD VKHFSPEELK VKVLGDVIEV + HGKHEERQDE HGFISREFHR KYRIPADVDP LAITSSLSSD GVLTVNGPRK + QASGPERTIP ITREEKPAVT AAPKK* + +Or, an example of a multiple sequence alignment: + +>P1;S27231 +rhodopsin - northern leopard frog +MNGTEGPNFY IPMSNKTGVV RSPFDYPQYY LAEPWKYSVL AAYMFLLILL GLPINFMTLY +VTIQHKKLRT PLNYILLNLG VCNHFMVLCG FTITMYTSLH GYFVFGQTGC YFEGFFATLG +GEIALWSLVV LAIERYIVVC KPMSNFRFGE NHAMMGVAFT WIMALACAVP PLFGWSRYIP +EGMQCSCGVD YYTLKPEVNN ESFVIYMFVV HFLIPLIIIS FCYGRLVCTV KEAAAQQQES +ATTQKAEKEV TRMVIIMVIF FLICWVPYAY VAFYIFTHQG SEFGPIFMTV PAFFAKSSAI +YNPVIYIMLN KQFRNCMITT LCCGKNPFGD DDASSAATSK TEATSVSTSQ VSPA* + +>P1;I51200 +rhodopsin - African clawed frog +MNGTEGPNFY VPMSNKTGVV RSPFDYPQYY LAEPWQYSAL AAYMFLLILL GLPINFMTLF +VTIQHKKLRT PLNYILLNLV FANHFMVLCG FTVTMYTSMH GYFIFGPTGC YIEGFFATLG +GEVALWSLVV LAVERYIVVC KPMANFRFGE NHAIMGVAFT WIMALSCAAP PLFGWSRYIP +EGMQCSCGVD YYTLKPEVNN ESFVIYMFIV HFTIPLIVIF FCYGRLLCTV KEAAAQQQES +LTTQKAEKEV TRMVVIMVVF FLICWVPYAY VAFYIFTHQG SNFGPVFMTV PAFFAKSSAI +YNPVIYIVLN KQFRNCLITT LCCGKNPFGD EDGSSAATSK TEASSVSSSQ VSPA* + +>P1;JN0120 +rhodopsin - Japanese lamprey +MNGTEGDNFY VPFSNKTGLA RSPYEYPQYY LAEPWKYSAL AAYMFFLILV GFPVNFLTLF +VTVQHKKLRT PLNYILLNLA MANLFMVLFG FTVTMYTSMN GYFVFGPTMC SIEGFFATLG +GEVALWSLVV LAIERYIVIC KPMGNFRFGN THAIMGVAFT WIMALACAAP PLVGWSRYIP +EGMQCSCGPD YYTLNPNFNN ESYVVYMFVV HFLVPFVIIF FCYGRLLCTV KEAAAAQQES +ASTQKAEKEV TRMVVLMVIG FLVCWVPYAS VAFYIFTHQG SDFGATFMTL PAFFAKSSAL +YNPVIYILMN KQFRNCMITT LCCGKNPLGD DE-SGASTSKT EVSSVSTSPV SPA* + + +As with the FASTA format, each record starts with a line begining with ">" +character. There is then a two letter sequence type (P1, F1, DL, DC, RL, +RC, or XX), a semi colon, and the identification code. The second like is +free text description. The remaining lines contain the sequence itself, +terminating in an asterisk. Space separated blocks of ten letters as shown +above are typical. + +Sequence codes and their meanings: + +P1 - Protein (complete) +F1 - Protein (fragment) +D1 - DNA (e.g. EMBOSS seqret output) +DL - DNA (linear) +DC - DNA (circular) +RL - RNA (linear) +RC - RNA (circular) +N3 - tRNA +N1 - Other functional RNA +XX - Unknown +""" + +from Bio.Alphabet import single_letter_alphabet, generic_protein, generic_dna, generic_rna +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord + +_pir_alphabets = {"P1" : generic_protein, + "F1" : generic_protein, + "D1" : generic_dna, + "DL" : generic_dna, + "DC" : generic_dna, + "RL" : generic_rna, + "RC" : generic_rna, + "N3" : generic_rna, + "XX" : single_letter_alphabet, + } + +#This is a generator function! +def PirIterator(handle) : + """Generator function to iterate over Fasta records (as SeqRecord objects). + + handle - input file + alphabet - optional alphabet + title2ids - A function that, when given the title of the FASTA + file (without the beginning >), will return the id, name and + description (in that order) for the record as a tuple of strings. + + If this is not given, then the entire title line will be used + as the description, and the first word as the id and name. + + Note that use of title2ids matches that of Bio.Fasta.SequenceParser + but the defaults are slightly different. + """ + #Skip any text before the first record (e.g. blank lines, comments) + while True : + line = handle.readline() + if line == "" : return #Premature end of file, or just empty? + if line[0] == ">" : + break + + while True : + if line[0]!=">" : + raise ValueError("Records in PIR files should start with '>' character") + pir_type = line[1:3] + if pir_type not in _pir_alphabets or line[3] != ";" : + raise ValueError("Records should start with '>XX;' where XX is a valid sequence type") + identifier = line[4:].strip() + description = handle.readline().strip() + + + lines = [] + line = handle.readline() + while True: + if not line : break + if line[0] == ">": break + #Remove trailing whitespace, and any internal spaces + lines.append(line.rstrip().replace(" ","")) + line = handle.readline() + seq = "".join(lines) + if seq[-1] != "*" : + #Note the * terminator is present on nucleotide sequences too, + #it is not a stop codon! + raise ValueError("Sequences in PIR files should include a * terminator!") + + #Return the record and then continue... + record = SeqRecord(Seq(seq[:-1], _pir_alphabets[pir_type]), + id = identifier, name = identifier, + description = description) + record.annotations["PIR-type"] = pir_type + yield record + + if not line : return #StopIteration + assert False, "Should not reach this line" + +if __name__ == "__main__" : + print "Running quick self test" + + from StringIO import StringIO + import os + + for name in ["clustalw", "DMA_nuc", "DMB_prot", "B_nuc", "Cw_prot"] : + print name + filename = "../../Tests/NBRF/%s.pir" % name + if not os.path.isfile(filename) : + print "Missing %s" % filename + continue + + records = list(PirIterator(open(filename))) + count = 0 + for record in records : + count += 1 + parts = record.description.split() + if "bases," in parts : + assert len(record) == int(parts[parts.index("bases,")-1]) + print "Could read %s (%i records)" % (name, count) + diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/QualityIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/QualityIO.py new file mode 100644 index 0000000..e80d5f2 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/QualityIO.py @@ -0,0 +1,1113 @@ +# Copyright 2009 by Peter Cock. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +# +# This module is for reading and writing FASTQ and QUAL format files as +# SeqRecord objects, and is expected to be used via the Bio.SeqIO API. + +"""Bio.SeqIO support for the "fastq" and "qual" file formats. + +Note that you are expected to use this code via the Bio.SeqIO interface, as +shown below. + +The FASTQ file format is used frequently at the Wellcome Trust Sanger Institute +to bundle a FASTA sequence and its PHRED quality data (integers between 0 and +90). Rather than using a single FASTQ file, often paired FASTA and QUAL files +are used containing the sequence and the quality information separately. + +The PHRED software reads DNA sequencing trace files, calls bases, and +assigns a quality value between 0 and 90 to each called base using a logged +transformation of the error probability, Q = -10 log10( Pe ), for example:: + + Pe = 0.0, Q = 0 + Pe = 0.1, Q = 10 + Pe = 0.01, Q = 20 + ... + Pe = 0.00000001, Q = 80 + Pe = 0.000000001, Q = 90 + +In the QUAL format these quality values are held as space separated text in +a FASTA like file format. In the FASTQ format, each quality values is encoded +with a single ASCI character using chr(Q+33), meaning zero maps to the +character "!" and for example 80 maps to "q". The sequences and quality are +then stored in pairs in a FASTA like format. + +Unfortunately there is no official document describing the FASTQ file format, +and worse, several related but different variants exist. Reasonable +documentation exists at: http://maq.sourceforge.net/fastq.shtml + +Solexa/Illumina quality scores use Q = - 10 log10 ( Pe / (1-Pe) ), which can +be negative or easily exceed 90. PHRED scores and Solexa scores are NOT +interchangeable (but a reasonable mapping can be achieved between them). +Confusingly Solexa produces a FASTQ like file but using their own score +mapping instead. + +Also note that Roche 454 sequencers can output files in the QUAL format, and +thankfully they use PHREP style scores like Sanger. To extract QUAL files from +a Roche 454 SFF binary file, use the Roche off instrument command line tool +"sffinfo" with the -q or -qual argument. You can extract a matching FASTA file +using the -s or -seq argument instead. + +You are expected to use this module via the Bio.SeqIO functions, with the +following format names: + - "fastq" means Sanger style FASTQ files using PHRED scores. + - "fastq-solexa" means Solexa/Illumina style FASTQ files. + - "qual" means simple quality files using PHRED scores. + +For example, consider the following short FASTQ file (extracted from a real +NCBI dataset):: + + @EAS54_6_R1_2_1_413_324 + CCCTTCTTGTCTTCAGCGTTTCTCC + + + ;;3;;;;;;;;;;;;7;;;;;;;88 + @EAS54_6_R1_2_1_540_792 + TTGGCAGGCCAAGGCCGATGGATCA + + + ;;;;;;;;;;;7;;;;;-;;;3;83 + @EAS54_6_R1_2_1_443_348 + GTTGCTTCTGGCGTGGGTGGGGGGG + + + ;;;;;;;;;;;9;7;;.7;393333 + +This contains three reads of length 25. From the read length these were +probably originally from an early Solexa/Illumina sequencer but NCBI have +followed the Sanger FASTQ convention and this actually uses PHRED style +qualities. This means we can parse this file using Bio.SeqIO using "fastq" +as the format name: + + >>> from Bio import SeqIO + >>> for record in SeqIO.parse(open("Quality/example.fastq"), "fastq") : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 CCCTTCTTGTCTTCAGCGTTTCTCC + EAS54_6_R1_2_1_540_792 TTGGCAGGCCAAGGCCGATGGATCA + EAS54_6_R1_2_1_443_348 GTTGCTTCTGGCGTGGGTGGGGGGG + +The qualities are held as a list of integers in each record's annotation: + + >>> print record + ID: EAS54_6_R1_2_1_443_348 + Name: EAS54_6_R1_2_1_443_348 + Description: EAS54_6_R1_2_1_443_348 + Number of features: 0 + Per letter annotation for: phred_quality + Seq('GTTGCTTCTGGCGTGGGTGGGGGGG', SingleLetterAlphabet()) + >>> print record.letter_annotations["phred_quality"] + [26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 24, 26, 22, 26, 26, 13, 22, 26, 18, 24, 18, 18, 18, 18] + +You can use the SeqRecord format method you can show this in the QUAL format: + + >>> print record.format("qual") + >EAS54_6_R1_2_1_443_348 + 26 26 26 26 26 26 26 26 26 26 26 24 26 22 26 26 13 22 26 18 + 24 18 18 18 18 + + +Or go back to the FASTQ format, + + >>> print record.format("fastq") + @EAS54_6_R1_2_1_443_348 + GTTGCTTCTGGCGTGGGTGGGGGGG + + + ;;;;;;;;;;;9;7;;.7;393333 + + +You can also get Biopython to convert the scores and show a Solexa style +FASTQ file: + + >>> print record.format("fastq-solexa") + @EAS54_6_R1_2_1_443_348 + GTTGCTTCTGGCGTGGGTGGGGGGG + + + ZZZZZZZZZZZXZVZZMVZRXRRRR + + +If you wanted to trim your sequences (perhaps to remove low quality regions, +or to remove a primer sequence), try slicing the SeqRecord objects. e.g. + + >>> sub_rec = record[5:15] + >>> print sub_rec + ID: EAS54_6_R1_2_1_443_348 + Name: EAS54_6_R1_2_1_443_348 + Description: EAS54_6_R1_2_1_443_348 + Number of features: 0 + Per letter annotation for: phred_quality + Seq('TTCTGGCGTG', SingleLetterAlphabet()) + >>> print sub_rec.letter_annotations["phred_quality"] + [26, 26, 26, 26, 26, 26, 24, 26, 22, 26] + >>> print sub_rec.format("fastq") + @EAS54_6_R1_2_1_443_348 + TTCTGGCGTG + + + ;;;;;;9;7; + + +If you wanted to, you could read in this FASTQ file, and save it as a QUAL file: + + >>> from Bio import SeqIO + >>> record_iterator = SeqIO.parse(open("Quality/example.fastq"), "fastq") + >>> out_handle = open("Quality/temp.qual", "w") + >>> SeqIO.write(record_iterator, out_handle, "qual") + 3 + >>> out_handle.close() + +You can of course read in a QUAL file, such as the one we just created: + + >>> from Bio import SeqIO + >>> for record in SeqIO.parse(open("Quality/temp.qual"), "qual") : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 ????????????????????????? + EAS54_6_R1_2_1_540_792 ????????????????????????? + EAS54_6_R1_2_1_443_348 ????????????????????????? + +Notice that QUAL files don't have a proper sequence present! But the quality +information is there: + + >>> print record + ID: EAS54_6_R1_2_1_443_348 + Name: EAS54_6_R1_2_1_443_348 + Description: EAS54_6_R1_2_1_443_348 + Number of features: 0 + Per letter annotation for: phred_quality + UnknownSeq(25, alphabet = SingleLetterAlphabet(), character = '?') + >>> print record.letter_annotations["phred_quality"] + [26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 24, 26, 22, 26, 26, 13, 22, 26, 18, 24, 18, 18, 18, 18] + +Just to keep things tidy, if you are following this example yourself, you can +delete this temporary file now: + + >>> import os + >>> os.remove("Quality/temp.qual") + +Sometimes you won't have a FASTQ file, but rather just a pair of FASTA and QUAL +files. Because the Bio.SeqIO system is designed for reading single files, you +would have to read the two in separately and then combine the data. However, +since this is such a common thing to want to do, there is a helper iterator +defined in this module that does this for you - PairedFastaQualIterator. + +Alternatively, if you have enough RAM to hold all the records in memory at once, +then a simple dictionary approach would work: + + >>> from Bio import SeqIO + >>> reads = SeqIO.to_dict(SeqIO.parse(open("Quality/example.fasta"), "fasta")) + >>> for rec in SeqIO.parse(open("Quality/example.qual"), "qual") : + ... reads[rec.id].letter_annotations["phred_quality"]=rec.letter_annotations["phred_quality"] + +You can then access any record by its key, and get both the sequence and the +quality scores. + + >>> print reads["EAS54_6_R1_2_1_540_792"].format("fastq") + @EAS54_6_R1_2_1_540_792 + TTGGCAGGCCAAGGCCGATGGATCA + + + ;;;;;;;;;;;7;;;;;-;;;3;83 + + +It is important that you explicitly tell Bio.SeqIO which FASTQ variant you are +using ("fastq" for the Sanger standard using PHRED values, or "fastq-solexa" +for the Solexa/Illumina variant), as this cannot be detected reliably +automatically. +""" +__docformat__ = "epytext en" #Don't just use plain text in epydoc API pages! + +#See also http://blog.malde.org/index.php/2008/09/09/the-fastq-file-format-for-sequences/ + +from Bio.Alphabet import single_letter_alphabet +from Bio.Seq import Seq, UnknownSeq +from Bio.SeqRecord import SeqRecord +from Interfaces import SequentialSequenceWriter +from math import log + +# define score offsets. See discussion for differences between Sanger and +# Solexa offsets. +SANGER_SCORE_OFFSET = 33 +SOLEXA_SCORE_OFFSET = 64 + +def solexa_quality_from_phred(phred_quality) : + """Covert a PHRED quality (range 0 to about 90) to a Solexa quality. + + This will return a floating point number, it is up to you to round this to + the nearest integer if appropriate. e.g. + + >>> print "%0.2f" % round(solexa_quality_from_phred(80),2) + 80.00 + >>> print "%0.2f" % round(solexa_quality_from_phred(50),2) + 50.00 + >>> print "%0.2f" % round(solexa_quality_from_phred(20),2) + 19.96 + >>> print "%0.2f" % round(solexa_quality_from_phred(10),2) + 9.54 + >>> print "%0.2f" % round(solexa_quality_from_phred(1),2) + -5.87 + """ + return 10*log(10**(phred_quality/10.0) - 1, 10) + +def phred_quality_from_solexa(solexa_quality) : + """Convert a Solexa quality (which can be negative) to a PHRED quality. + + This will return a floating point number, it is up to you to round this to + the nearest integer if appropriate. e.g. + + >>> print "%0.2f" % round(phred_quality_from_solexa(80),2) + 80.00 + >>> print "%0.2f" % round(phred_quality_from_solexa(20),2) + 20.04 + >>> print "%0.2f" % round(phred_quality_from_solexa(10),2) + 10.41 + >>> print "%0.2f" % round(phred_quality_from_solexa(0),2) + 3.01 + >>> print "%0.2f" % round(phred_quality_from_solexa(-10),2) + 0.41 + """ + return 10*log(10**(solexa_quality/10.0) + 1, 10) + +def _get_phred_quality(record) : + """Extract PHRED qualities from a SeqRecord's letter_annotations (PRIVATE). + + If there are no PHRED qualities, but there are Solexa qualities, those are + used instead after conversion. + """ + try : + return record.letter_annotations["phred_quality"] + except KeyError : + pass + try : + return [phred_quality_from_solexa(q) for \ + q in record.letter_annotations["solexa_quality"]] + except KeyError : + raise ValueError("No suitable quality scores found in letter_annotations " + "of SeqRecord (id=%s)." % record.id) + +def _get_solexa_quality(record) : + """Extract Solexa qualities from a SeqRecord's letter_annotations (PRIVATE). + + If there are no Solexa qualities, but there are PHRED qualities, those are + used instead after conversion. + """ + try : + return record.letter_annotations["solexa_quality"] + except KeyError : + pass + try : + return [solexa_quality_from_phred(q) for \ + q in record.letter_annotations["phred_quality"]] + except KeyError : + raise ValueError("No suitable quality scores found in letter_annotation " + "of SeqRecord (id=%s)." % record.id) + + +#TODO - Default to nucleotide or even DNA? +def FastqGeneralIterator(handle) : + """Iterate over Fastq records as string tuples (not as SeqRecord objects). + + This code does not try to interpret the quality string numerically. It + just returns tuples of the title, sequence and quality as strings. For + the sequence and quality, any whitespace (such as new lines) is removed. + + Our SeqRecord based FASTQ iterators call this function internally, and then + turn the strings into a SeqRecord objects, mapping the quality string into + a list of numerical scores. If you want to do a custom quality mapping, + then you might consider calling this function directly. + + For parsing FASTQ files, the title string from the "@" line at the start + of each record can optionally be omitted on the "+" lines. If it is + repeated, it must be identical. + + The sequence string and the quality string can optionally be split over + multiple lines, although several sources discourage this. In comparison, + for the FASTA file format line breaks between 60 and 80 characters are + the norm. + + WARNING - Because the "@" character can appear in the quality string, + this can cause problems as this is also the marker for the start of + a new sequence. In fact, the "+" sign can also appear as well. Some + sources recommended having no line breaks in the quality to avoid this, + but even that is not enough, consider this example:: + + @071113_EAS56_0053:1:1:998:236 + TTTCTTGCCCCCATAGACTGAGACCTTCCCTAAATA + +071113_EAS56_0053:1:1:998:236 + IIIIIIIIIIIIIIIIIIIIIIIIIIIIICII+III + @071113_EAS56_0053:1:1:182:712 + ACCCAGCTAATTTTTGTATTTTTGTTAGAGACAGTG + + + @IIIIIIIIIIIIIIICDIIIII<%<6&-*).(*%+ + @071113_EAS56_0053:1:1:153:10 + TGTTCTGAAGGAAGGTGTGCGTGCGTGTGTGTGTGT + + + IIIIIIIIIIIICIIGIIIII>IAIIIE65I=II:6 + @071113_EAS56_0053:1:3:990:501 + TGGGAGGTTTTATGTGGA + AAGCAGCAATGTACAAGA + + + IIIIIII.IIIIII1@44 + @-7.%<&+/$/%4(++(% + + This is four PHRED encoded FASTQ entries originally from an NCBI source + (given the read length of 36, these are probably Solexa Illumna reads where + the quality has been mapped onto the PHRED values). + + This example has been edited to illustrate some of the nasty things allowed + in the FASTQ format. Firstly, on the "+" lines most but not all of the + (redundant) identifiers are ommited. In real files it is likely that all or + none of these extra identifiers will be present. + + Secondly, while the first three sequences have been shown without line + breaks, the last has been split over multiple lines. In real files any line + breaks are likely to be consistent. + + Thirdly, some of the quality string lines start with an "@" character. For + the second record this is unavoidable. However for the fourth sequence this + only happens because its quality string is split over two lines. A naive + parser could wrongly treat any line starting with an "@" as the beginning of + a new sequence! This code copes with this possible ambiguity by keeping track + of the length of the sequence which gives the expected length of the quality + string. + + Using this tricky example file as input, this short bit of code demonstrates + what this parsing function would return: + + >>> handle = open("Quality/tricky.fastq", "rU") + >>> for (title, sequence, quality) in FastqGeneralIterator(handle) : + ... print title + ... print sequence, quality + 071113_EAS56_0053:1:1:998:236 + TTTCTTGCCCCCATAGACTGAGACCTTCCCTAAATA IIIIIIIIIIIIIIIIIIIIIIIIIIIIICII+III + 071113_EAS56_0053:1:1:182:712 + ACCCAGCTAATTTTTGTATTTTTGTTAGAGACAGTG @IIIIIIIIIIIIIIICDIIIII<%<6&-*).(*%+ + 071113_EAS56_0053:1:1:153:10 + TGTTCTGAAGGAAGGTGTGCGTGCGTGTGTGTGTGT IIIIIIIIIIIICIIGIIIII>IAIIIE65I=II:6 + 071113_EAS56_0053:1:3:990:501 + TGGGAGGTTTTATGTGGAAAGCAGCAATGTACAAGA IIIIIII.IIIIII1@44@-7.%<&+/$/%4(++(% + >>> handle.close() + + Finally we note that some sources state that the quality string should + start with "!" (which using the PHRED mapping means the first letter always + has a quality score of zero). This rather restrictive rule is not widely + observed, so is therefore ignored here. One plus point about this "!" rule + is that (provided there are no line breaks in the quality sequence) it + would prevent the above problem with the "@" character. + """ + #Skip any text before the first record (e.g. blank lines, comments?) + while True : + line = handle.readline() + if line == "" : return #Premature end of file, or just empty? + if line[0] == "@" : + break + + while True : + if line[0]!="@" : + raise ValueError("Records in Fastq files should start with '@' character") + title_line = line[1:].rstrip() + + seq_lines = [] + line = handle.readline() + while True: + if not line : + raise ValueError("End of file without quality information.") + if line[0] == "+": + #The title here is optional, but if present must match! + if line[1:].rstrip() and line[1:].rstrip() != title_line : + raise ValueError("Sequence and quality captions differ.") + break + seq_lines.extend(line.split()) #removes any whitespace + line = handle.readline() + + seq_string = "".join(seq_lines) + del seq_lines + + quality_lines = [] + line = handle.readline() + while True: + if not line : break + if line[0] == "@": + #This COULD be the start of a new sequence. However, it MAY just + #be a line of quality data which starts with a "@" character. We + #should be able to check this by looking at the sequence length + #and the amount of quality data found so far. + if len("".join(quality_lines)) >= len(seq_string) : + #We expect it to be equal if this is the start of a new record. + #If the quality data is longer, we'll raise an error below. + break + #Continue - its just some (more) sequence data. + + quality_lines.extend(line.split()) #removes any whitespace + line = handle.readline() + + quality_string = "".join(quality_lines) + del quality_lines + + if len(seq_string) != len(quality_string) : + raise ValueError("Lengths of sequence and quality values differs " + " for %s (%i and %i)." \ + % (title_line, len(seq_string), len(quality_string))) + + #Return the record and then continue... + yield (title_line, seq_string, quality_string) + if not line : return #StopIteration at end of file + assert False, "Should not reach this line" + +#This is a generator function! +def FastqPhredIterator(handle, alphabet = single_letter_alphabet, title2ids = None) : + """Generator function to iterate over FASTQ records (as SeqRecord objects). + + - handle - input file + - alphabet - optional alphabet + - title2ids - A function that, when given the title line from the FASTQ + file (without the beginning >), will return the id, name and + description (in that order) for the record as a tuple of + strings. If this is not given, then the entire title line + will be used as the description, and the first word as the + id and name. + + Note that use of title2ids matches that of Bio.SeqIO.FastaIO. + + For each sequence in a (Sanger style) FASTQ file there is a matching string + encoding the PHRED qualities (integers between 0 and about 90) using ASCII + values with an offset of 33. + + For example, consider a file containing three short reads:: + + @EAS54_6_R1_2_1_413_324 + CCCTTCTTGTCTTCAGCGTTTCTCC + + + ;;3;;;;;;;;;;;;7;;;;;;;88 + @EAS54_6_R1_2_1_540_792 + TTGGCAGGCCAAGGCCGATGGATCA + + + ;;;;;;;;;;;7;;;;;-;;;3;83 + @EAS54_6_R1_2_1_443_348 + GTTGCTTCTGGCGTGGGTGGGGGGG + + + ;;;;;;;;;;;9;7;;.7;393333 + + For each sequence (e.g. "CCCTTCTTGTCTTCAGCGTTTCTCC") there is a matching + string encoding the PHRED qualities using a ASCI values with an offset of + 33 (e.g. ";;3;;;;;;;;;;;;7;;;;;;;88"). + + Using this module directly you might run: + + >>> handle = open("Quality/example.fastq", "rU") + >>> for record in FastqPhredIterator(handle) : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 CCCTTCTTGTCTTCAGCGTTTCTCC + EAS54_6_R1_2_1_540_792 TTGGCAGGCCAAGGCCGATGGATCA + EAS54_6_R1_2_1_443_348 GTTGCTTCTGGCGTGGGTGGGGGGG + >>> handle.close() + + Typically however, you would call this via Bio.SeqIO instead with "fastq" as + the format: + + >>> from Bio import SeqIO + >>> handle = open("Quality/example.fastq", "rU") + >>> for record in SeqIO.parse(handle, "fastq") : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 CCCTTCTTGTCTTCAGCGTTTCTCC + EAS54_6_R1_2_1_540_792 TTGGCAGGCCAAGGCCGATGGATCA + EAS54_6_R1_2_1_443_348 GTTGCTTCTGGCGTGGGTGGGGGGG + >>> handle.close() + + If you want to look at the qualities, they are record in each record's + per-letter-annotation dictionary as a simple list of integers: + + >>> print record.letter_annotations["phred_quality"] + [26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 24, 26, 22, 26, 26, 13, 22, 26, 18, 24, 18, 18, 18, 18] + """ + for title_line, seq_string, quality_string in FastqGeneralIterator(handle) : + if title2ids : + id, name, descr = title2ids(title_line) + else : + descr = title_line + id = descr.split()[0] + name = id + record = SeqRecord(Seq(seq_string, alphabet), + id=id, name=name, description=descr) + + assert SANGER_SCORE_OFFSET == ord("!") + #According to BioPerl documentation at least, the first character should + #be an "!" (and therefore quality zero). This seems crazy - what if the + #sequence has been trimmed to remove any poor quality sequence? In any + #case real examples from the NCBI don't follow this practice, so we + #won't enforce it here. + #e.g. ftp://ftp.ncbi.nih.gov/pub/TraceDB/ShortRead/SRA000271/fastq/200x36x36-071113_EAS56_0053-s_1_1.fastq.gz + # + #if quality_string[0] != "!" : + # raise ValueError("The quality string should always start with a ! character.") + qualities = [ord(letter)-SANGER_SCORE_OFFSET for letter in quality_string] + if qualities : + if min(qualities) < 0 or max(qualities) > 90 : + raise ValueError("Quality score outside 0 to 90 found - these are perhaps " + "in a Solexa/Illumina format, not the Sanger FASTQ format " + "which uses PHRED scores.") + record.letter_annotations["phred_quality"] = qualities + yield record + +#This is a generator function! +def FastqSolexaIterator(handle, alphabet = single_letter_alphabet, title2ids = None) : + """Parsing the Solexa/Illumina FASTQ like files (which differ in the quality mapping). + + The optional arguments are the same as those for the FastqPhredIterator. + + For each sequence in Solexa/Illumina FASTQ files there is a matching string + encoding the Solexa integer qualities using ASCII values with an offset + of 64. Solexa scores are scaled differently to PHRED scores, and Biopython + will NOT perform any automatic conversion when loading. + + For example, consider a file containing these five records:: + + @SLXA-B3_649_FC8437_R1_1_1_610_79 + GATGTGCAATACCTTTGTAGAGGAA + +SLXA-B3_649_FC8437_R1_1_1_610_79 + YYYYYYYYYYYYYYYYYYWYWYYSU + @SLXA-B3_649_FC8437_R1_1_1_397_389 + GGTTTGAGAAAGAGAAATGAGATAA + +SLXA-B3_649_FC8437_R1_1_1_397_389 + YYYYYYYYYWYYYYWWYYYWYWYWW + @SLXA-B3_649_FC8437_R1_1_1_850_123 + GAGGGTGTTGATCATGATGATGGCG + +SLXA-B3_649_FC8437_R1_1_1_850_123 + YYYYYYYYYYYYYWYYWYYSYYYSY + @SLXA-B3_649_FC8437_R1_1_1_362_549 + GGAAACAAAGTTTTTCTCAACATAG + +SLXA-B3_649_FC8437_R1_1_1_362_549 + YYYYYYYYYYYYYYYYYYWWWWYWY + @SLXA-B3_649_FC8437_R1_1_1_183_714 + GTATTATTTAATGGCATACACTCAA + +SLXA-B3_649_FC8437_R1_1_1_183_714 + YYYYYYYYYYWYYYYWYWWUWWWQQ + + Using this module directly you might run: + + >>> handle = open("Quality/solexa_example.fastq", "rU") + >>> for record in FastqSolexaIterator(handle) : + ... print record.id, record.seq + SLXA-B3_649_FC8437_R1_1_1_610_79 GATGTGCAATACCTTTGTAGAGGAA + SLXA-B3_649_FC8437_R1_1_1_397_389 GGTTTGAGAAAGAGAAATGAGATAA + SLXA-B3_649_FC8437_R1_1_1_850_123 GAGGGTGTTGATCATGATGATGGCG + SLXA-B3_649_FC8437_R1_1_1_362_549 GGAAACAAAGTTTTTCTCAACATAG + SLXA-B3_649_FC8437_R1_1_1_183_714 GTATTATTTAATGGCATACACTCAA + >>> handle.close() + + Typically however, you would call this via Bio.SeqIO instead with "fastq" as + the format: + + >>> from Bio import SeqIO + >>> handle = open("Quality/solexa_example.fastq", "rU") + >>> for record in SeqIO.parse(handle, "fastq-solexa") : + ... print record.id, record.seq + SLXA-B3_649_FC8437_R1_1_1_610_79 GATGTGCAATACCTTTGTAGAGGAA + SLXA-B3_649_FC8437_R1_1_1_397_389 GGTTTGAGAAAGAGAAATGAGATAA + SLXA-B3_649_FC8437_R1_1_1_850_123 GAGGGTGTTGATCATGATGATGGCG + SLXA-B3_649_FC8437_R1_1_1_362_549 GGAAACAAAGTTTTTCTCAACATAG + SLXA-B3_649_FC8437_R1_1_1_183_714 GTATTATTTAATGGCATACACTCAA + >>> handle.close() + + If you want to look at the qualities, they are recorded in each record's + per-letter-annotation dictionary as a simple list of integers: + + >>> print record.letter_annotations["solexa_quality"] + [25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 23, 25, 25, 25, 25, 23, 25, 23, 23, 21, 23, 23, 23, 17, 17] + + These scores aren't very good, but they are high enough that they map + almost exactly onto PHRED scores: + + >>> print "%0.2f" % phred_quality_from_solexa(25) + 25.01 + + Let's look at another example read which is even worse, where there are + more noticeable differences between the Solexa and PHRED scores:: + + @slxa_0013_1_0001_24 + ACAAAAATCACAAGCATTCTTATACACC + +slxa_0013_1_0001_24 + ??????????????????:??>> from Bio import SeqIO + >>> handle = open("Quality/solexa.fastq", "rU") + >>> record = SeqIO.read(handle, "fastq-solexa") + >>> handle.close() + >>> print record.id, record.seq + slxa_0013_1_0001_24 ACAAAAATCACAAGCATTCTTATACACC + >>> print record.letter_annotations["solexa_quality"] + [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -6, -1, -1, -4, -1, -4, -19, -10, -27, -18] + + These quality scores are so low that when converted from the Solexa scheme + into PHRED scores they look quite different: + + >>> print "%0.2f" % phred_quality_from_solexa(-1) + 2.54 + + Note you can use the Bio.SeqIO.write() function or the SeqRecord's format + method to output the record(s): + + >>> print record.format("fastq-solexa") + @slxa_0013_1_0001_24 + ACAAAAATCACAAGCATTCTTATACACC + + + ??????????????????:?? + + Note this output is slightly different from the input file as Biopython + has left out the optional repetition of the sequence identifier on the "+" + line. If you want the to use PHRED scores, use "fastq" or "qual" as the + output format instead, and Biopython will do the conversion for you: + + >>> print record.format("fastq") + @slxa_0013_1_0001_24 + ACAAAAATCACAAGCATTCTTATACACC + + + $$$$$$$$$$$$$$$$$$"$$"$"!!!! + + + >>> print record.format("qual") + >slxa_0013_1_0001_24 + 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 1 3 1 0 0 0 0 + + """ + for title_line, seq_string, quality_string in FastqGeneralIterator(handle) : + if title2ids : + id, name, descr = title_line + else : + descr = title_line + id = descr.split()[0] + name = id + record = SeqRecord(Seq(seq_string, alphabet), + id=id, name=name, description=descr) + qualities = [ord(letter)-SOLEXA_SCORE_OFFSET for letter in quality_string] + #DO NOT convert these into PHRED qualities automatically! + record.letter_annotations["solexa_quality"] = qualities + yield record + +def QualPhredIterator(handle, alphabet = single_letter_alphabet, title2ids = None) : + """For QUAL files which include PHRED quality scores, but no sequence. + + For example, consider this short QUAL file:: + + >EAS54_6_R1_2_1_413_324 + 26 26 18 26 26 26 26 26 26 26 26 26 26 26 26 22 26 26 26 26 + 26 26 26 23 23 + >EAS54_6_R1_2_1_540_792 + 26 26 26 26 26 26 26 26 26 26 26 22 26 26 26 26 26 12 26 26 + 26 18 26 23 18 + >EAS54_6_R1_2_1_443_348 + 26 26 26 26 26 26 26 26 26 26 26 24 26 22 26 26 13 22 26 18 + 24 18 18 18 18 + + Using this module directly you might run: + + >>> handle = open("Quality/example.qual", "rU") + >>> for record in QualPhredIterator(handle) : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 ????????????????????????? + EAS54_6_R1_2_1_540_792 ????????????????????????? + EAS54_6_R1_2_1_443_348 ????????????????????????? + >>> handle.close() + + Typically however, you would call this via Bio.SeqIO instead with "qual" + as the format: + + >>> from Bio import SeqIO + >>> handle = open("Quality/example.qual", "rU") + >>> for record in SeqIO.parse(handle, "qual") : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 ????????????????????????? + EAS54_6_R1_2_1_540_792 ????????????????????????? + EAS54_6_R1_2_1_443_348 ????????????????????????? + >>> handle.close() + + Becase QUAL files don't contain the sequence string itself, the seq + property is set to an UnknownSeq object. As no alphabet was given, this + has defaulted to a generic single letter alphabet and the character "?" + used. + + By specifying a nucleotide alphabet, "N" is used instead: + + >>> from Bio import SeqIO + >>> from Bio.Alphabet import generic_dna + >>> handle = open("Quality/example.qual", "rU") + >>> for record in SeqIO.parse(handle, "qual", alphabet=generic_dna) : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 NNNNNNNNNNNNNNNNNNNNNNNNN + EAS54_6_R1_2_1_540_792 NNNNNNNNNNNNNNNNNNNNNNNNN + EAS54_6_R1_2_1_443_348 NNNNNNNNNNNNNNNNNNNNNNNNN + >>> handle.close() + + However, the quality scores themselves are available as a list of integers + in each record's per-letter-annotation: + + >>> print record.letter_annotations["phred_quality"] + [26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 24, 26, 22, 26, 26, 13, 22, 26, 18, 24, 18, 18, 18, 18] + + You can still slice one of these SeqRecord objects with an UnknownSeq: + + >>> sub_record = record[5:10] + >>> print sub_record.id, sub_record.letter_annotations["phred_quality"] + EAS54_6_R1_2_1_443_348 [26, 26, 26, 26, 26] + """ + #Skip any text before the first record (e.g. blank lines, comments) + while True : + line = handle.readline() + if line == "" : return #Premature end of file, or just empty? + if line[0] == ">" : + break + + while True : + if line[0]!=">" : + raise ValueError("Records in Fasta files should start with '>' character") + if title2ids : + id, name, descr = title2ids(line[1:].rstrip()) + else : + descr = line[1:].rstrip() + id = descr.split()[0] + name = id + + qualities = [] + line = handle.readline() + while True: + if not line : break + if line[0] == ">": break + qualities.extend([int(word) for word in line.split()]) + line = handle.readline() + + if qualities : + if min(qualities) < 0 or max(qualities) > 90 : + raise ValueError(("Quality score range for %s is %i to %i, outside the " \ + +"expected 0 to 90. Perhaps these are Solexa/Illumina " \ + +"scores, and not PHRED scores?") \ + % (id, min(qualities), max(qualities))) + + #Return the record and then continue... + record = SeqRecord(UnknownSeq(len(qualities), alphabet), + id = id, name = name, description = descr) + record.letter_annotations["phred_quality"] = qualities + yield record + + if not line : return #StopIteration + assert False, "Should not reach this line" + +class FastqPhredWriter(SequentialSequenceWriter): + """Class to write FASTQ format files (using PHRED quality scores). + + Although you can use this class directly, you are strongly encouraged + to use the Bio.SeqIO.write() function instead. For example, this code + reads in a FASTQ (PHRED) file and re-saves it as another FASTQ (PHRED) + file: + + >>> from Bio import SeqIO + >>> record_iterator = SeqIO.parse(open("Quality/example.fastq"), "fastq") + >>> out_handle = open("Quality/temp.fastq", "w") + >>> SeqIO.write(record_iterator, out_handle, "fastq") + 3 + >>> out_handle.close() + + You might want to do this if the original file included extra line breaks, + which while valid may not be supported by all tools. The output file from + Biopython will have each sequence on a single line, and each quality + string on a single line (which is considered desirable for maximum + compatibility). + + In this next example, a Solexa FASTQ file is converted into a standard + Sanger style FASTQ file using PHRED qualities: + + >>> from Bio import SeqIO + >>> record_iterator = SeqIO.parse(open("Quality/solexa.fastq"), "fastq-solexa") + >>> out_handle = open("Quality/temp.fastq", "w") + >>> SeqIO.write(record_iterator, out_handle, "fastq") + 1 + >>> out_handle.close() + + This code is also called if you use the .format("fastq") method of a + SeqRecord. + + P.S. To avoid cluttering up your working directory, you can delete this + temporary file now: + + >>> import os + >>> os.remove("Quality/temp.fastq") + + """ + def write_record(self, record): + """Write a single FASTQ record to the file.""" + assert self._header_written + assert not self._footer_written + self._record_written = True + + #TODO - Is an empty sequence allowed in FASTQ format? + assert SANGER_SCORE_OFFSET == ord("!") + #This rounds to the nearest integer: + qualities = "".join([chr(int(round(q+SANGER_SCORE_OFFSET,0))) for q \ + in _get_phred_quality(record)]) + if record.seq is None: + raise ValueError("No sequence for record %s" % record.id) + if len(qualities) != len(record) : + raise ValueError("Record %s has sequence length %i but %i quality scores" \ + % (record.id, len(record), len(qualities))) + + title = self.clean(record.id) #TODO - add the description too? cf Fasta output + self.handle.write("@%s\n%s\n+\n%s\n" % (title, record.seq, qualities)) + +class QualPhredWriter(SequentialSequenceWriter): + """Class to write QUAL format files (using PHRED quality scores). + + Although you can use this class directly, you are strongly encouraged + to use the Bio.SeqIO.write() function instead. For example, this code + reads in a FASTQ file and saves the quality scores into a QUAL file: + + >>> from Bio import SeqIO + >>> record_iterator = SeqIO.parse(open("Quality/example.fastq"), "fastq") + >>> out_handle = open("Quality/temp.qual", "w") + >>> SeqIO.write(record_iterator, out_handle, "qual") + 3 + >>> out_handle.close() + + This code is also called if you use the .format("qual") method of a + SeqRecord. + + P.S. Don't forget to clean up the temp file if you don't need it anymore: + + >>> import os + >>> os.remove("Quality/temp.qual") + """ + def __init__(self, handle, wrap=60, record2title=None): + """Create a QUAL writer. + + Arguments: + - handle - Handle to an output file, e.g. as returned + by open(filename, "w") + - wrap - Optional line length used to wrap sequence lines. + Defaults to wrapping the sequence at 60 characters + Use zero (or None) for no wrapping, giving a single + long line for the sequence. + - record2title - Optional function to return the text to be + used for the title line of each record. By default + a combination of the record.id and record.description + is used. If the record.description starts with the + record.id, then just the record.description is used. + + The record2title argument is present for consistency with the + Bio.SeqIO.FastaIO writer class. + """ + SequentialSequenceWriter.__init__(self, handle) + #self.handle = handle + self.wrap = None + if wrap : + if wrap < 1 : + raise ValueError + self.wrap = wrap + self.record2title = record2title + + def write_record(self, record): + """Write a single QUAL record to the file.""" + assert self._header_written + assert not self._footer_written + self._record_written = True + + if self.record2title : + title=self.clean(self.record2title(record)) + else : + id = self.clean(record.id) + description = self.clean(record.description) + + #if description[:len(id)]==id : + if description and description.split(None,1)[0]==id : + #The description includes the id at the start + title = description + else : + title = "%s %s" % (id, description) + + assert "\n" not in title + assert "\r" not in title + self.handle.write(">%s\n" % title) + + #This rounds to the nearest integer. + #TODO - can we put a float in a qual file? + qualities = [("%i" % round(q,0)) for q in _get_phred_quality(record)] + + if self.wrap : + while qualities : + line=qualities.pop(0) + while qualities \ + and len(line) + 1 + len(qualities[0]) < self.wrap : + line += " " + qualities.pop(0) + self.handle.write(line + "\n") + else : + data = " ".join(qualities) + self.handle.write(data + "\n") + +class FastqSolexaWriter(SequentialSequenceWriter): + """Class to write FASTQ format files (using Solexa quality scores). + + Although you can use this class directly, you are strongly encouraged + to use the Bio.SeqIO.write() function instead. For example, this code + reads in a FASTQ file and re-saves it as another FASTQ file: + + >>> from Bio import SeqIO + >>> record_iterator = SeqIO.parse(open("Quality/solexa.fastq"), "fastq-solexa") + >>> out_handle = open("Quality/temp.fastq", "w") + >>> SeqIO.write(record_iterator, out_handle, "fastq-solexa") + 1 + >>> out_handle.close() + + You might want to do this if the original file included extra line + breaks, which (while valid) may not be supported by all tools. The + output file from Biopython will have each sequence on a single line, and + each quality string on a single line (which is considered desirable for + maximum compatibility). + + This code is also called if you use the .format("fastq-solexa") method of + a SeqRecord. + + P.S. Don't forget to delete the temp file if you don't need it anymore: + + >>> import os + >>> os.remove("Quality/temp.fastq") + """ + def write_record(self, record): + """Write a single FASTQ record to the file.""" + assert self._header_written + assert not self._footer_written + self._record_written = True + + #TODO - Is an empty sequence allowed in FASTQ format? + qualities = "".join([chr(int(round(q+SOLEXA_SCORE_OFFSET,0))) for q \ + in _get_solexa_quality(record)]) + if record.seq is None: + raise ValueError("No sequence for record %s" % record.id) + if len(qualities) != len(record) : + raise ValueError("Record %s has sequence length %i but %i quality scores" \ + % (record.id, len(record), len(qualities))) + + title = self.clean(record.id) #TODO - add the description too? cf Fasta output + self.handle.write("@%s\n%s\n+\n%s\n" % (title, record.seq, qualities)) + +def PairedFastaQualIterator(fasta_handle, qual_handle, alphabet = single_letter_alphabet, title2ids = None) : + """Iterate over matched FASTA and QUAL files as SeqRecord objects. + + For example, consider this short QUAL file:: + + >EAS54_6_R1_2_1_413_324 + 26 26 18 26 26 26 26 26 26 26 26 26 26 26 26 22 26 26 26 26 + 26 26 26 23 23 + >EAS54_6_R1_2_1_540_792 + 26 26 26 26 26 26 26 26 26 26 26 22 26 26 26 26 26 12 26 26 + 26 18 26 23 18 + >EAS54_6_R1_2_1_443_348 + 26 26 26 26 26 26 26 26 26 26 26 24 26 22 26 26 13 22 26 18 + 24 18 18 18 18 + + And a matching FASTA file:: + + >EAS54_6_R1_2_1_413_324 + CCCTTCTTGTCTTCAGCGTTTCTCC + >EAS54_6_R1_2_1_540_792 + TTGGCAGGCCAAGGCCGATGGATCA + >EAS54_6_R1_2_1_443_348 + GTTGCTTCTGGCGTGGGTGGGGGGG + + You can parse these separately using Bio.SeqIO with the "qual" and + "fasta" formats, but then you'll get a group of SeqRecord objects with + no sequence, and a matching group with the sequence but not the + qualities. Because it only deals with one input file handle, Bio.SeqIO + can't be used to read the two files together - but this function can! + For example, + + >>> rec_iter = PairedFastaQualIterator(open("Quality/example.fasta", "rU"), + ... open("Quality/example.qual", "rU")) + >>> for record in rec_iter : + ... print record.id, record.seq + EAS54_6_R1_2_1_413_324 CCCTTCTTGTCTTCAGCGTTTCTCC + EAS54_6_R1_2_1_540_792 TTGGCAGGCCAAGGCCGATGGATCA + EAS54_6_R1_2_1_443_348 GTTGCTTCTGGCGTGGGTGGGGGGG + + As with the FASTQ or QUAL parsers, if you want to look at the qualities, + they are in each record's per-letter-annotation dictionary as a simple + list of integers: + + >>> print record.letter_annotations["phred_quality"] + [26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 24, 26, 22, 26, 26, 13, 22, 26, 18, 24, 18, 18, 18, 18] + + If you have access to data as a FASTQ format file, using that directly + would be simpler and more straight forward. Note that you can easily use + this function to convert paired FASTA and QUAL files into FASTQ files: + + >>> from Bio import SeqIO + >>> rec_iter = PairedFastaQualIterator(open("Quality/example.fasta", "rU"), + ... open("Quality/example.qual", "rU")) + >>> out_handle = open("Quality/temp.fastq", "w") + >>> SeqIO.write(rec_iter, out_handle, "fastq") + 3 + >>> out_handle.close() + + And don't forget to clean up the temp file if you don't need it anymore: + + >>> import os + >>> os.remove("Quality/temp.fastq") + """ + from Bio.SeqIO.FastaIO import FastaIterator + fasta_iter = FastaIterator(fasta_handle, alphabet=alphabet, \ + title2ids=title2ids) + qual_iter = QualPhredIterator(qual_handle, alphabet=alphabet, \ + title2ids=title2ids) + + #Using zip(...) would create a list loading everything into memory! + #It would also not catch any extra records found in only one file. + while True : + try : + f_rec = fasta_iter.next() + except StopIteration : + f_rec = None + try : + q_rec = qual_iter.next() + except StopIteration : + q_rec = None + if f_rec is None and q_rec is None : + #End of both files + break + if f_rec is None : + raise ValueError("FASTA file has more entries than the QUAL file.") + if q_rec is None : + raise ValueError("QUAL file has more entries than the FASTA file.") + if f_rec.id != q_rec.id : + raise ValueError("FASTA and QUAL entries do not match (%s vs %s)." \ + % (f_rec.id, q_rec.id)) + if len(f_rec) != len(q_rec.letter_annotations["phred_quality"]) : + raise ValueError("Sequence length and number of quality scores disagree for %s" \ + % f_rec.id) + #Merge the data.... + f_rec.letter_annotations["phred_quality"] = q_rec.letter_annotations["phred_quality"] + yield f_rec + #Done + + +def _test(): + """Run the Bio.SeqIO module's doctests. + + This will try and locate the unit tests directory, and run the doctests + from there in order that the relative paths used in the examples work. + """ + import doctest + import os + if os.path.isdir(os.path.join("..","..","Tests")) : + print "Runing doctests..." + cur_dir = os.path.abspath(os.curdir) + os.chdir(os.path.join("..","..","Tests")) + assert os.path.isfile("Quality/example.fastq") + assert os.path.isfile("Quality/example.fasta") + assert os.path.isfile("Quality/example.qual") + assert os.path.isfile("Quality/tricky.fastq") + assert os.path.isfile("Quality/solexa.fastq") + doctest.testmod() + os.chdir(cur_dir) + del cur_dir + print "Done" + +if __name__ == "__main__" : + _test() + diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/SwissIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/SwissIO.py new file mode 100644 index 0000000..a55c0fe --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/SwissIO.py @@ -0,0 +1,65 @@ +# Copyright 2006 by Peter Cock. All rights reserved. +# +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Bio.SeqIO support for the "swiss" (aka SwissProt/UniProt) file format. + +You are expected to use this module via the Bio.SeqIO functions. +See also the Bio.SwissProt module which offers more than just accessing +the sequences as SeqRecord objects.""" + +from Bio.SwissProt import SProt +import cStringIO + +#This is a generator function! +def SwissIterator(handle) : + """Breaks up a Swiss-Prot/UniProt file into SeqRecord objects. + + Every section from the ID line to the terminating // becomes + a single SeqRecord with associated annotation and features. + + This parser is for the flat file "swiss" format as used by: + * Swiss-Prot aka SwissProt + * TrEMBL + * UniProtKB aka UniProt Knowledgebase + + It does NOT read their new XML file format. + http://www.expasy.org/sprot/ + + For consistency with BioPerl and EMBOSS we call this the "swiss" + format. + """ + parser = SProt.SequenceParser() + lines = [] + for line in handle: + lines.append(line) + if line[:2]=='//': + handle = cStringIO.StringIO("".join(lines)) + record = parser.parse(handle) + lines = [] + yield record + #If there are more lines, it could only be a partial record. + #Should we try and parse them anyway? + + +if __name__ == "__main__" : + print "Quick self test..." + + example_filename = "../../Tests/SwissProt/sp008" + + import os + if not os.path.isfile(example_filename): + print "Missing test file %s" % example_filename + else : + #Try parsing it! + handle = open(example_filename) + records = SwissIterator(handle) + for record in records: + print record.name + print record.id + print record.annotations['keywords'] + print repr(record.annotations['organism']) + print record.seq.tostring()[:20] + "..." + handle.close() diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/TabIO.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/TabIO.py new file mode 100644 index 0000000..3613863 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/TabIO.py @@ -0,0 +1,109 @@ +# Copyright 2008 by Peter Cock. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Bio.SeqIO support for the "tab" (simple tab separated) file format. + +You are expected to use this module via the Bio.SeqIO functions. + +The "tab" format is an ad-hoc plain text file format where each sequence is +on one (long) line. Each line contains the identifier/description, followed +by a tab, followed by the sequence. For example, consider the following +short FASTA format file: + +>ID123456 possible binding site? +CATCNAGATGACACTACGACTACGACTCAGACTAC +>ID123457 random sequence +ACACTACGACTACGACTCAGACTACAAN + +Apart from the descriptions, this can be represented in the simple two column +tab separated format as follows: + +ID123456(tab)CATCNAGATGACACTACGACTACGACTCAGACTAC +ID123457(tab)ACACTACGACTACGACTCAGACTACAAN + +When reading this file, "ID123456" or "ID123457" will be taken as the record's +.id and .name property. There is no other information to record. + +Similarly, when writing to this format, Biopython will ONLY record the record's +.id and .seq (and not the description or any other information) as in the example +above. +""" + +from Bio.Alphabet import single_letter_alphabet +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord +from Interfaces import SequentialSequenceWriter + +#This is a generator function! +def TabIterator(handle, alphabet = single_letter_alphabet) : + """Iterates over tab separated lines (as SeqRecord objects). + + Each line of the file should contain one tab only, dividing the line + into an identifier and the full sequence. + + handle - input file + alphabet - optional alphabet + + The first field is taken as the record's .id and .name (regardless of + any spaces within the text) and the second field is the sequence. + + Any blank lines are ignored. + """ + for line in handle : + try : + title, seq = line.split("\t") #will fail if more than one tab! + except : + if line.strip() == "" : + #It's a blank line, ignore it + continue + raise ValueError("Each line should have one tab separating the" + \ + " title and sequence, this line has %i tabs: %s" \ + % (line.count("\t"), repr(line))) + title = title.strip() + seq = seq.strip() #removes the trailing new line + yield SeqRecord(Seq(seq, alphabet), id = title, name = title) + +class TabWriter(SequentialSequenceWriter): + """Class to write simple tab separated format files. + + Each line consists of "id(tab)sequence" only. + + Any description, name or other annotation is not recorded. + """ + def write_record(self, record): + """Write a single tab line to the file.""" + assert self._header_written + assert not self._footer_written + self._record_written = True + + title = self.clean(record.id) + seq = self._get_seq_string(record) #Catches sequence being None + assert "\t" not in title + assert "\n" not in title + assert "\r" not in title + assert "\t" not in seq + assert "\n" not in seq + assert "\r" not in seq + self.handle.write("%s\t%s\n" % (title, seq)) + + +if __name__ == "__main__" : + print "Running quick self test" + from StringIO import StringIO + + #This example has a trailing blank line which should be ignored + handle = StringIO("Alpha\tAAAAAAA\nBeta\tCCCCCCC\n\n") + records = list(TabIterator(handle)) + assert len(records) == 2 + + handle = StringIO("Alpha\tAAAAAAA\tExtra\nBeta\tCCCCCCC\n") + try : + records = list(TabIterator(handle)) + assert False, "Should have reject this invalid example!" + except ValueError : + #Good! + pass + + print "Done" diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqIO/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/__init__.py new file mode 100644 index 0000000..e2be3ec --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqIO/__init__.py @@ -0,0 +1,650 @@ +# Copyright 2006-2008 by Peter Cock. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +# +#Nice link: +# http://www.ebi.ac.uk/help/formats_frame.html + +"""Sequence input/output as SeqRecord objects. + +Bio.SeqIO is also documented at U{http://biopython.org/wiki/SeqIO} and by +a whole chapter in our tutorial: + - U{http://biopython.org/DIST/docs/tutorial/Tutorial.html} + - U{http://biopython.org/DIST/docs/tutorial/Tutorial.pdf} + +Input +===== +The main function is Bio.SeqIO.parse(...) which takes an input file handle, +and format string. This returns an iterator giving SeqRecord objects: + + >>> from Bio import SeqIO + >>> handle = open("Fasta/f002", "rU") + >>> for record in SeqIO.parse(handle, "fasta") : + ... print record.id, len(record) + gi|1348912|gb|G26680|G26680 633 + gi|1348917|gb|G26685|G26685 413 + gi|1592936|gb|G29385|G29385 471 + >>> handle.close() + +Note that the parse() function will all invoke the relevant parser for the +format with its default settings. You may want more control, in which case +you need to create a format specific sequence iterator directly. + +For non-interlaced files (e.g. Fasta, GenBank, EMBL) with multiple records +using a sequence iterator can save you a lot of memory (RAM). There is +less benefit for interlaced file formats (e.g. most multiple alignment file +formats). However, an iterator only lets you access the records one by one. + +If you want random access to the records by number, turn this into a list: + + >>> from Bio import SeqIO + >>> handle = open("Fasta/f002", "rU") + >>> records = list(SeqIO.parse(handle, "fasta")) + >>> handle.close() + >>> print records[1].id + gi|1348917|gb|G26685|G26685 + +If you want random access to the records by a key such as the record id, +turn the iterator into a dictionary: + + >>> from Bio import SeqIO + >>> handle = open("Fasta/f002", "rU") + >>> record_dict = SeqIO.to_dict(SeqIO.parse(handle, "fasta")) + >>> handle.close() + >>> print len(record_dict["gi|1348917|gb|G26685|G26685"]) + 413 + +If you expect your file to contain one-and-only-one record, then we provide +the following 'helper' function which will return a single SeqRecord, or +raise an exception if there are no records or more than one record: + + >>> from Bio import SeqIO + >>> handle = open("Fasta/f001", "rU") + >>> record = SeqIO.read(handle, "fasta") + >>> handle.close() + >>> print record.id, len(record) + gi|3318709|pdb|1A91| 79 + +This style is useful when you expect a single record only (and would +consider multiple records an error). For example, when dealing with GenBank +files for bacterial genomes or chromosomes, there is normally only a single +record. Alternatively, use this with a handle when download a single record +from the internet. + +However, if you just want the first record from a file containing multiple +record, use the iterator's next() method: + + >>> from Bio import SeqIO + >>> handle = open("Fasta/f002", "rU") + >>> record = SeqIO.parse(handle, "fasta").next() + >>> handle.close() + >>> print record.id, len(record) + gi|1348912|gb|G26680|G26680 633 + +The above code will work as long as the file contains at least one record. +Note that if there is more than one record, the remaining records will be +silently ignored. + +Input - Alignments +================== +You can read in alignment files as Alignment objects using Bio.AlignIO. +Alternatively, reading in an alignment file format via Bio.SeqIO will give +you a SeqRecord for each row of each alignment: + + >>> from Bio import SeqIO + >>> handle = open("Clustalw/hedgehog.aln", "rU") + >>> for record in SeqIO.parse(handle, "clustal") : + ... print record.id, len(record) + gi|167877390|gb|EDS40773.1| 447 + gi|167234445|ref|NP_001107837. 447 + gi|74100009|gb|AAZ99217.1| 447 + gi|13990994|dbj|BAA33523.2| 447 + gi|56122354|gb|AAV74328.1| 447 + >>> handle.close() + +Output +====== +Use the function Bio.SeqIO.write(...), which takes a complete set of +SeqRecord objects (either as a list, or an iterator), an output file handle +and of course the file format:: + + from Bio import SeqIO + records = ... + handle = open("example.faa", "w") + SeqIO.write(records, handle, "fasta") + handle.close() + +In general, you are expected to call this function once (with all your +records) and then close the file handle. + +Output - Advanced +================= +The effect of calling write() multiple times on a single file will vary +depending on the file format, and is best avoided unless you have a strong +reason to do so. + +Trying this for certain alignment formats (e.g. phylip, clustal, stockholm) +would have the effect of concatenating several multiple sequence alignments +together. Such files are created by the PHYLIP suite of programs for +bootstrap analysis. + +For sequential files formats (e.g. fasta, genbank) each "record block" holds +a single sequence. For these files it would probably be safe to call +write() multiple times. + +File Formats +============ +When specifying the file format, use lowercase strings. The same format +names are also used in Bio.AlignIO and include the following: + + - ace - Reads the contig sequences from an ACE assembly file. + - embl - The EMBL flat file format. Uses Bio.GenBank internally. + - fasta - The generic sequence file format where each record starts with + an identifer line starting with a ">" character, followed by + lines of sequence. + - fastq - A "FASTA like" format used by Sanger which also stores PHRED + sequence quality values. + - fastq-solexa - The Solexa/Illumnia variant of the Sanger FASTQ format which + encodes Solexa quality scores (not PHRED quality scores). + - genbank - The GenBank or GenPept flat file format. + - gb - An alias for "genbank", for consistency with NCBI Entrez Utilities + - ig - The IntelliGenetics file format, apparently the same as the + MASE alignment format. + - phd - Output from PHRED, used by PHRAP and CONSED for input. + - pir - A "FASTA like" format introduced by the National Biomedical + Research Foundation (NBRF) for the Protein Information Resource + (PIR) database, now part of UniProt. + - swiss - Plain text Swiss-Prot aka UniProt format. + - tab - Simple two column tab separated sequence files, where each + line holds a record's identifier and sequence. For example, + this is used as by Aligent's eArray software when saving + microarray probes in a minimal tab delimited text file. + - qual - A "FASTA like" format holding PHRED quality values from + sequencing DNA, but no actual sequences (usually provided + in separate FASTA files). + +Note that while Bio.SeqIO can read all the above file formats, it cannot +write to all of them. + +You can also use any file format supported by Bio.AlignIO, such as "nexus", +"phlip" and "stockholm", which gives you access to the individual sequences +making up each alignment as SeqRecords. +""" +__docformat__ = "epytext en" #not just plaintext + +#TODO +# - define policy on reading aligned sequences with gaps in +# (e.g. - and . characters) including how the alphabet interacts +# +# - Can we build the to_alignment(...) functionality +# into the generic Alignment class instead? +# +# - How best to handle unique/non unique record.id when writing. +# For most file formats reading such files is fine; The stockholm +# parser would fail. +# +# - MSF multiple alignment format, aka GCG, aka PileUp format (*.msf) +# http://www.bioperl.org/wiki/MSF_multiple_alignment_format + +""" +FAO BioPython Developers +======================== +The way I envision this SeqIO system working as that for any sequence file +format we have an iterator that returns SeqRecord objects. + +This also applies to interlaced fileformats (like clustal - although that +is now handled via Bio.AlignIO instead) where the file cannot be read record +by record. You should still return an iterator, even if the implementation +could just as easily return a list. + +These file format specific sequence iterators may be implemented as: +* Classes which take a handle for __init__ and provide the __iter__ method +* Functions that take a handle, and return an iterator object +* Generator functions that take a handle, and yield SeqRecord objects + +It is then trivial to turn this iterator into a list of SeqRecord objects, +an in memory dictionary, or a multiple sequence alignment object. + +For building the dictionary by default the id propery of each SeqRecord is +used as the key. You should always populate the id property, and it should +be unique in most cases. For some file formats the accession number is a good +choice. If the file itself contains ambiguous identifiers, don't try and +dis-ambiguate them - return them as is. + +When adding a new file format, please use the same lower case format name +as BioPerl, or if they have not defined one, try the names used by EMBOSS. + +See also http://biopython.org/wiki/SeqIO_dev + +--Peter +""" + +import os +from Bio.Seq import Seq +from Bio.SeqRecord import SeqRecord +from Bio.Align.Generic import Alignment +from Bio.Alphabet import Alphabet, AlphabetEncoder, _get_base_alphabet + +import AceIO +import FastaIO +import IgIO #IntelliGenetics or MASE format +import InsdcIO #EMBL and GenBank +import PhdIO +import PirIO +import SwissIO +import TabIO +import QualityIO #FastQ and qual files + + +#Convention for format names is "mainname-subtype" in lower case. +#Please use the same names as BioPerl where possible. +# +#Note that this simple system copes with defining +#multiple possible iterators for a given format/extension +#with the -subtype suffix +# +#Most alignment file formats will be handled via Bio.AlignIO + +_FormatToIterator ={"fasta" : FastaIO.FastaIterator, + "gb" : InsdcIO.GenBankIterator, + "genbank" : InsdcIO.GenBankIterator, + "genbank-cds" : InsdcIO.GenBankCdsFeatureIterator, + "embl" : InsdcIO.EmblIterator, + "embl-cds" : InsdcIO.EmblCdsFeatureIterator, + "ig" : IgIO.IgIterator, + "swiss" : SwissIO.SwissIterator, + "phd" : PhdIO.PhdIterator, + "ace" : AceIO.AceIterator, + "tab" : TabIO.TabIterator, + "pir" : PirIO.PirIterator, + "fastq" : QualityIO.FastqPhredIterator, + "fastq-solexa" : QualityIO.FastqSolexaIterator, + "qual" : QualityIO.QualPhredIterator, + } + +_FormatToWriter ={"fasta" : FastaIO.FastaWriter, + "gb" : InsdcIO.GenBankWriter, + "genbank" : InsdcIO.GenBankWriter, + "tab" : TabIO.TabWriter, + "fastq" : QualityIO.FastqPhredWriter, + "fastq-solexa" : QualityIO.FastqSolexaWriter, + "qual" : QualityIO.QualPhredWriter, + } + +def write(sequences, handle, format) : + """Write complete set of sequences to a file. + + - sequences - A list (or iterator) of SeqRecord objects. + - handle - File handle object to write to. + - format - lower case string describing the file format to write. + + You should close the handle after calling this function. + + Returns the number of records written (as an integer). + """ + from Bio import AlignIO + + #Try and give helpful error messages: + if isinstance(handle, basestring) : + raise TypeError("Need a file handle, not a string (i.e. not a filename)") + if not isinstance(format, basestring) : + raise TypeError("Need a string for the file format (lower case)") + if not format : + raise ValueError("Format required (lower case string)") + if format != format.lower() : + raise ValueError("Format string '%s' should be lower case" % format) + if isinstance(sequences,SeqRecord): + raise ValueError("Use a SeqRecord list/iterator, not just a single SeqRecord") + + #Map the file format to a writer class + if format in _FormatToWriter : + writer_class = _FormatToWriter[format] + count = writer_class(handle).write_file(sequences) + elif format in AlignIO._FormatToWriter : + #Try and turn all the records into a single alignment, + #and write that using Bio.AlignIO + alignment = to_alignment(sequences) + alignment_count = AlignIO.write([alignment], handle, format) + assert alignment_count == 1, "Internal error - the underlying writer " \ + + " should have returned 1, not %s" % repr(alignment_count) + count = len(alignment.get_all_seqs()) + del alignment_count, alignment + elif format in _FormatToIterator or format in AlignIO._FormatToIterator : + raise ValueError("Reading format '%s' is supported, but not writing" \ + % format) + else : + raise ValueError("Unknown format '%s'" % format) + + assert isinstance(count, int), "Internal error - the underlying writer " \ + + " should have returned the record count, not %s" % repr(count) + return count + +def parse(handle, format, alphabet=None) : + r"""Turns a sequence file into an iterator returning SeqRecords. + + - handle - handle to the file. + - format - lower case string describing the file format. + - alphabet - optional Alphabet object, useful when the sequence type + cannot be automatically inferred from the file itself + (e.g. format="fasta" or "tab") + + Typical usage, opening a file to read in, and looping over the record(s): + + >>> from Bio import SeqIO + >>> filename = "Nucleic/sweetpea.nu" + >>> for record in SeqIO.parse(open(filename,"rU"), "fasta") : + ... print "ID", record.id + ... print "Sequence length", len(record) + ... print "Sequence alphabet", record.seq.alphabet + ID gi|3176602|gb|U78617.1|LOU78617 + Sequence length 309 + Sequence alphabet SingleLetterAlphabet() + + For file formats like FASTA where the alphabet cannot be determined, it + may be useful to specify the alphabet explicitly: + + >>> from Bio import SeqIO + >>> from Bio.Alphabet import generic_dna + >>> filename = "Nucleic/sweetpea.nu" + >>> for record in SeqIO.parse(open(filename,"rU"), "fasta", generic_dna) : + ... print "ID", record.id + ... print "Sequence length", len(record) + ... print "Sequence alphabet", record.seq.alphabet + ID gi|3176602|gb|U78617.1|LOU78617 + Sequence length 309 + Sequence alphabet DNAAlphabet() + + If you have a string 'data' containing the file contents, you must + first turn this into a handle in order to parse it: + + >>> data = ">Alpha\nACCGGATGTA\n>Beta\nAGGCTCGGTTA\n" + >>> from Bio import SeqIO + >>> from StringIO import StringIO + >>> for record in SeqIO.parse(StringIO(data), "fasta") : + ... print record.id, record.seq + Alpha ACCGGATGTA + Beta AGGCTCGGTTA + + Use the Bio.SeqIO.read(handle, format) function when you expect a single + record only. + """ + #NOTE - The above docstring has some raw \n characters needed + #for the StringIO example, hense the whole docstring is in raw + #string more (see the leading r before the opening quote). + from Bio import AlignIO + + #Try and give helpful error messages: + if isinstance(handle, basestring) : + raise TypeError("Need a file handle, not a string (i.e. not a filename)") + if not isinstance(format, basestring) : + raise TypeError("Need a string for the file format (lower case)") + if not format : + raise ValueError("Format required (lower case string)") + if format != format.lower() : + raise ValueError("Format string '%s' should be lower case" % format) + if alphabet is not None and not (isinstance(alphabet, Alphabet) or \ + isinstance(alphabet, AlphabetEncoder)) : + raise ValueError("Invalid alphabet, %s" % repr(alphabet)) + + #Map the file format to a sequence iterator: + if format in _FormatToIterator : + iterator_generator = _FormatToIterator[format] + if alphabet is None : + return iterator_generator(handle) + try : + return iterator_generator(handle, alphabet=alphabet) + except : + return _force_alphabet(iterator_generator(handle), alphabet) + elif format in AlignIO._FormatToIterator : + #Use Bio.AlignIO to read in the alignments + #TODO - Once we drop support for Python 2.3, this helper function can be + #replaced with a generator expression. + return _iterate_via_AlignIO(handle, format, alphabet) + else : + raise ValueError("Unknown format '%s'" % format) + +#This is a generator function +def _iterate_via_AlignIO(handle, format, alphabet) : + """Iterate over all records in several alignments (PRIVATE).""" + from Bio import AlignIO + for align in AlignIO.parse(handle, format, alphabet=alphabet) : + for record in align : + yield record + +def _force_alphabet(record_iterator, alphabet) : + """Iterate over records, over-riding the alphabet (PRIVATE).""" + #Assume the alphabet argument has been pre-validated + given_base_class = _get_base_alphabet(alphabet).__class__ + for record in record_iterator : + if isinstance(_get_base_alphabet(record.seq.alphabet), + given_base_class) : + record.seq.alphabet = alphabet + yield record + else : + raise ValueError("Specified alphabet %s clashes with "\ + "that determined from the file, %s" \ + % (repr(alphabet), repr(record.seq.alphabet))) + +def read(handle, format, alphabet=None) : + """Turns a sequence file into a single SeqRecord. + + - handle - handle to the file. + - format - string describing the file format. + - alphabet - optional Alphabet object, useful when the sequence type + cannot be automatically inferred from the file itself + (e.g. format="fasta" or "tab") + + This function is for use parsing sequence files containing + exactly one record. For example, reading a GenBank file: + + >>> from Bio import SeqIO + >>> record = SeqIO.read(open("GenBank/arab1.gb", "rU"), "genbank") + >>> print "ID", record.id + ID AC007323.5 + >>> print "Sequence length", len(record) + Sequence length 86436 + >>> print "Sequence alphabet", record.seq.alphabet + Sequence alphabet IUPACAmbiguousDNA() + + If the handle contains no records, or more than one record, + an exception is raised. For example: + + >>> from Bio import SeqIO + >>> record = SeqIO.read(open("GenBank/cor6_6.gb", "rU"), "genbank") + Traceback (most recent call last): + ... + ValueError: More than one record found in handle + + If however you want the first record from a file containing + multiple records this function would raise an exception (as + shown in the example above). Instead use: + + >>> from Bio import SeqIO + >>> record = SeqIO.parse(open("GenBank/cor6_6.gb", "rU"), "genbank").next() + >>> print "First record's ID", record.id + First record's ID X55053.1 + + Use the Bio.SeqIO.parse(handle, format) function if you want + to read multiple records from the handle. + """ + iterator = parse(handle, format, alphabet) + try : + first = iterator.next() + except StopIteration : + first = None + if first is None : + raise ValueError("No records found in handle") + try : + second = iterator.next() + except StopIteration : + second = None + if second is not None : + raise ValueError("More than one record found in handle") + return first + +def to_dict(sequences, key_function=None) : + """Turns a sequence iterator or list into a dictionary. + + - sequences - An iterator that returns SeqRecord objects, + or simply a list of SeqRecord objects. + - key_function - Optional function which when given a SeqRecord + returns a unique string for the dictionary key. + + e.g. key_function = lambda rec : rec.name + or, key_function = lambda rec : rec.description.split()[0] + + If key_function is ommitted then record.id is used, on the + assumption that the records objects returned are SeqRecords + with a unique id field. + + If there are duplicate keys, an error is raised. + + Example usage, defaulting to using the record.id as key: + + >>> from Bio import SeqIO + >>> handle = open("GenBank/cor6_6.gb", "rU") + >>> format = "genbank" + >>> id_dict = SeqIO.to_dict(SeqIO.parse(handle, format)) + >>> print id_dict.keys() + ['L31939.1', 'AJ237582.1', 'X62281.1', 'AF297471.1', 'X55053.1', 'M81224.1'] + >>> print id_dict["L31939.1"].description + Brassica rapa (clone bif72) kin mRNA, complete cds. + + A more complex example, using the key_function argument in order to use + a sequence checksum as the dictionary key: + + >>> from Bio import SeqIO + >>> from Bio.SeqUtils.CheckSum import seguid + >>> handle = open("GenBank/cor6_6.gb", "rU") + >>> format = "genbank" + >>> seguid_dict = SeqIO.to_dict(SeqIO.parse(handle, format), + ... key_function = lambda rec : seguid(rec.seq)) + >>> for key, record in seguid_dict.iteritems() : + ... print key, record.id + SabZaA4V2eLE9/2Fm5FnyYy07J4 X55053.1 + l7gjJFE6W/S1jJn5+1ASrUKW/FA X62281.1 + /wQvmrl87QWcm9llO4/efg23Vgg AJ237582.1 + TtWsXo45S3ZclIBy4X/WJc39+CY M81224.1 + uVEYeAQSV5EDQOnFoeMmVea+Oow AF297471.1 + BUg6YxXSKWEcFFH0L08JzaLGhQs L31939.1 + """ + if key_function is None : + key_function = lambda rec : rec.id + + d = dict() + for record in sequences : + key = key_function(record) + if key in d : + raise ValueError("Duplicate key '%s'" % key) + d[key] = record + return d + + +def to_alignment(sequences, alphabet=None, strict=True) : + """Returns a multiple sequence alignment (OBSOLETE). + + - sequences -An iterator that returns SeqRecord objects, + or simply a list of SeqRecord objects. All + the record sequences must be the same length. + - alphabet - Optional alphabet. Stongly recommended. + - strict - Optional, defaults to True. Should error checking + be done? + + Using this function is now discouraged. Rather doing this: + + >>> from Bio import SeqIO + >>> handle = open("Clustalw/protein.aln") + >>> alignment = SeqIO.to_alignment(SeqIO.parse(handle, "clustal")) + >>> handle.close() + + You are now encouraged to use Bio.AlignIO instead, e.g. + + >>> from Bio import AlignIO + >>> handle = open("Clustalw/protein.aln") + >>> alignment = AlignIO.read(handle, "clustal") + >>> handle.close() + """ + #TODO - Move this functionality into the Alignment class instead? + from Bio.Alphabet import generic_alphabet + from Bio.Alphabet import _consensus_alphabet + if alphabet is None : + sequences = list(sequences) + alphabet = _consensus_alphabet([rec.seq.alphabet for rec in sequences \ + if rec.seq is not None]) + + if not (isinstance(alphabet, Alphabet) or isinstance(alphabet, AlphabetEncoder)) : + raise ValueError("Invalid alphabet") + + alignment_length = None + alignment = Alignment(alphabet) + for record in sequences : + if strict : + if alignment_length is None : + alignment_length = len(record.seq) + elif alignment_length != len(record.seq) : + raise ValueError("Sequences must all be the same length") + + assert isinstance(record.seq.alphabet, Alphabet) \ + or isinstance(record.seq.alphabet, AlphabetEncoder), \ + "Sequence does not have a valid alphabet" + + #TODO - Move this alphabet comparison code into the Alphabet module/class? + #TODO - Is a normal alphabet "ungapped" by default, or does it just mean + #undecided? + if isinstance(record.seq.alphabet, Alphabet) \ + and isinstance(alphabet, Alphabet) : + #Comparing two non-gapped alphabets + if not isinstance(record.seq.alphabet, alphabet.__class__) : + raise ValueError("Incompatible sequence alphabet " \ + + "%s for %s alignment" \ + % (record.seq.alphabet, alphabet)) + elif isinstance(record.seq.alphabet, AlphabetEncoder) \ + and isinstance(alphabet, Alphabet) : + raise ValueError("Sequence has a gapped alphabet, alignment does not") + elif isinstance(record.seq.alphabet, Alphabet) \ + and isinstance(alphabet, Gapped) : + #Sequence isn't gapped, alignment is. + if not isinstance(record.seq.alphabet, alphabet.alphabet.__class__) : + raise ValueError("Incompatible sequence alphabet " \ + + "%s for %s alignment" \ + % (record.seq.alphabet, alphabet)) + else : + #Comparing two gapped alphabets + if not isinstance(record.seq.alphabet, alphabet.__class__) : + raise ValueError("Incompatible sequence alphabet " \ + + "%s for %s alignment" \ + % (record.seq.alphabet, alphabet)) + if record.seq.alphabet.gap_char != alphabet.gap_char : + raise ValueError("Sequence gap characters != alignment gap char") + #ToDo, additional checks on the specified alignment... + #Should we look at the alphabet.contains() method? + if record.seq is None : + raise TypeError("SeqRecord (id=%s) has None for its sequence." % record.id) + + #This is abusing the "private" records list, + #we should really have a method like add_sequence + #but which takes SeqRecord objects. See also Bug 1944 + alignment._records.append(record) + return alignment + +def _test(): + """Run the Bio.SeqIO module's doctests. + + This will try and locate the unit tests directory, and run the doctests + from there in order that the relative paths used in the examples work. + """ + import doctest + import os + if os.path.isdir(os.path.join("..","..","Tests")) : + print "Runing doctests..." + cur_dir = os.path.abspath(os.curdir) + os.chdir(os.path.join("..","..","Tests")) + doctest.testmod() + os.chdir(cur_dir) + del cur_dir + print "Done" + +if __name__ == "__main__": + #Run the doctests + _test() diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqRecord.py b/binaries/src/globplot/biopython-1.50/Bio/SeqRecord.py new file mode 100644 index 0000000..46a426c --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqRecord.py @@ -0,0 +1,628 @@ +# Copyright 2000-2002 Andrew Dalke. +# Copyright 2002-2004 Brad Chapman. +# Copyright 2006-2009 by Peter Cock. +# All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +"""Represent a Sequence Record, a sequence with annotation.""" +__docformat__ = "epytext en" #Simple markup to show doctests nicely + +# NEEDS TO BE SYNCH WITH THE REST OF BIOPYTHON AND BIOPERL +# In particular, the SeqRecord and BioSQL.BioSeq.DBSeqRecord classes +# need to be in sync (this is the BioSQL "Database SeqRecord", see +# also BioSQL.BioSeq.DBSeq which is the "Database Seq" class) + +class _RestrictedDict(dict): + """Dict which only allows sequences of given length as values (PRIVATE). + + This simple subclass of the python dictionary is used in the SeqRecord + object for holding per-letter-annotations. This class is intended to + prevent simple errors by only allowing python sequences (e.g. lists, + strings and tuples) to be stored, and only if their length matches that + expected (the length of the SeqRecord's seq object). It cannot however + prevent the entries being edited in situ (for example appending entries + to a list). + """ + def __init__(self, length) : + """Create an EMPTY restricted dictionary.""" + dict.__init__(self) + self._length = int(length) + def __setitem__(self, key, value) : + if not hasattr(value,"__len__") or not hasattr(value,"__getitem__") \ + or len(value) != self._length : + raise TypeError("We only allow python sequences (lists, tuples or " + "strings) of length %i." % self._length) + dict.__setitem__(self, key, value) + +class SeqRecord(object): + """A SeqRecord object holds a sequence and information about it. + + Main attributes: + - id - Identifier such as a locus tag (string) + - seq - The sequence itself (Seq object) + + Additional attributes: + - name - Sequence name, e.g. gene name (string) + - description - Additional text (string) + - dbxrefs - List of database cross references (list of strings) + - features - Any (sub)features defined (list of SeqFeature objects) + - annotations - Further information about the whole sequence (dictionary) + Most entries are lists of strings. + - letter_annotations - Per letter/symbol annotation (restricted + dictionary). This holds python sequences (lists, strings + or tuples) whose length matches that of the sequence. + A typical use would be to hold a list of integers + representing sequencing quality scores, or a string + representing the secondary structure. + + You will typically use Bio.SeqIO to read in sequences from files as + SeqRecord objects. However, you may want to create your own SeqRecord + objects directly (see the __init__ method for further details): + + >>> from Bio.Seq import Seq + >>> from Bio.SeqRecord import SeqRecord + >>> from Bio.Alphabet import IUPAC + >>> record = SeqRecord(Seq("MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF", + ... IUPAC.protein), + ... id="YP_025292.1", name="HokC", + ... description="toxic membrane protein") + >>> print record + ID: YP_025292.1 + Name: HokC + Description: toxic membrane protein + Number of features: 0 + Seq('MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF', IUPACProtein()) + + If you want to save SeqRecord objects to a sequence file, use Bio.SeqIO + for this. For the special case where you want the SeqRecord turned into + a string in a particular file format there is a format method which uses + Bio.SeqIO internally: + + >>> print record.format("fasta") + >YP_025292.1 toxic membrane protein + MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF + + """ + def __init__(self, seq, id = "", name = "", + description = "", dbxrefs = None, + features = None): + """Create a SeqRecord. + + Arguments: + - seq - Sequence, required (Seq or Mutable object) + - id - Sequence identifier, recommended (string) + - name - Sequence name, optional (string) + - description - Sequence description, optional (string) + - dbxrefs - Database cross references, optional (list of strings) + - features - Any (sub)features, optional (list of SeqFeature objects) + + You will typically use Bio.SeqIO to read in sequences from files as + SeqRecord objects. However, you may want to create your own SeqRecord + objects directly. + + Note that while an id is optional, we strongly recommend you supply a + unique id string for each record. This is especially important + if you wish to write your sequences to a file. + + If you don't have the actual sequence, but you do know its length, + then using the UnknownSeq object from Bio.Seq is appropriate. + + You can create a 'blank' SeqRecord object, and then populate the + attributes later. Note that currently the annotations and the + letter_annotations dictionaries cannot be specified when creating + the SeqRecord. + """ + if id is not None and not isinstance(id, basestring) : + #Lots of existing code uses id=None... this may be a bad idea. + raise TypeError("id argument should be a string") + if not isinstance(name, basestring) : + raise TypeError("name argument should be a string") + if not isinstance(description, basestring) : + raise TypeError("description argument should be a string") + if dbxrefs is not None and not isinstance(dbxrefs, list) : + raise TypeError("dbxrefs argument should be a list (of strings)") + if features is not None and not isinstance(features, list) : + raise TypeError("features argument should be a list (of SeqFeature objects)") + self._seq = seq + self.id = id + self.name = name + self.description = description + if dbxrefs is None: + dbxrefs = [] + self.dbxrefs = dbxrefs + # annotations about the whole sequence + self.annotations = {} + + # annotations about each letter in the sequence + if seq is None : + #Should we allow this and use a normal unrestricted dict? + self._per_letter_annotations = _RestrictedDict(length=0) + else : + try : + self._per_letter_annotations = _RestrictedDict(length=len(seq)) + except : + raise TypeError("seq argument should be a Seq or MutableSeq") + + # annotations about parts of the sequence + if features is None: + features = [] + self.features = features + + #TODO - Just make this a read only property? + def _set_per_letter_annotations(self, value) : + if not isinstance(value, dict) : + raise TypeError("The per-letter-annotations should be a " + "(restricted) dictionary.") + #Turn this into a restricted-dictionary (and check the entries) + try : + self._per_letter_annotations = _RestrictedDict(length=len(self.seq)) + except AttributeError : + #e.g. seq is None + self._per_letter_annotations = _RestrictedDict(length=0) + self._per_letter_annotations.update(value) + letter_annotations = property( \ + fget=lambda self : self._per_letter_annotations, + fset=_set_per_letter_annotations, + doc="""Dictionary of per-letter-annotation for the sequence. + + For example, this can hold quality scores used in FASTQ or QUAL files. + Consider this example using Bio.SeqIO to read in an example Solexa + variant FASTQ file as a SeqRecord: + + >>> from Bio import SeqIO + >>> handle = open("Quality/solexa.fastq", "rU") + >>> record = SeqIO.read(handle, "fastq-solexa") + >>> handle.close() + >>> print record.id, record.seq + slxa_0013_1_0001_24 ACAAAAATCACAAGCATTCTTATACACC + >>> print record.letter_annotations.keys() + ['solexa_quality'] + >>> print record.letter_annotations["solexa_quality"] + [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -6, -1, -1, -4, -1, -4, -19, -10, -27, -18] + + The per-letter-annotaions get sliced automatically if you slice the + parent SeqRecord, for example taking the last ten bases: + + >>> sub_record = record[-10:] + >>> print sub_record.id, sub_record.seq + slxa_0013_1_0001_24 CTTATACACC + >>> print sub_record.letter_annotations["solexa_quality"] + [-6, -1, -1, -4, -1, -4, -19, -10, -27, -18] + + Any python sequence (i.e. list, tuple or string) can be recorded in + the SeqRecord's letter_annotations dictionary as long as the length + matches that of the SeqRecord's sequence. e.g. + + >>> len(sub_record.letter_annotations) + 1 + >>> sub_record.letter_annotations["dummy"] = "abcdefghij" + >>> len(sub_record.letter_annotations) + 2 + + You can delete entries from the letter_annotations dictionary as usual: + + >>> del sub_record.letter_annotations["solexa_quality"] + >>> sub_record.letter_annotations + {'dummy': 'abcdefghij'} + + You can completely clear the dictionary easily as follows: + + >>> sub_record.letter_annotations = {} + >>> sub_record.letter_annotations + {} + """) + + def _set_seq(self, value) : + #TODO - Add a deprecation warning that the seq should be write only? + if self._per_letter_annotations : + #TODO - Make this a warning? Silently empty the dictionary? + raise ValueError("You must empty the letter annotations first!") + self._seq = value + try : + self._per_letter_annotations = _RestrictedDict(length=len(self.seq)) + except AttributeError : + #e.g. seq is None + self._per_letter_annotations = _RestrictedDict(length=0) + + seq = property(fget=lambda self : self._seq, + fset=_set_seq, + doc="The sequence itself, as a Seq or MutableSeq object.") + + def __getitem__(self, index) : + """Returns a sub-sequence or an individual letter. + + Splicing, e.g. my_record[5:10], returns a new SeqRecord for + that sub-sequence with approriate annotation preserved. The + name, id and description are kept. + + Any per-letter-annotations are sliced to match the requested + sub-sequence. Unless a stride is used, all those features + which fall fully within the subsequence are included (with + their locations adjusted accordingly). + + However, the annotations dictionary and the dbxrefs list are + not used for the new SeqRecord, as in general they may not + apply to the subsequence. If you want to preserve them, you + must explictly copy them to the new SeqRecord yourself. + + Using an integer index, e.g. my_record[5] is shorthand for + extracting that letter from the sequence, my_record.seq[5]. + + For example, consider this short protein and its secondary + structure as encoded by the PDB (e.g. H for alpha helices), + plus a simple feature for its histidine self phosphorylation + site: + + >>> from Bio.Seq import Seq + >>> from Bio.SeqRecord import SeqRecord + >>> from Bio.SeqFeature import SeqFeature, FeatureLocation + >>> from Bio.Alphabet import IUPAC + >>> rec = SeqRecord(Seq("MAAGVKQLADDRTLLMAGVSHDLRTPLTRIRLAT" + ... "EMMSEQDGYLAESINKDIEECNAIIEQFIDYLR", + ... IUPAC.protein), + ... id="1JOY", name="EnvZ", + ... description="Homodimeric domain of EnvZ from E. coli") + >>> rec.letter_annotations["secondary_structure"] = \ + " S SSSSSSHHHHHTTTHHHHHHHHHHHHHHHHHHHHHHTHHHHHHHHHHHHHHHHHHHHHTT " + >>> rec.features.append(SeqFeature(FeatureLocation(20,21), + ... type = "Site")) + + Now let's have a quick look at the full record, + + >>> print rec + ID: 1JOY + Name: EnvZ + Description: Homodimeric domain of EnvZ from E. coli + Number of features: 1 + Per letter annotation for: secondary_structure + Seq('MAAGVKQLADDRTLLMAGVSHDLRTPLTRIRLATEMMSEQDGYLAESINKDIEE...YLR', IUPACProtein()) + >>> print rec.letter_annotations["secondary_structure"] + S SSSSSSHHHHHTTTHHHHHHHHHHHHHHHHHHHHHHTHHHHHHHHHHHHHHHHHHHHHTT + >>> print rec.features[0].location + [20:21] + + Now let's take a sub sequence, here chosen as the first (fractured) + alpha helix which includes the histidine phosphorylation site: + + >>> sub = rec[11:41] + >>> print sub + ID: 1JOY + Name: EnvZ + Description: Homodimeric domain of EnvZ from E. coli + Number of features: 1 + Per letter annotation for: secondary_structure + Seq('RTLLMAGVSHDLRTPLTRIRLATEMMSEQD', IUPACProtein()) + >>> print sub.letter_annotations["secondary_structure"] + HHHHHTTTHHHHHHHHHHHHHHHHHHHHHH + >>> print sub.features[0].location + [9:10] + + You can also of course omit the start or end values, for + example to get the first ten letters only: + + >>> print rec[:10] + ID: 1JOY + Name: EnvZ + Description: Homodimeric domain of EnvZ from E. coli + Number of features: 0 + Per letter annotation for: secondary_structure + Seq('MAAGVKQLAD', IUPACProtein()) + + Or for the last ten letters: + + >>> print rec[-10:] + ID: 1JOY + Name: EnvZ + Description: Homodimeric domain of EnvZ from E. coli + Number of features: 0 + Per letter annotation for: secondary_structure + Seq('IIEQFIDYLR', IUPACProtein()) + + If you omit both, then you get a copy of the original record (although + lacking the annotations and dbxrefs): + + >>> print rec[:] + ID: 1JOY + Name: EnvZ + Description: Homodimeric domain of EnvZ from E. coli + Number of features: 1 + Per letter annotation for: secondary_structure + Seq('MAAGVKQLADDRTLLMAGVSHDLRTPLTRIRLATEMMSEQDGYLAESINKDIEE...YLR', IUPACProtein()) + + Finally, indexing with a simple integer is shorthand for pulling out + that letter from the sequence directly: + + >>> rec[5] + 'K' + >>> rec.seq[5] + 'K' + """ + if isinstance(index, int) : + #NOTE - The sequence level annotation like the id, name, etc + #do not really apply to a single character. However, should + #we try and expose any per-letter-annotation here? If so how? + return self.seq[index] + elif isinstance(index, slice) : + if self.seq is None : + raise ValueError("If the sequence is None, we cannot slice it.") + parent_length = len(self) + answer = self.__class__(self.seq[index], + id=self.id, + name=self.name, + description=self.description) + #TODO - The desription may no longer apply. + #It would be safer to change it to something + #generic like "edited" or the default value. + + #Don't copy the annotation dict and dbxefs list, + #they may not apply to a subsequence. + #answer.annotations = dict(self.annotations.iteritems()) + #answer.dbxrefs = self.dbxrefs[:] + + #TODO - Cope with strides by generating ambiguous locations? + if index.step is None or index.step == 1 : + #Select relevant features, add them with shifted locations + if index.start is None : + start = 0 + else : + start = index.start + if index.stop is None : + stop = -1 + else : + stop = index.stop + if (start < 0 or stop < 0) and parent_length == 0 : + raise ValueError, \ + "Cannot support negative indices without the sequence length" + if start < 0 : + start = parent_length - start + if stop < 0 : + stop = parent_length - stop + 1 + #assert str(self.seq)[index] == str(self.seq)[start:stop] + for f in self.features : + if start <= f.location.start.position \ + and f.location.end.position < stop : + answer.features.append(f._shift(-start)) + + #Slice all the values to match the sliced sequence + #(this should also work with strides, even negative strides): + for key, value in self.letter_annotations.iteritems() : + answer._per_letter_annotations[key] = value[index] + + return answer + raise ValueError, "Invalid index" + + def __iter__(self) : + """Iterate over the letters in the sequence. + + For example, using Bio.SeqIO to read in a protein FASTA file: + + >>> from Bio import SeqIO + >>> record = SeqIO.read(open("Amino/loveliesbleeding.pro"),"fasta") + >>> for amino in record : + ... print amino + ... if amino == "L" : break + X + A + G + L + >>> print record.seq[3] + L + + This is just a shortcut for iterating over the sequence directly: + + >>> for amino in record.seq : + ... print amino + ... if amino == "L" : break + X + A + G + L + >>> print record.seq[3] + L + + Note that this does not facilitate iteration together with any + per-letter-annotation. However, you can achieve that using the + python zip function on the record (or its sequence) and the relevant + per-letter-annotation: + + >>> from Bio import SeqIO + >>> rec = SeqIO.read(open("Quality/solexa.fastq", "rU"), + ... "fastq-solexa") + >>> print rec.id, rec.seq + slxa_0013_1_0001_24 ACAAAAATCACAAGCATTCTTATACACC + >>> print rec.letter_annotations.keys() + ['solexa_quality'] + >>> for nuc, qual in zip(rec,rec.letter_annotations["solexa_quality"]) : + ... if qual < -10 : + ... print nuc, qual + C -19 + C -27 + C -18 + + You may agree that using zip(rec.seq, ...) is more explicit than using + zip(rec, ...) as shown above. + """ + return iter(self.seq) + + def __str__(self) : + """A human readable summary of the record and its annotation (string). + + The python built in function str works by calling the object's ___str__ + method. e.g. + + >>> from Bio.Seq import Seq + >>> from Bio.SeqRecord import SeqRecord + >>> from Bio.Alphabet import IUPAC + >>> record = SeqRecord(Seq("MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF", + ... IUPAC.protein), + ... id="YP_025292.1", name="HokC", + ... description="toxic membrane protein, small") + >>> print str(record) + ID: YP_025292.1 + Name: HokC + Description: toxic membrane protein, small + Number of features: 0 + Seq('MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF', IUPACProtein()) + + In this example you don't actually need to call str explicity, as the + print command does this automatically: + + >>> print record + ID: YP_025292.1 + Name: HokC + Description: toxic membrane protein, small + Number of features: 0 + Seq('MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF', IUPACProtein()) + + Note that long sequences are shown truncated. + """ + lines = [] + if self.id : lines.append("ID: %s" % self.id) + if self.name : lines.append("Name: %s" % self.name) + if self.description : lines.append("Description: %s" % self.description) + if self.dbxrefs : lines.append("Database cross-references: " \ + + ", ".join(self.dbxrefs)) + lines.append("Number of features: %i" % len(self.features)) + for a in self.annotations: + lines.append("/%s=%s" % (a, str(self.annotations[a]))) + if self.letter_annotations : + lines.append("Per letter annotation for: " \ + + ", ".join(self.letter_annotations.keys())) + #Don't want to include the entire sequence, + #and showing the alphabet is useful: + lines.append(repr(self.seq)) + return "\n".join(lines) + + def __repr__(self) : + """A concise summary of the record for debugging (string). + + The python built in function repr works by calling the object's ___repr__ + method. e.g. + + >>> from Bio.Seq import Seq + >>> from Bio.SeqRecord import SeqRecord + >>> from Bio.Alphabet import generic_protein + >>> rec = SeqRecord(Seq("MASRGVNKVILVGNLGQDPEVRYMPNGGAVANITLATSESWRDKAT" + ... +"GEMKEQTEWHRVVLFGKLAEVASEYLRKGSQVYIEGQLRTRKWTDQ" + ... +"SGQDRYTTEVVVNVGGTMQMLGGRQGGGAPAGGNIGGGQPQGGWGQ" + ... +"PQQPQGGNQFSGGAQSRPQQSAPAAPSNEPPMDFDDDIPF", + ... generic_protein), + ... id="NP_418483.1", name="b4059", + ... description="ssDNA-binding protein", + ... dbxrefs=["ASAP:13298", "GI:16131885", "GeneID:948570"]) + >>> print repr(rec) + SeqRecord(seq=Seq('MASRGVNKVILVGNLGQDPEVRYMPNGGAVANITLATSESWRDKATGEMKEQTE...IPF', ProteinAlphabet()), id='NP_418483.1', name='b4059', description='ssDNA-binding protein', dbxrefs=['ASAP:13298', 'GI:16131885', 'GeneID:948570']) + + At the python prompt you can also use this shorthand: + + >>> rec + SeqRecord(seq=Seq('MASRGVNKVILVGNLGQDPEVRYMPNGGAVANITLATSESWRDKATGEMKEQTE...IPF', ProteinAlphabet()), id='NP_418483.1', name='b4059', description='ssDNA-binding protein', dbxrefs=['ASAP:13298', 'GI:16131885', 'GeneID:948570']) + + Note that long sequences are shown truncated. + """ + return self.__class__.__name__ \ + + "(seq=%s, id=%s, name=%s, description=%s, dbxrefs=%s)" \ + % tuple(map(repr, (self.seq, self.id, self.name, + self.description, self.dbxrefs))) + + def format(self, format) : + r"""Returns the record as a string in the specified file format. + + The format should be a lower case string supported as an output + format by Bio.SeqIO, which is used to turn the SeqRecord into a + string. e.g. + + >>> from Bio.Seq import Seq + >>> from Bio.SeqRecord import SeqRecord + >>> from Bio.Alphabet import IUPAC + >>> record = SeqRecord(Seq("MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF", + ... IUPAC.protein), + ... id="YP_025292.1", name="HokC", + ... description="toxic membrane protein") + >>> record.format("fasta") + '>YP_025292.1 toxic membrane protein\nMKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF\n' + >>> print record.format("fasta") + >YP_025292.1 toxic membrane protein + MKQHKAMIVALIVICITAVVAALVTRKDLCEVHIRTGQTEVAVF + + + The python print command automatically appends a new line, meaning + in this example a blank line is shown. If you look at the string + representation you can see there is a trailing new line (shown as + slash n) which is important when writing to a file or if + concatenating mutliple sequence strings together. + + Note that this method will NOT work on every possible file format + supported by Bio.SeqIO (e.g. some are for multiple sequences only). + """ + #See also the __format__ added for Python 2.6 / 3.0, PEP 3101 + #See also the Bio.Align.Generic.Alignment class and its format() + return self.__format__(format) + + def __format__(self, format_spec) : + """Returns the record as a string in the specified file format. + + This method supports the python format() function added in + Python 2.6/3.0. The format_spec should be a lower case + string supported by Bio.SeqIO as an output file format. + See also the SeqRecord's format() method. + """ + if format_spec: + from StringIO import StringIO + from Bio import SeqIO + handle = StringIO() + SeqIO.write([self], handle, format_spec) + return handle.getvalue() + else : + #Follow python convention and default to using __str__ + return str(self) + + def __len__(self) : + """Returns the length of the sequence. + + For example, using Bio.SeqIO to read in a FASTA nucleotide file: + + >>> from Bio import SeqIO + >>> record = SeqIO.read(open("Nucleic/sweetpea.nu"),"fasta") + >>> len(record) + 309 + >>> len(record.seq) + 309 + """ + return len(self.seq) + + def __nonzero__(self) : + """Returns True regardless of the length of the sequence. + + This behaviour is for backwards compatibility, since until the + __len__ method was added, a SeqRecord always evaluated as True. + + Note that in comparison, a Seq object will evaluate to False if it + has a zero length sequence. + + WARNING: The SeqRecord may in future evaluate to False when its + sequence is of zero length (in order to better match the Seq + object behaviour)! + """ + return True + +def _test(): + """Run the Bio.SeqRecord module's doctests (PRIVATE). + + This will try and locate the unit tests directory, and run the doctests + from there in order that the relative paths used in the examples work. + """ + import doctest + import os + if os.path.isdir(os.path.join("..","Tests")) : + print "Runing doctests..." + cur_dir = os.path.abspath(os.curdir) + os.chdir(os.path.join("..","Tests")) + doctest.testmod() + os.chdir(cur_dir) + del cur_dir + print "Done" + +if __name__ == "__main__": + _test() diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqRecord.pyc b/binaries/src/globplot/biopython-1.50/Bio/SeqRecord.pyc new file mode 100644 index 0000000000000000000000000000000000000000..714ba112f006daaa44736d898c1c57225ce91e18 GIT binary patch literal 24318 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 WA1@p~aqir?U#VZI>l3e?F8nt}tp>gT literal 0 HcmV?d00001 diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CheckSum.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CheckSum.py new file mode 100644 index 0000000..ae1a9d6 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CheckSum.py @@ -0,0 +1,124 @@ +# Copyright 2002 by Yves Bastide and Brad Chapman. +# All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Functions to calculate assorted sequence checksums.""" + +# crc32, crc64, gcg, and seguid +# crc64 is adapted from BioPerl + +from binascii import crc32 as _crc32 + +def crc32(seq) : + """Returns the crc32 checksum for a sequence (string or Seq object)""" + try : + #Assume its a Seq object + return _crc32(seq.tostring()) + except AttributeError : + #Assume its a string + return _crc32(seq) + +def _init_table_h(): + _table_h = [] + for i in range(256): + l = i + part_h = 0 + for j in range(8): + rflag = l & 1 + l >>= 1 + if part_h & 1: l |= (1L << 31) + part_h >>= 1L + if rflag: part_h ^= 0xd8000000L + _table_h.append(part_h) + return _table_h + +# Initialisation +_table_h = _init_table_h() + +def crc64(s): + """Returns the crc64 checksum for a sequence (string or Seq object)""" + crcl = 0 + crch = 0 + for c in s: + shr = (crch & 0xFF) << 24 + temp1h = crch >> 8 + temp1l = (crcl >> 8) | shr + idx = (crcl ^ ord(c)) & 0xFF + crch = temp1h ^ _table_h[idx] + crcl = temp1l + + return "CRC-%08X%08X" % (crch, crcl) + + +def gcg(seq): + """Returns the GCG checksum (int) for a sequence (string or Seq object) + + Given a nucleotide or amino-acid secuence (or any string), + returns the GCG checksum (int). Checksum used by GCG program. + seq type = str. + Based on BioPerl GCG_checksum. Adapted by Sebastian Bassi + with the help of John Lenton, Pablo Ziliani, and Gabriel Genellina. + All sequences are converted to uppercase """ + index = checksum = 0 + if type(seq)!=type("aa"): + seq=seq.tostring() + for char in seq: + index += 1 + checksum += index * ord(char.upper()) + if index == 57: index = 0 + return checksum % 10000 + +def seguid(seq): + """Returns the SEGUID (string) for a sequence (string or Seq object) + + Given a nucleotide or amino-acid secuence (or any string), + returns the SEGUID string (A SEquence Globally Unique IDentifier). + seq type = str. + For more information about SEGUID, see: + http://bioinformatics.anl.gov/seguid/ + DOI: 10.1002/pmic.200600032 """ + try: + #Python 2.5 sha1 is in hashlib + import hashlib + m = hashlib.sha1() + except: + #For older versions + import sha + m = sha.new() + import base64 + if type(seq)!=type("aa"): + seq=seq.tostring().upper() + else: + seq=seq.upper() + m.update(seq) + try: + #For Python 2.5 + return base64.b64encode(m.digest()).rstrip("=") + except: + #For older versions + import os + #Note: Using os.linesep doesn't work on Windows, + #where os.linesep= "\r\n" but the encoded string + #contains "\n" but not "\r\n" + return base64.encodestring(m.digest()).replace("\n","").rstrip("=") + +if __name__ == "__main__" : + print "Quick self test" + + str_light_chain_one = "QSALTQPASVSGSPGQSITISCTGTSSDVGSYNLVSWYQQHPGK" \ + + "APKLMIYEGSKRPSGVSNRFSGSKSGNTASLTISGLQAEDEADY" \ + + "YCSSYAGSSTLVFGGGTKLTVL" + + str_light_chain_two = "QSALTQPASVSGSPGQSITISCTGTSSDVGSYNLVSWYQQHPGK" \ + + "APKLMIYEGSKRPSGVSNRFSGSKSGNTASLTISGLQAEDEADY" \ + + "YCCSYAGSSTWVFGGGTKLTVL" + + assert crc64(str_light_chain_one) == crc64(str_light_chain_two) + assert 'CRC-44CAAD88706CC153' == crc64(str_light_chain_one) + + assert 'BpBeDdcNUYNsdk46JoJdw7Pd3BI' == seguid(str_light_chain_one) + assert 'X5XEaayob1nZLOc7eVT9qyczarY' == seguid(str_light_chain_two) + + print "Done" diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsage.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsage.py new file mode 100644 index 0000000..84e213a --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsage.py @@ -0,0 +1,153 @@ +import math +from CodonUsageIndices import SharpEcoliIndex +from Bio import SeqIO # To parse a FASTA file + +CodonsDict = {'TTT':0, 'TTC':0, 'TTA':0, 'TTG':0, 'CTT':0, +'CTC':0, 'CTA':0, 'CTG':0, 'ATT':0, 'ATC':0, +'ATA':0, 'ATG':0, 'GTT':0, 'GTC':0, 'GTA':0, +'GTG':0, 'TAT':0, 'TAC':0, 'TAA':0, 'TAG':0, +'CAT':0, 'CAC':0, 'CAA':0, 'CAG':0, 'AAT':0, +'AAC':0, 'AAA':0, 'AAG':0, 'GAT':0, 'GAC':0, +'GAA':0, 'GAG':0, 'TCT':0, 'TCC':0, 'TCA':0, +'TCG':0, 'CCT':0, 'CCC':0, 'CCA':0, 'CCG':0, +'ACT':0, 'ACC':0, 'ACA':0, 'ACG':0, 'GCT':0, +'GCC':0, 'GCA':0, 'GCG':0, 'TGT':0, 'TGC':0, +'TGA':0, 'TGG':0, 'CGT':0, 'CGC':0, 'CGA':0, +'CGG':0, 'AGT':0, 'AGC':0, 'AGA':0, 'AGG':0, +'GGT':0, 'GGC':0, 'GGA':0, 'GGG':0} + + +# this dictionary is used to know which codons encode the same AA. +SynonymousCodons = {'CYS': ['TGT', 'TGC'], 'ASP': ['GAT', 'GAC'], +'SER': ['TCT', 'TCG', 'TCA', 'TCC', 'AGC', 'AGT'], +'GLN': ['CAA', 'CAG'], 'MET': ['ATG'], 'ASN': ['AAC', 'AAT'], +'PRO': ['CCT', 'CCG', 'CCA', 'CCC'], 'LYS': ['AAG', 'AAA'], +'STOP': ['TAG', 'TGA', 'TAA'], 'THR': ['ACC', 'ACA', 'ACG', 'ACT'], +'PHE': ['TTT', 'TTC'], 'ALA': ['GCA', 'GCC', 'GCG', 'GCT'], +'GLY': ['GGT', 'GGG', 'GGA', 'GGC'], 'ILE': ['ATC', 'ATA', 'ATT'], +'LEU': ['TTA', 'TTG', 'CTC', 'CTT', 'CTG', 'CTA'], 'HIS': ['CAT', 'CAC'], +'ARG': ['CGA', 'CGC', 'CGG', 'CGT', 'AGG', 'AGA'], 'TRP': ['TGG'], +'VAL': ['GTA', 'GTC', 'GTG', 'GTT'], 'GLU': ['GAG', 'GAA'], 'TYR': ['TAT', 'TAC']} + + +class CodonAdaptationIndex: + """A codon adaptaion index (CAI) implementation. + + This class implements the codon adaptaion index (CAI) described by Sharp and + Li (Nucleic Acids Res. 1987 Feb 11;15(3):1281-95). + + methods: + + set_cai_index(Index): + + This method sets-up an index to be used when calculating CAI for a gene. + Just pass a dictionary similar to the SharpEcoliIndex in CodonUsageIndices + module. + + generate_index(FastaFile): + + This method takes a location of a FastaFile and generates an index. This + index can later be used to calculate CAI of a gene. + + cai_for_gene(DNAsequence): + + This method uses the Index (either the one you set or the one you generated) + and returns the CAI for the DNA sequence. + + print_index(): + This method prints out the index you used. + + NOTE - This implementation does not currently cope with alternative genetic + codes, only the synonymous codons in the standard table are considered. + """ + def __init__(self): + self.index = {} + self.codon_count={} + + # use this method with predefined CAI index + def set_cai_index(self, Index): + self.index = Index + + def generate_index(self, FastaFile): + """Generate a codon usage index from a FASTA file of CDS sequences. + + This method takes a location of a Fasta file containing CDS sequences + (which must all have a whole number of codons) and generates a codon + usage index. This index can later be used to calculate CAI of a gene. + """ + # first make sure i am not overwriting an existing index: + if self.index != {} or self.codon_count!={}: + raise ValueError("an index has already been set or a codon count has been done. cannot overwrite either.") + # count codon occurances in the file. + self._count_codons(FastaFile) + + # now to calculate the index we first need to sum the number of times + # synonymous codons were used all together. + for AA in SynonymousCodons.keys(): + Sum=0.0 + RCSU=[] # RCSU values are equal to CodonCount/((1/num of synonymous codons) * sum of all synonymous codons) + + for codon in SynonymousCodons[AA]: + Sum += self.codon_count[codon] + # calculate the RSCU value for each of the codons + for codon in SynonymousCodons[AA]: + RCSU.append(self.codon_count[codon]/((1.0/len(SynonymousCodons[AA]))*Sum)) + # now generate the index W=RCSUi/RCSUmax: + RCSUmax = max(RCSU) + for i in range(len(SynonymousCodons[AA])): + self.index[SynonymousCodons[AA][i]]= RCSU[i]/RCSUmax + + + def cai_for_gene(self, DNAsequence): + """Calculate the CAI (float) for the provided DNA sequence (string). + + This method uses the Index (either the one you set or the one you generated) + and returns the CAI for the DNA sequence. + """ + caiValue = 0 + LengthForCai = 0 + # if no index is set or generated, the default SharpEcoliIndex will be used. + if self.index=={}: + self.set_cai_index(SharpEcoliIndex) + + if DNAsequence.islower(): + DNAsequence = DNAsequence.upper() + for i in range (0,len(DNAsequence),3): + codon = DNAsequence[i:i+3] + if codon in self.index: + if codon!='ATG' and codon!= 'TGG': #these two codons are always one, exclude them. + caiValue += math.log(self.index[codon]) + LengthForCai += 1 + elif codon not in ['TGA','TAA', 'TAG']: # some indices you will use may not include stop codons. + raise TypeError("illegal codon in sequence: %s.\n%s" % (codon, self.index)) + return math.exp(caiValue*(1.0/(LengthForCai-1))) + + def _count_codons(self, FastaFile): + handle = open(FastaFile, 'r') + + # make the codon dictionary local + self.codon_count = CodonsDict.copy() + + # iterate over sequence and count all the codons in the FastaFile. + for cur_record in SeqIO.parse(handle, "fasta") : + # make sure the sequence is lower case + if str(cur_record.seq).islower(): + DNAsequence = str(cur_record.seq).upper() + else: + DNAsequence = str(cur_record.seq) + for i in range(0,len(DNAsequence),3): + codon = DNAsequence[i:i+3] + if codon in self.codon_count: + self.codon_count[codon] += 1 + else: + raise TypeError("illegal codon %s in gene: %s" % (codon, cur_record.id)) + handle.close() + + # this just gives the index when the objects is printed. + def print_index (self): + """This method prints out the index you used.""" + X=self.index.keys() + X.sort() + for i in X: + print "%s\t%.3f" %(i, self.index[i]) + diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsageIndices.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsageIndices.py new file mode 100644 index 0000000..29c9756 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/CodonUsageIndices.py @@ -0,0 +1,14 @@ +# Copyright Yair Benita Y.Benita@pharm.uu.nl +# Biopython (http://biopython.org) license applies + +# sharp Ecoli index for codon adaption index. +# from Sharp & Li, Nucleic Acids Res. 1987 +SharpEcoliIndex = { +'GCA':0.586, 'GCC':0.122, 'GCG':0.424, 'GCT':1, 'AGA':0.004, 'AGG':0.002, 'CGA':0.004, +'CGC':0.356, 'CGG':0.004, 'CGT':1, 'AAC':1, 'AAT':0.051, 'GAC':1, 'GAT':0.434, 'TGC':1, +'TGT':0.5, 'CAA':0.124, 'CAG':1, 'GAA':1, 'GAG':0.259, 'GGA':0.01, 'GGC':0.724, 'GGG':0.019, +'GGT':1, 'CAC':1, 'CAT':0.291, 'ATA':0.003, 'ATC':1, 'ATT':0.185, 'CTA':0.007, 'CTC':0.037, +'CTG':1, 'CTT':0.042, 'TTA':0.02, 'TTG':0.02, 'AAA':1, 'AAG':0.253, 'ATG':1, 'TTC':1, 'TTT':0.296, +'CCA':0.135, 'CCC':0.012, 'CCG':1, 'CCT':0.07, 'AGC':0.41, 'AGT':0.085, 'TCA':0.077, 'TCC':0.744, +'TCG':0.017, 'TCT':1, 'ACA':0.076, 'ACC':1,'ACG':0.099, 'ACT':0.965, 'TGG':1, 'TAC':1, 'TAT':0.239, +'GTA':0.495, 'GTC':0.066,'GTG':0.221, 'GTT':1} diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/IsoelectricPoint.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/IsoelectricPoint.py new file mode 100644 index 0000000..d53ee46 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/IsoelectricPoint.py @@ -0,0 +1,114 @@ +# Copyright Yair Benita Y.Benita@pharm.uu.nl +# Biopython (http://biopython.org) license applies + +"""Calculate isoelectric points of polypeptides using methods of Bjellqvist. + +pK values and the methos are taken from: + +* Bjellqvist, B.,Hughes, G.J., Pasquali, Ch., Paquet, N., Ravier, F., Sanchez, +J.-Ch., Frutiger, S. & Hochstrasser, D.F. +The focusing positions of polypeptides in immobilized pH gradients can be predicted +from their amino acid sequences. Electrophoresis 1993, 14, 1023-1031. + +* Bjellqvist, B., Basse, B., Olsen, E. and Celis, J.E. +Reference points for comparisons of two-dimensional maps of proteins from +different human cell types defined in a pH scale where isoelectric points correlate +with polypeptide compositions. Electrophoresis 1994, 15, 529-539. + +I designed the algorithm according to a note by David L. Tabb, available at: +http://fields.scripps.edu/DTASelect/20010710-pI-Algorithm.pdf + +""" + +positive_pKs = { 'Nterm': 7.5, 'K': 10.0, 'R': 12.0, 'H':5.98 } +negative_pKs = { 'Cterm': 3.55, 'D': 4.05, 'E': 4.45, 'C':9.0, 'Y':10.0 } +pKcterminal= {'D':4.55, 'E':4.75} +pKnterminal = {'A':7.59, 'M':7.0, 'S':6.93, 'P':8.36, 'T':6.82, 'V':7.44, 'E':7.7} +charged_aas = ('K', 'R', 'H', 'D', 'E', 'C', 'Y') + +# access this module through ProtParam.ProteinAnalysis class. +# first make a ProteinAnalysis object and then call its isoelectric_point method. +class IsoelectricPoint: + def __init__(self, ProteinSequence, AminoAcidsContent): + self.sequence = ProteinSequence + self.charged_aas_content = self._select_charged(AminoAcidsContent) + + # This function creates a dictionary with the contents of each charged aa, + # plus Cterm and Nterm. + def _select_charged(self, AminoAcidsContent): + charged = {} + for aa in charged_aas: + charged[aa] = float(AminoAcidsContent[aa]) + charged['Nterm'] = 1.0 + charged['Cterm'] = 1.0 + return charged + + #This function calculates the total charge of the protein at a given pH. + def _chargeR(self, pH, pos_pKs, neg_pKs): + PositiveCharge = 0.0 + for aa, pK in pos_pKs.iteritems(): + CR = 10**(pK-pH) + partial_charge = CR/(CR+1.0) + PositiveCharge += self.charged_aas_content[aa] * partial_charge + + NegativeCharge = 0.0 + for aa, pK in neg_pKs.iteritems(): + CR = 10**(pH-pK) + partial_charge = CR/(CR+1.0) + NegativeCharge += self.charged_aas_content[aa] * partial_charge + + return PositiveCharge - NegativeCharge + + # This is the action function, it tries different pH until the charge of the protein is 0 (or close). + def pi(self): + pos_pKs = dict(positive_pKs) + neg_pKs = dict(negative_pKs) + nterm = self.sequence[0] + cterm = self.sequence[-1] + if nterm in pKnterminal.keys(): + pos_pKs['Nterm'] = pKnterminal[nterm] + if cterm in pKcterminal.keys(): + neg_pKs['Cterm'] = pKcterminal[cterm] + + # Bracket between pH1 and pH2 + pH = 7.0 + Charge = self._chargeR(pH, pos_pKs, neg_pKs) + if Charge > 0.0: + pH1 = pH + Charge1 = Charge + while Charge1 > 0.0: + pH = pH1 + 1.0 + Charge = self._chargeR(pH, pos_pKs, neg_pKs) + if Charge > 0.0: + pH1 = pH + Charge1 = Charge + else: + pH2 = pH + Charge2 = Charge + break + else: + pH2 = pH + Charge2 = Charge + while Charge2 < 0.0: + pH = pH2 - 1.0 + Charge = self._chargeR(pH, pos_pKs, neg_pKs) + if Charge < 0.0: + pH2 = pH + Charge2 = Charge + else: + pH1 = pH + Charge1 = Charge + break + + # Bisection + while pH2 - pH1 > 0.0001 and Charge!=0.0: + pH = (pH1 + pH2) / 2.0 + Charge = self._chargeR(pH, pos_pKs, neg_pKs) + if Charge > 0.0: + pH1 = pH + Charge1 = Charge + else: + pH2 = pH + Charge2 = Charge + + return pH diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/MeltingTemp.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/MeltingTemp.py new file mode 100644 index 0000000..d734ac2 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/MeltingTemp.py @@ -0,0 +1,156 @@ +# Copyright 2004-2008 by Sebastian Bassi. +# All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Calculate the thermodynamic melting temperatures of nucleotide sequences.""" + +import math +def Tm_staluc(s,dnac=50,saltc=50,rna=0): + """Returns DNA/DNA tm using nearest neighbor thermodynamics. + + dnac is DNA concentration [nM] + saltc is salt concentration [mM]. + rna=0 is for DNA/DNA (default), for RNA, rna should be 1. + + Sebastian Bassi """ + + #Credits: + #Main author: Sebastian Bassi + #Overcount function: Greg Singer + #Based on the work of Nicolas Le Novere Bioinformatics. + #17:1226-1227(2001) + + #This function returns better results than EMBOSS DAN because it uses + #updated thermodynamics values and takes into account inicialization + #parameters from the work of SantaLucia (1998). + + #Things to do: + #+Detect complementary sequences. Change K according to result. + #+Add support for heteroduplex (see Sugimoto et al. 1995). + #+Correction for Mg2+. Now supports only monovalent ions. + #+Put thermodinamics table in a external file for users to change at will + #+Add support for danglings ends (see Le Novele. 2001) and mismatches. + + dh = 0 #DeltaH. Enthalpy + ds = 0 #deltaS Entropy + + def tercorr(stri): + deltah = 0 + deltas = 0 + if rna==0: + #DNA/DNA + #Allawi and SantaLucia (1997). Biochemistry 36 : 10581-10594 + if stri.startswith('G') or stri.startswith('C'): + deltah -= 0.1 + deltas += 2.8 + elif stri.startswith('A') or stri.startswith('T'): + deltah -= 2.3 + deltas -= 4.1 + if stri.endswith('G') or stri.endswith('C'): + deltah -= 0.1 + deltas += 2.8 + elif stri.endswith('A') or stri.endswith('T'): + deltah -= 2.3 + deltas -= 4.1 + dhL = dh + deltah + dsL = ds + deltas + return dsL,dhL + elif rna==1: + #RNA + if stri.startswith('G') or stri.startswith('C'): + deltah -= 3.61 + deltas -= 1.5 + elif stri.startswith('A') or stri.startswith('T') or \ + stri.startswith('U'): + deltah -= 3.72 + deltas += 10.5 + if stri.endswith('G') or stri.endswith('C'): + deltah -= 3.61 + deltas -= 1.5 + elif stri.endswith('A') or stri.endswith('T') or \ + stri.endswith('U'): + deltah -= 3.72 + deltas += 10.5 + dhL = dh + deltah + dsL = ds + deltas + # print "delta h=",dhL + return dsL,dhL + + def overcount(st,p): + """Returns how many p are on st, works even for overlapping""" + ocu = 0 + x = 0 + while 1: + try: + i = st.index(p,x) + except ValueError: + break + ocu += 1 + x = i + 1 + return ocu + + R = 1.987 # universal gas constant in Cal/degrees C*Mol + sup = s.upper() + vsTC,vh = tercorr(sup) + vs = vsTC + + k = (dnac/4.0)*1e-9 + #With complementary check on, the 4.0 should be changed to a variable. + + if rna==0: + #DNA/DNA + #Allawi and SantaLucia (1997). Biochemistry 36 : 10581-10594 + vh = vh + (overcount(sup,"AA"))*7.9 + (overcount(sup,"TT"))*\ + 7.9 + (overcount(sup,"AT"))*7.2 + (overcount(sup,"TA"))*7.2 \ + + (overcount(sup,"CA"))*8.5 + (overcount(sup,"TG"))*8.5 + \ + (overcount(sup,"GT"))*8.4 + (overcount(sup,"AC"))*8.4 + vh = vh + (overcount(sup,"CT"))*7.8+(overcount(sup,"AG"))*\ + 7.8 + (overcount(sup,"GA"))*8.2 + (overcount(sup,"TC"))*8.2 + vh = vh + (overcount(sup,"CG"))*10.6+(overcount(sup,"GC"))*\ + 9.8 + (overcount(sup,"GG"))*8 + (overcount(sup,"CC"))*8 + vs = vs + (overcount(sup,"AA"))*22.2+(overcount(sup,"TT"))*\ + 22.2 + (overcount(sup,"AT"))*20.4 + (overcount(sup,"TA"))*21.3 + vs = vs + (overcount(sup,"CA"))*22.7+(overcount(sup,"TG"))*\ + 22.7 + (overcount(sup,"GT"))*22.4 + (overcount(sup,"AC"))*22.4 + vs = vs + (overcount(sup,"CT"))*21.0+(overcount(sup,"AG"))*\ + 21.0 + (overcount(sup,"GA"))*22.2 + (overcount(sup,"TC"))*22.2 + vs = vs + (overcount(sup,"CG"))*27.2+(overcount(sup,"GC"))*\ + 24.4 + (overcount(sup,"GG"))*19.9 + (overcount(sup,"CC"))*19.9 + ds = vs + dh = vh + + else: + #RNA/RNA hybridisation of Xia et al (1998) + #Biochemistry 37: 14719-14735 + vh = vh+(overcount(sup,"AA"))*6.82+(overcount(sup,"TT"))*6.6+\ + (overcount(sup,"AT"))*9.38 + (overcount(sup,"TA"))*7.69+\ + (overcount(sup,"CA"))*10.44 + (overcount(sup,"TG"))*10.5+\ + (overcount(sup,"GT"))*11.4 + (overcount(sup,"AC"))*10.2 + vh = vh + (overcount(sup,"CT"))*10.48 + (overcount(sup,"AG"))\ + *7.6+(overcount(sup,"GA"))*12.44+(overcount(sup,"TC"))*13.3 + vh = vh + (overcount(sup,"CG"))*10.64 + (overcount(sup,"GC"))\ + *14.88+(overcount(sup,"GG"))*13.39+(overcount(sup,"CC"))*12.2 + vs = vs + (overcount(sup,"AA"))*19.0 + (overcount(sup,"TT"))*\ + 18.4+(overcount(sup,"AT"))*26.7+(overcount(sup,"TA"))*20.5 + vs = vs + (overcount(sup,"CA"))*26.9 + (overcount(sup,"TG"))*\ + 27.8 + (overcount(sup,"GT"))*29.5 + (overcount(sup,"AC"))*26.2 + vs = vs + (overcount(sup,"CT"))*27.1 + (overcount(sup,"AG"))*\ + 19.2 + (overcount(sup,"GA"))*32.5 + (overcount(sup,"TC"))*35.5 + vs = vs + (overcount(sup,"CG"))*26.7 + (overcount(sup,"GC"))\ + *36.9 + (overcount(sup,"GG"))*32.7 + (overcount(sup,"CC"))*29.7 + ds = vs + dh = vh + + ds = ds-0.368*(len(s)-1)*math.log(saltc/1e3) + tm = ((1000* (-dh))/(-ds+(R * (math.log(k)))))-273.15 + # print "ds="+str(ds) + # print "dh="+str(dh) + return tm + +if __name__ == "__main__" : + print "Quick self test" + assert Tm_staluc('CAGTCAGTACGTACGTGTACTGCCGTA') == 59.865612727457972 + assert Tm_staluc('CAGTCAGTACGTACGTGTACTGCCGTA',rna=1) == 68.141611264576682 + print "Done" diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParam.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParam.py new file mode 100644 index 0000000..d0b9ae1 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParam.py @@ -0,0 +1,251 @@ +# Copyright Yair Benita Y.Benita@pharm.uu.nl +# Biopython (http://biopython.org) license applies + +import sys +import ProtParamData, IsoelectricPoint +from ProtParamData import kd # Added by Iddo to enable the gravy method +from Bio.Seq import Seq +from Bio.Alphabet import IUPAC +from Bio.Data import IUPACData +#from BioModule import + +class ProteinAnalysis: + """ + This class contains methods for protein analysis. The class init method takes + only one argument, the protein sequence as a string and build a sequence + object using the Bio.Seq module. This is done just to make sure the sequence + is a protein sequence and not anything else. + + methods: + + count_amino_acids: + + Simply counts the number times an amino acid is repeated in the protein + sequence. Returns a dictionary {AminoAcid:Number} and also stores the + dictionary in self.amino_acids_content. + + get_amino_acids_percent: + + The same as count_amino_acids only returns the Number in percentage of entire + sequence. Returns a dictionary and stores the dictionary in + self.amino_acids_content_percent. + + molecular_weight: + Calculates the molecular weight of a protein. + + aromaticity: + + Calculates the aromaticity value of a protein according to Lobry, 1994. It is + simply the relative frequency of Phe+Trp+Tyr. + + + instability_index: + + Implementation of the method of Guruprasad et al. (Protein Engineering + 4:155-161,1990). This method tests a protein for stability. Any value above 40 + means the protein is unstable (=has a short half life). + + flexibility: + Implementation of the flexibility method of Vihinen et al. (Proteins. 1994 Jun;19(2):141-9). + + isoelectric_point: + This method uses the module IsoelectricPoint to calculate the pI of a protein. + + secondary_structure_fraction: + This methods returns a list of the fraction of amino acids which tend to be in Helix, Turn or Sheet. + Amino acids in helix: V, I, Y, F, W, L. + Amino acids in Turn: N, P, G, S. + Amino acids in sheet: E, M, A, L. + The list contains 3 values: [Helix, Turn, Sheet]. + + + protein_scale(Scale, WindwonSize, Edge): + + An amino acid scale is defined by a numerical value assigned to each type of + amino acid. The most frequently used scales are the hydrophobicity or + hydrophilicity scales and the secondary structure conformational parameters + scales, but many other scales exist which are based on different chemical and + physical properties of the amino acids. You can set several parameters that + control the computation of a scale profile, such as the window size and the + window edge relative weight value. WindowSize: The window size is the length + of the interval to use for the profile computation. For a window size n, we + use the i- ( n-1)/2 neighboring residues on each side of residue it compute + the score for residue i. The score for residue is the sum of the scale values + for these amino acids, optionally weighted according to their position in the + window. Edge: The central amino acid of the window always has a weight of 1. + By default, the amino acids at the remaining window positions have the same + weight, but you can make the residue at the center of the window have a + larger weight than the others by setting the edge value for the residues at + the beginning and end of the interval to a value between 0 and 1. For + instance, for Edge=0.4 and a window size of 5 the weights will be: 0.4, 0.7, + 1.0, 0.7, 0.4. The method returns a list of values which can be plotted to + view the change along a protein sequence. Many scales exist. Just add your + favorites to the ProtParamData modules. + """ + def __init__(self, ProtSequence): + if ProtSequence.islower(): + self.sequence = Seq(ProtSequence.upper(), IUPAC.protein) + else: + self.sequence = Seq(ProtSequence, IUPAC.protein) + self.amino_acids_content = None + self.amino_acids_percent = None + self.length = len(self.sequence) + + def count_amino_acids(self): + ProtDic = dict([ (k, 0) for k in IUPACData.protein_letters]) + for i in ProtDic.keys(): + ProtDic[i]=self.sequence.count(i) + self.amino_acids_content = ProtDic + return ProtDic + + """Calculate the amino acid content in percents. + input is the dictionary from CountAA. + output is a dictionary with AA as keys.""" + def get_amino_acids_percent(self): + if not self.amino_acids_content: + self.count_amino_acids() + + PercentAA = {} + for i in self.amino_acids_content.keys(): + if self.amino_acids_content[i] > 0: + PercentAA[i]=self.amino_acids_content[i]/float(self.length) + else: + PercentAA[i] = 0 + self.amino_acids_percent = PercentAA + return PercentAA + + # Calculate MW from Protein sequence + # Calculate MW from Protein sequence + def molecular_weight (self): + # make local dictionary for speed + MwDict = {} + # remove a molecule of water from the amino acid weight. + for i in IUPACData.protein_weights.keys(): + MwDict[i] = IUPACData.protein_weights[i] - 18.02 + MW = 18.02 # add just one water molecule for the whole sequence. + for i in self.sequence: + MW += MwDict[i] + return MW + + # calculate the aromaticity according to Lobry, 1994. + # Arom=sum of relative frequency of Phe+Trp+Tyr + def aromaticity(self): + if not self.amino_acids_percent: + self.get_amino_acids_percent() + + Arom= self.amino_acids_percent['Y']+self.amino_acids_percent['W']+self.amino_acids_percent['F'] + return Arom + + # a function to calculate the instability index according to: + # Guruprasad K., Reddy B.V.B., Pandit M.W. Protein Engineering 4:155-161(1990). + def instability_index(self): + #make the dictionary local for speed. + DIWV=ProtParamData.DIWV.copy() + score=0.0 + for i in range(self.length - 1): + DiPeptide=DIWV[self.sequence[i]][self.sequence[i+1]] + score += DiPeptide + return (10.0/self.length) * score + + # Calculate the flexibility according to Vihinen, 1994. + # No argument to change window size because parameters are specific for a window=9. + # the parameters used are optimized for determining the flexibility. + def flexibility(self): + Flex = ProtParamData.Flex.copy() + Window=9 + Weights=[0.25,0.4375,0.625,0.8125,1] + List=[] + for i in range(self.length - Window): + SubSeq=self.sequence[i:i+Window] + score = 0.0 + for j in range(Window/2): + score += (Flex[SubSeq[j]]+Flex[SubSeq[Window-j-1]]) * Weights[j] + score += Flex[SubSeq[Window/2+1]] + List.append(score/5.25) + return List + + # calculate the gravy according to kyte and doolittle. + def gravy(self): + ProtGravy=0.0 + for i in self.sequence: + ProtGravy += kd[i] + + return ProtGravy/self.length + + # this method is used to make a list of relative weight of the + # window edges compared to the window center. The weights are linear. + # it actually generates half a list. For a window of size 9 and edge 0.4 + # you get a list of [0.4, 0.55, 0.7, 0.85]. + def _weight_list(self, window, edge): + unit = ((1.0-edge)/(window-1))*2 + list = [0.0]*(window/2) + for i in range(window/2): + list[i] = edge + unit * i + return list + + # this method allows you to compute and represent the profile produced + # by any amino acid scale on a selected protein. + # Similar to expasy's ProtScale: http://www.expasy.org/cgi-bin/protscale.pl + # The weight list returns only one tail. If the list should be [0.4,0.7,1.0,0.7,0.4] + # what you actually get from _weights_list is [0.4,0.7]. The correct calculation is done + # in the loop. + def protein_scale(self, ParamDict, Window, Edge=1.0): + # generate the weights + weight = self._weight_list(Window,Edge) + list = [] + # the score in each Window is divided by the sum of weights + sum_of_weights = 0.0 + for i in weight: sum_of_weights += i + # since the weight list is one sided: + sum_of_weights = sum_of_weights*2+1 + + for i in range(self.length-Window+1): + subsequence = self.sequence[i:i+Window] + score = 0.0 + for j in range(Window/2): + # walk from the outside of the Window towards the middle. + # Iddo: try/except clauses added to avoid raising an exception on a non-standad amino acid + try: + score += weight[j] * ParamDict[subsequence[j]] + weight[j] * ParamDict[subsequence[Window-j-1]] + except KeyError: + sys.stderr.write('warning: %s or %s is not a standard amino acid.\n' % + (subsequence[j],subsequence[Window-j-1])) + + # Now add the middle value, which always has a weight of 1. + if subsequence[Window/2] in ParamDict: + score += ParamDict[subsequence[Window/2]] + else: + sys.stderr.write('warning: %s is not a standard amino acid.\n' % (subsequence[Window/2])) + + list.append(score/sum_of_weights) + return list + + # calculate the isoelectric point. + def isoelectric_point(self): + if not self.amino_acids_content: + self.count_amino_acids() + X = IsoelectricPoint.IsoelectricPoint(self.sequence, self.amino_acids_content) + return X.pi() + + # calculate fraction of helix, turn and sheet + def secondary_structure_fraction (self): + if not self.amino_acids_percent: + self.get_amino_acids_percent() + Helix = self.amino_acids_percent['V'] + self.amino_acids_percent['I'] + self.amino_acids_percent['Y'] + self.amino_acids_percent['F'] + self.amino_acids_percent['W'] + self.amino_acids_percent['L'] + Turn = self.amino_acids_percent['N'] + self.amino_acids_percent['P'] + self.amino_acids_percent['G'] + self.amino_acids_percent['S'] + Sheet = self.amino_acids_percent['E'] + self.amino_acids_percent['M'] + self.amino_acids_percent['A'] + self.amino_acids_percent['L'] + return Helix, Turn, Sheet + +#---------------------------------------------------------# +""" +X = ProteinAnalysis("MAEGEITTFTALTEKFNLPPGNYKKPKLLYCSNGGHFLRILPDGTVDGTRDRSDQHIQLQLSAESVGEVYIKSTETGQYLAMDTSGLLYGSQTPSEECLFLERLEENHYNTYTSKKHAEKNWFVGLKKNGSCKRGPRTHYGQKAILFLPLPV") +print X.count_amino_acids() +print X.get_amino_acids_percent() +print X.molecular_weight() +print X.aromaticity() +print X.instability_index() +print X.flexibility() +print X.pi() +print X.secondary_structure_fraction() +print X.protein_scale(ProtParamData.kd, 9, 0.4) +""" diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParamData.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParamData.py new file mode 100644 index 0000000..6a52e8f --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/ProtParamData.py @@ -0,0 +1,43 @@ +# This module contains indices to be used with ProtParam + +# Kyte & Doolittle index of hydrophobicity +kd = { 'A': 1.8,'R':-4.5,'N':-3.5,'D':-3.5,'C': 2.5, + 'Q':-3.5,'E':-3.5,'G':-0.4,'H':-3.2,'I': 4.5, + 'L': 3.8,'K':-3.9,'M': 1.9,'F': 2.8,'P':-1.6, + 'S':-0.8,'T':-0.7,'W':-0.9,'Y':-1.3,'V': 4.2 } + +# Flexibility +# Normalized flexibility parameters (B-values), average (Vihinen et al., 1994) +Flex= {'A': 0.984, 'C': 0.906, 'E': 1.094, 'D': 1.068, + 'G': 1.031, 'F': 0.915, 'I': 0.927, 'H': 0.950, + 'K': 1.102, 'M': 0.952, 'L': 0.935, 'N': 1.048, + 'Q': 1.037, 'P': 1.049, 'S': 1.046, 'R': 1.008, + 'T': 0.997, 'W': 0.904, 'V': 0.931, 'Y': 0.929} + +# Hydrophilicity +# 1 Hopp & Wood +# Proc. Natl. Acad. Sci. U.S.A. 78:3824-3828(1981). +hw = { 'A':-0.5,'R':3.0, 'N':0.2, 'D':3.0, 'C':-1.0, + 'Q':0.2, 'E':3.0, 'G':0.0, 'H':-0.5,'I':-1.8, + 'L':-1.8,'K':3.0, 'M':-1.3,'F':-2.5,'P':0.0, + 'S':0.3, 'T':-0.4,'W':-3.4,'Y':-2.3,'V':-1.5 } + +# Surface accessibility +# 1 Emini Surface fractional probability +em = { 'A':0.815,'R':1.475,'N':1.296,'D':1.283,'C':0.394, + 'Q':1.348,'E':1.445,'G':0.714,'H':1.180,'I':0.603, + 'L':0.603,'K':1.545,'M':0.714,'F':0.695,'P':1.236, + 'S':1.115,'T':1.184,'W':0.808,'Y':1.089,'V':0.606 } + +# 2 Janin Interior to surface transfer energy scale +ja = { 'A': 0.28,'R':-1.14,'N':-0.55,'D':-0.52,'C': 0.97, + 'Q':-0.69,'E':-1.01,'G': 0.43,'H':-0.31,'I': 0.60, + 'L': 0.60,'K':-1.62,'M': 0.43,'F': 0.46,'P':-0.42, + 'S':-0.19,'T':-0.32,'W': 0.29,'Y':-0.15,'V': 0.60 } + + +# A two dimentional dictionary for calculating the instability index. +# Guruprasad K., Reddy B.V.B., Pandit M.W. Protein Engineering 4:155-161(1990). +# It is based on dipeptide values therefore the vale for the dipeptide DG is DIWV['D']['G']. +# I know this looks ugly but i can't think of a better way to display it. +DIWV = {'A': {'A': 1.0, 'C': 44.94, 'E': 1.0, 'D': -7.49, 'G': 1.0, 'F': 1.0, 'I': 1.0, 'H': -7.49, 'K': 1.0, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 1.0, 'P': 20.26, 'S': 1.0, 'R': 1.0, 'T': 1.0, 'W': 1.0, 'V': 1.0, 'Y': 1.0},'C': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': 20.26, 'G': 1.0, 'F': 1.0, 'I': 1.0, 'H': 33.60, 'K': 1.0, 'M': 33.60, 'L': 20.26, 'N': 1.0, 'Q': -6.54, 'P': 20.26, 'S': 1.0, 'R': 1.0, 'T': 33.60, 'W': 24.68, 'V': -6.54, 'Y': 1.0},'E': {'A': 1.0, 'C': 44.94, 'E': 33.60, 'D': 20.26, 'G': 1.0, 'F': 1.0, 'I': 20.26, 'H': -6.54, 'K': 1.0, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 20.26, 'P': 20.26, 'S': 20.26, 'R': 1.0, 'T': 1.0, 'W': -14.03, 'V': 1.0, 'Y': 1.0}, 'D': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': 1.0, 'G': 1.0, 'F': -6.54, 'I': 1.0, 'H': 1.0, 'K': -7.49, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 1.0, 'P': 1.0, 'S': 20.26, 'R': -6.54, 'T': -14.03, 'W': 1.0, 'V': 1.0, 'Y': 1.0}, 'G': {'A': -7.49, 'C': 1.0, 'E': -6.54, 'D': 1.0, 'G': 13.34, 'F': 1.0, 'I': -7.49, 'H': 1.0, 'K': -7.49, 'M': 1.0, 'L': 1.0, 'N': -7.49, 'Q': 1.0, 'P': 1.0, 'S': 1.0, 'R': 1.0, 'T': -7.49, 'W': 13.34, 'V': 1.0, 'Y': -7.49}, 'F': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': 13.34, 'G': 1.0, 'F': 1.0, 'I': 1.0, 'H': 1.0, 'K': -14.03, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 1.0, 'P': 20.26, 'S': 1.0, 'R': 1.0, 'T': 1.0, 'W': 1.0, 'V': 1.0, 'Y': 33.601}, 'I': {'A': 1.0, 'C': 1.0, 'E': 44.94, 'D': 1.0, 'G': 1.0, 'F': 1.0, 'I': 1.0, 'H': 13.34, 'K': -7.49, 'M': 1.0, 'L': 20.26, 'N': 1.0, 'Q': 1.0, 'P': -1.88, 'S': 1.0, 'R': 1.0, 'T': 1.0, 'W': 1.0, 'V': -7.49, 'Y': 1.0}, 'H': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': 1.0, 'G': -9.37, 'F': -9.37, 'I': 44.94, 'H': 1.0, 'K': 24.68, 'M': 1.0, 'L': 1.0, 'N': 24.68, 'Q': 1.0, 'P': -1.88, 'S': 1.0, 'R': 1.0, 'T': -6.54, 'W': -1.88, 'V': 1.0, 'Y': 44.94}, 'K': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': 1.0, 'G': -7.49, 'F': 1.0, 'I': -7.49, 'H': 1.0, 'K': 1.0, 'M': 33.60, 'L': -7.49, 'N': 1.0, 'Q': 24.64, 'P': -6.54, 'S': 1.0, 'R': 33.60, 'T': 1.0, 'W': 1.0, 'V': -7.49, 'Y': 1.0}, 'M': {'A': 13.34, 'C': 1.0, 'E': 1.0, 'D': 1.0, 'G': 1.0, 'F': 1.0, 'I': 1.0, 'H': 58.28, 'K': 1.0, 'M': -1.88, 'L': 1.0, 'N': 1.0, 'Q': -6.54, 'P': 44.94, 'S': 44.94, 'R': -6.54, 'T': -1.88, 'W': 1.0, 'V': 1.0, 'Y': 24.68}, 'L': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': 1.0, 'G': 1.0, 'F': 1.0, 'I': 1.0, 'H': 1.0, 'K': -7.49, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 33.60, 'P': 20.26, 'S': 1.0, 'R': 20.26, 'T': 1.0, 'W': 24.68, 'V': 1.0, 'Y': 1.0}, 'N': {'A': 1.0, 'C': -1.88, 'E': 1.0, 'D': 1.0, 'G': -14.03, 'F': -14.03, 'I': 44.94, 'H': 1.0, 'K': 24.68, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': -6.54, 'P': -1.88, 'S': 1.0, 'R': 1.0, 'T': -7.49, 'W': -9.37, 'V': 1.0, 'Y': 1.0}, 'Q': {'A': 1.0, 'C': -6.54, 'E': 20.26, 'D': 20.26, 'G': 1.0, 'F': -6.54, 'I': 1.0, 'H': 1.0, 'K': 1.0, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 20.26, 'P': 20.26, 'S': 44.94, 'R': 1.0, 'T': 1.0, 'W': 1.0, 'V': -6.54, 'Y': -6.54}, 'P': {'A': 20.26, 'C': -6.54, 'E': 18.38, 'D': -6.54, 'G': 1.0, 'F': 20.26, 'I': 1.0, 'H': 1.0, 'K': 1.0, 'M': -6.54, 'L': 1.0, 'N': 1.0, 'Q': 20.26, 'P': 20.26, 'S': 20.26, 'R': -6.54, 'T': 1.0, 'W': -1.88, 'V': 20.26, 'Y': 1.0}, 'S': {'A': 1.0, 'C': 33.60, 'E': 20.26, 'D': 1.0, 'G': 1.0, 'F': 1.0, 'I': 1.0, 'H': 1.0, 'K': 1.0, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 20.26, 'P': 44.94, 'S': 20.26, 'R': 20.26, 'T': 1.0, 'W': 1.0, 'V': 1.0, 'Y': 1.0}, 'R': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': 1.0, 'G': -7.49, 'F': 1.0, 'I': 1.0, 'H': 20.26, 'K': 1.0, 'M': 1.0, 'L': 1.0, 'N': 13.34, 'Q': 20.26, 'P': 20.26, 'S': 44.94, 'R': 58.28, 'T': 1.0, 'W': 58.28, 'V': 1.0, 'Y': -6.54}, 'T': {'A': 1.0, 'C': 1.0, 'E': 20.26, 'D': 1.0, 'G': -7.49, 'F': 13.34, 'I': 1.0, 'H': 1.0, 'K': 1.0, 'M': 1.0, 'L': 1.0, 'N': -14.03, 'Q': -6.54, 'P': 1.0, 'S': 1.0, 'R': 1.0, 'T': 1.0, 'W': -14.03, 'V': 1.0, 'Y': 1.0}, 'W': {'A': -14.03, 'C': 1.0, 'E': 1.0, 'D': 1.0, 'G': -9.37, 'F': 1.0, 'I': 1.0, 'H': 24.68, 'K': 1.0, 'M': 24.68, 'L': 13.34, 'N': 13.34, 'Q': 1.0, 'P': 1.0, 'S': 1.0, 'R': 1.0, 'T': -14.03, 'W': 1.0, 'V': -7.49, 'Y': 1.0}, 'V': {'A': 1.0, 'C': 1.0, 'E': 1.0, 'D': -14.03, 'G': -7.49, 'F': 1.0, 'I': 1.0, 'H': 1.0, 'K': -1.88, 'M': 1.0, 'L': 1.0, 'N': 1.0, 'Q': 1.0, 'P': 20.26, 'S': 1.0, 'R': 1.0, 'T': -7.49, 'W': 1.0, 'V': 1.0, 'Y': -6.54}, 'Y': {'A': 24.68, 'C': 1.0, 'E': -6.54, 'D': 24.68, 'G': -7.49, 'F': 1.0, 'I': 1.0, 'H': 13.34, 'K': 1.0, 'M': 44.94, 'L': 1.0, 'N': 1.0, 'Q': 1.0, 'P': 13.34, 'S': 1.0, 'R': -15.91, 'T': -7.49, 'W': -9.37, 'V': 1.0, 'Y': 13.34}} \ No newline at end of file diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/__init__.py new file mode 100644 index 0000000..d606ed7 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/__init__.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python +# Created: Wed May 29 08:07:18 2002 +# thomas@cbs.dtu.dk, Cecilia.Alsmark@ebc.uu.se +# Copyright 2001 by Thomas Sicheritz-Ponten and Cecilia Alsmark. +# All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Miscellaneous functions for dealing with sequences.""" + +import re, time +from Bio import SeqIO +from Bio import Translate +from Bio.Seq import Seq +from Bio import Alphabet +from Bio.Alphabet import IUPAC +from Bio.Data import IUPACData, CodonTable + + +###################################### +# DNA +###################### +# {{{ + +def reverse(seq): + """Reverse the sequence. Works on string sequences. + + e.g. + >>> reverse("ACGGT") + 'TGGCA' + + """ + r = list(seq) + r.reverse() + return ''.join(r) + +def GC(seq): + """Calculates G+C content, returns the percentage (float between 0 and 100). + + Copes mixed case seuqneces, and with the ambiguous nucleotide S (G or C) + when counting the G and C content. The percentage is calculated against + the full length, e.g.: + + >>> from Bio.SeqUtils import GC + >>> GC("ACTGN") + 40.0 + + Note that this will return zero for an empty sequence. + """ + try : + gc = sum(map(seq.count,['G','C','g','c','S','s'])) + return gc*100.0/len(seq) + except ZeroDivisionError : + return 0.0 + + +def GC123(seq): + """Calculates total G+C content plus first, second and third positions. + + Returns a tuple of four floats (percentages between 0 and 100) for the + entire sequence, and the three codon positions. e.g. + + >>> from Bio.SeqUtils import GC123 + >>> GC123("ACTGTN") + (40.0, 50.0, 50.0, 0.0) + + Copes with mixed case sequences, but does NOT deal with ambiguous + nucleotides. + """ + d= {} + for nt in ['A','T','G','C']: + d[nt] = [0,0,0] + + for i in range(0,len(seq),3): + codon = seq[i:i+3] + if len(codon) <3: codon += ' ' + for pos in range(0,3): + for nt in ['A','T','G','C']: + if codon[pos] == nt or codon[pos] == nt.lower(): + d[nt][pos] += 1 + gc = {} + gcall = 0 + nall = 0 + for i in range(0,3): + try: + n = d['G'][i] + d['C'][i] +d['T'][i] + d['A'][i] + gc[i] = (d['G'][i] + d['C'][i])*100.0/n + except: + gc[i] = 0 + + gcall = gcall + d['G'][i] + d['C'][i] + nall = nall + n + + gcall = 100.0*gcall/nall + return gcall, gc[0], gc[1], gc[2] + +def GC_skew(seq, window = 100): + """Calculates GC skew (G-C)/(G+C) for multuple windows along the sequence. + + Returns a list of ratios (floats), controlled by the length of the sequence + and the size of the window. + + Does NOT look at any ambiguous nucleotides. + """ + # 8/19/03: Iddo: added lowercase + values = [] + for i in range(0, len(seq), window): + s = seq[i: i + window] + g = s.count('G') + s.count('g') + c = s.count('C') + s.count('c') + skew = (g-c)/float(g+c) + values.append(skew) + return values + +from math import pi, sin, cos, log +def xGC_skew(seq, window = 1000, zoom = 100, + r = 300, px = 100, py = 100): + """Calculates and plots normal and accumulated GC skew (GRAPHICS !!!).""" + from Tkinter import Scrollbar, Canvas, BOTTOM, BOTH, ALL, \ + VERTICAL, HORIZONTAL, RIGHT, LEFT, X, Y + yscroll = Scrollbar(orient = VERTICAL) + xscroll = Scrollbar(orient = HORIZONTAL) + canvas = Canvas(yscrollcommand = yscroll.set, + xscrollcommand = xscroll.set, background = 'white') + win = canvas.winfo_toplevel() + win.geometry('700x700') + + yscroll.config(command = canvas.yview) + xscroll.config(command = canvas.xview) + yscroll.pack(side = RIGHT, fill = Y) + xscroll.pack(side = BOTTOM, fill = X) + canvas.pack(fill=BOTH, side = LEFT, expand = 1) + canvas.update() + + X0, Y0 = r + px, r + py + x1, x2, y1, y2 = X0 - r, X0 + r, Y0 -r, Y0 + r + + ty = Y0 + canvas.create_text(X0, ty, text = '%s...%s (%d nt)' % (seq[:7], seq[-7:], len(seq))) + ty +=20 + canvas.create_text(X0, ty, text = 'GC %3.2f%%' % (GC(seq))) + ty +=20 + canvas.create_text(X0, ty, text = 'GC Skew', fill = 'blue') + ty +=20 + canvas.create_text(X0, ty, text = 'Accumulated GC Skew', fill = 'magenta') + ty +=20 + canvas.create_oval(x1,y1, x2, y2) + + acc = 0 + start = 0 + for gc in GC_skew(seq, window): + r1 = r + acc+=gc + # GC skew + alpha = pi - (2*pi*start)/len(seq) + r2 = r1 - gc*zoom + x1 = X0 + r1 * sin(alpha) + y1 = Y0 + r1 * cos(alpha) + x2 = X0 + r2 * sin(alpha) + y2 = Y0 + r2 * cos(alpha) + canvas.create_line(x1,y1,x2,y2, fill = 'blue') + # accumulated GC skew + r1 = r - 50 + r2 = r1 - acc + x1 = X0 + r1 * sin(alpha) + y1 = Y0 + r1 * cos(alpha) + x2 = X0 + r2 * sin(alpha) + y2 = Y0 + r2 * cos(alpha) + canvas.create_line(x1,y1,x2,y2, fill = 'magenta') + + canvas.update() + start += window + + canvas.configure(scrollregion = canvas.bbox(ALL)) + +def molecular_weight(seq): + """Calculate the molecular weight of a DNA sequence.""" + if type(seq) == type(''): seq = Seq(seq, IUPAC.unambiguous_dna) + weight_table = IUPACData.unambiguous_dna_weights + #TODO, use a generator expession once we drop Python 2.3? + #e.g. return sum(weight_table[x] for x in seq) + total = 0 + for x in seq: + total += weight_table[x] + return total + +def nt_search(seq, subseq): + """Search for a DNA subseq in sequence. + + use ambiguous values (like N = A or T or C or G, R = A or G etc.) + searches only on forward strand + """ + pattern = '' + for nt in subseq: + value = IUPACData.ambiguous_dna_values[nt] + if len(value) == 1: + pattern += value + else: + pattern += '[%s]' % value + + pos = -1 + result = [pattern] + l = len(seq) + while True: + pos+=1 + s = seq[pos:] + m = re.search(pattern, s) + if not m: break + pos += int(m.start(0)) + result.append(pos) + return result + +# }}} + +###################################### +# Protein +###################### +# {{{ + +# temporary hack for exception free translation of "dirty" DNA +# should be moved to ??? + +class ProteinX(Alphabet.ProteinAlphabet): + letters = IUPACData.extended_protein_letters + "X" + +proteinX = ProteinX() + +class MissingTable: + def __init__(self, table): + self._table = table + def get(self, codon, stop_symbol): + try: + return self._table.get(codon, stop_symbol) + except CodonTable.TranslationError: + return 'X' + +def makeTableX(table): + assert table.protein_alphabet == IUPAC.extended_protein + return CodonTable.CodonTable(table.nucleotide_alphabet, proteinX, + MissingTable(table.forward_table), + table.back_table, table.start_codons, + table.stop_codons) + +# end of hacks + +def seq3(seq): + """Turn a one letter code protein sequence into one with three letter codes. + + The single input argument 'seq' should be a protein sequence using single + letter codes, either as a python string or as a Seq or MutableSeq object. + + This function returns the amino acid sequence as a string using the three + letter amino acid codes. Output follows the IUPAC standard (including + ambiguous characters B for "Asx", J for "Xle" and X for "Xaa", and also U + for "Sel" and O for "Pyl") plus "Ter" for a terminator given as an asterisk. Any unknown + character (including possible gap characters), is changed into 'Xaa'. + + e.g. + >>> from Bio.SeqUtils import seq3 + >>> seq3("MAIVMGRWKGAR*") + 'MetAlaIleValMetGlyArgTrpLysGlyAlaArgTer' + + This function was inspired by BioPerl's seq3. + """ + threecode = {'A':'Ala', 'B':'Asx', 'C':'Cys', 'D':'Asp', + 'E':'Glu', 'F':'Phe', 'G':'Gly', 'H':'His', + 'I':'Ile', 'K':'Lys', 'L':'Leu', 'M':'Met', + 'N':'Asn', 'P':'Pro', 'Q':'Gln', 'R':'Arg', + 'S':'Ser', 'T':'Thr', 'V':'Val', 'W':'Trp', + 'Y':'Tyr', 'Z':'Glx', 'X':'Xaa', '*':'Ter', + 'U':'Sel', 'O':'Pyl', 'J':'Xle', + } + #We use a default of 'Xaa' for undefined letters + #Note this will map '-' to 'Xaa' which may be undesirable! + return ''.join([threecode.get(aa,'Xaa') for aa in seq]) + + +# }}} + +###################################### +# Mixed ??? +###################### +# {{{ + +def translate(seq, frame = 1, genetic_code = 1, translator = None): + """Translation of DNA in one of the six different reading frames (DEPRECATED). + + Use the Bio.Seq.Translate function, or the Seq object's translate method + instead: + + >>> from Bio.Seq import Seq + >>> my_seq = Seq("AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG") + >>> my_seq = Seq("AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAGUA") + >>> for frame in [0,1,2] : + ... print my_seq[frame:].translate() + ... + MAIVMGR*KGAR* + WPL*WAAERVPDS + GHCNGPLKGCPIV + >>> for frame in [0,1,2] : + ... print my_seq.reverse_complement()[frame:].translate() + ... + YYRAPFQRPITMA + TIGHPFSGPLQWP + LSGTLSAAHYNGH + """ + import warnings + warnings.warn("Bio.SeqUtils.translate() has been deprecated, and we intend" \ + +" to remove it in a future release of Biopython. Please use"\ + +" the method or function in Bio.Seq instead, as described in"\ + +" the Tutorial.", DeprecationWarning) + + if frame not in [1,2,3,-1,-2,-3]: + raise ValueError('invalid frame') + + if not translator: + table = makeTableX(CodonTable.ambiguous_dna_by_id[genetic_code]) + translator = Translate.Translator(table) + + #Does this frame calculation do something sensible? No RC taken! + return translator.translate(Seq(seq[frame-1:], IUPAC.ambiguous_dna)).data + +def GC_Frame(seq, genetic_code = 1): + """Just an alias for six_frame_translations (OBSOLETE). + + Use six_frame_translation directly, as this function may be deprecated + in a future release.""" + return six_frame_translations(seq, genetic_code) + +def six_frame_translations(seq, genetic_code = 1): + """Formatted string showing the 6 frame translations and GC content. + + nice looking 6 frame translation with GC content - code from xbbtools + similar to DNA Striders six-frame translation + + e.g. + from Bio.SeqUtils import six_frame_translations + print six_frame_translations("AUGGCCAUUGUAAUGGGCCGCUGA") + """ + from Bio.Seq import reverse_complement, translate + anti = reverse_complement(seq) + comp = anti[::-1] + length = len(seq) + frames = {} + for i in range(0,3): + frames[i+1] = translate(seq[i:], genetic_code) + frames[-(i+1)] = reverse(translate(anti[i:], genetic_code)) + + # create header + if length > 20: + short = '%s ... %s' % (seq[:10], seq[-10:]) + else: + short = seq + #TODO? Remove the date as this would spoil any unit test... + date = time.strftime('%y %b %d, %X', time.localtime(time.time())) + header = 'GC_Frame: %s, ' % date + for nt in ['a','t','g','c']: + header += '%s:%d ' % (nt, seq.count(nt.upper())) + + header += '\nSequence: %s, %d nt, %0.2f %%GC\n\n\n' % (short.lower(),length, GC(seq)) + res = header + + for i in range(0,length,60): + subseq = seq[i:i+60] + csubseq = comp[i:i+60] + p = i/3 + res = res + '%d/%d\n' % (i+1, i/3+1) + res = res + ' ' + ' '.join(map(None,frames[3][p:p+20])) + '\n' + res = res + ' ' + ' '.join(map(None,frames[2][p:p+20])) + '\n' + res = res + ' '.join(map(None,frames[1][p:p+20])) + '\n' + # seq + res = res + subseq.lower() + '%5d %%\n' % int(GC(subseq)) + res = res + csubseq.lower() + '\n' + # - frames + res = res + ' '.join(map(None,frames[-2][p:p+20])) +' \n' + res = res + ' ' + ' '.join(map(None,frames[-1][p:p+20])) + '\n' + res = res + ' ' + ' '.join(map(None,frames[-3][p:p+20])) + '\n\n' + return res + +# }}} + +###################################### +# FASTA file utilities +###################### +# {{{ + +def fasta_uniqids(file): + """Checks and changes the name/ID's to be unique identifiers by adding numbers (OBSOLETE). + + file - a FASTA format filename to read in. + + No return value, the output is written to screen. + """ + dict = {} + txt = open(file).read() + entries = [] + for entry in txt.split('>')[1:]: + name, seq= entry.split('\n',1) + name = name.split()[0].split(',')[0] + + if name in dict: + n = 1 + while 1: + n = n + 1 + _name = name + str(n) + if _name not in dict: + name = _name + break + + dict[name] = seq + + for name, seq in dict.items(): + print '>%s\n%s' % (name, seq) + +def quick_FASTA_reader(file): + """Simple FASTA reader, returning a list of string tuples. + + The single argument 'file' should be the filename of a FASTA format file. + This function will open and read in the entire file, constructing a list + of all the records, each held as a tuple of strings (the sequence name or + title, and its sequence). + + This function was originally intended for use on large files, where its + low overhead makes it very fast. However, because it returns the data as + a single in memory list, this can require a lot of RAM on large files. + + You are generally encouraged to use Bio.SeqIO.parse(handle, "fasta") which + allows you to iterate over the records one by one (avoiding having all the + records in memory at once). Using Bio.SeqIO also makes it easy to switch + between different input file formats. However, please note that rather + than simple strings, Bio.SeqIO uses SeqRecord objects for each record. + """ + #Want to split on "\n>" not just ">" in case there are any extra ">" + #in the name/description. So, in order to make sure we also split on + #the first entry, prepend a "\n" to the start of the file. + handle = open(file) + txt = "\n" + handle.read() + handle.close() + entries = [] + for entry in txt.split('\n>')[1:]: + name,seq= entry.split('\n',1) + seq = seq.replace('\n','').replace(' ','').upper() + entries.append((name, seq)) + return entries + +def apply_on_multi_fasta(file, function, *args): + """Apply a function on each sequence in a multiple FASTA file (OBSOLETE). + + file - filename of a FASTA format file + function - the function you wish to invoke on each record + *args - any extra arguments you want passed to the function + + This function will iterate over each record in a FASTA file as SeqRecord + objects, calling your function with the record (and supplied args) as + arguments. + + This function returns a list. For those records where your function + returns a value, this is taken as a sequence and used to construct a + FASTA format string. If your function never has a return value, this + means apply_on_multi_fasta will return an empty list. + """ + try: + f = globals()[function] + except: + raise NotImplementedError("%s not implemented" % function) + + handle = open(file, 'r') + records = SeqIO.parse(handle, "fasta") + results = [] + for record in records: + arguments = [record.sequence] + for arg in args: arguments.append(arg) + result = f(*arguments) + if result: + results.append('>%s\n%s' % (record.name, result)) + handle.close() + return results + +def quicker_apply_on_multi_fasta(file, function, *args): + """Apply a function on each sequence in a multiple FASTA file (OBSOLETE). + + file - filename of a FASTA format file + function - the function you wish to invoke on each record + *args - any extra arguments you want passed to the function + + This function will use quick_FASTA_reader to load every record in the + FASTA file into memory as a list of tuples. For each record, it will + call your supplied function with the record as a tuple of the name and + sequence as strings (plus any supplied args). + + This function returns a list. For those records where your function + returns a value, this is taken as a sequence and used to construct a + FASTA format string. If your function never has a return value, this + means quicker_apply_on_multi_fasta will return an empty list. + """ + try: + f = globals()[function] + except: + raise NotImplementedError("%s not implemented" % function) + + entries = quick_FASTA_reader(file) + results = [] + for name, seq in entries: + arguments = [seq] + for arg in args: arguments.append(arg) + result = f(*arguments) + if result: + results.append('>%s\n%s' % (name, result)) + handle.close() + return results + +# }}} + +###################################### +# Main +##################### +# {{{ + +if __name__ == '__main__': + import sys, getopt + # crude command line options to use most functions directly on a FASTA file + options = {'apply_on_multi_fasta':0, + 'quick':0, + 'uniq_ids':0, + } + + optlist, args = getopt.getopt(sys.argv[1:], '', ['describe', 'apply_on_multi_fasta=', + 'help', 'quick', 'uniq_ids', 'search=']) + for arg in optlist: + if arg[0] in ['-h', '--help']: + pass + elif arg[0] in ['--describe']: + # get all new functions from this file + mol_funcs = [x[0] for x in locals().items() if type(x[1]) == type(GC)] + mol_funcs.sort() + print 'available functions:' + for f in mol_funcs: print '\t--%s' % f + print '\n\ne.g.\n./sequtils.py --apply_on_multi_fasta GC test.fas' + + sys.exit(0) + elif arg[0] in ['--apply_on_multi_fasta']: + options['apply_on_multi_fasta'] = arg[1] + elif arg[0] in ['--search']: + options['search'] = arg[1] + else: + key = re.search('-*(.+)', arg[0]).group(1) + options[key] = 1 + + + if options.get('apply_on_multi_fasta'): + file = args[0] + function = options['apply_on_multi_fasta'] + arguments = [] + if options.get('search'): + arguments = options['search'] + if function == 'xGC_skew': + arguments = 1000 + if options.get('quick'): + results = quicker_apply_on_multi_fasta(file, function, arguments) + else: + results = apply_on_multi_fasta(file, function, arguments) + for result in results: print result + + elif options.get('uniq_ids'): + file = args[0] + fasta_uniqids(file) + +# }}} + diff --git a/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/lcc.py b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/lcc.py new file mode 100644 index 0000000..1fb71a2 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/SeqUtils/lcc.py @@ -0,0 +1,162 @@ +# Copyright 2003, 2007 by Sebastian Bassi. sbassi@genesdigitales.com +# All rights reserved. This code is part of the Biopython +# distribution and governed by its license. +# Please see the LICENSE file that should have been included as part +# of this package. + +import math + +def lcc_mult(seq,wsize): + """Local Composition Complexity (LCC) values over sliding window. + + Returns a list of floats, the LCC values for a sliding window over + the sequence. + + seq - an unambiguous DNA sequence (a string or Seq object) + wsize - window size, integer + + The result is the same as applying lcc_simp multiple times, but this + version is optimized for speed. The optimization works by using the + value of previous window as a base to compute the next one.""" + l2=math.log(2) + tamseq=len(seq) + try : + #Assume its a string + upper = seq.upper() + except AttributeError : + #Should be a Seq object then + upper = seq.tostring().upper() + compone=[0] + lccsal=[0] + for i in range(wsize): + compone.append(((i+1)/float(wsize))* + ((math.log((i+1)/float(wsize)))/l2)) + window=seq[0:wsize] + cant_a=window.count('A') + cant_c=window.count('C') + cant_t=window.count('T') + cant_g=window.count('G') + term_a=compone[cant_a] + term_c=compone[cant_c] + term_t=compone[cant_t] + term_g=compone[cant_g] + lccsal.append(-(term_a+term_c+term_t+term_g)) + tail=seq[0] + for x in range (tamseq-wsize): + window=upper[x+1:wsize+x+1] + if tail==window[-1]: + lccsal.append(lccsal[-1]) + elif tail=='A': + cant_a=cant_a-1 + if window.endswith('C'): + cant_c=cant_c+1 + term_a=compone[cant_a] + term_c=compone[cant_c] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('T'): + cant_t=cant_t+1 + term_a=compone[cant_a] + term_t=compone[cant_t] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('G'): + cant_g=cant_g+1 + term_a=compone[cant_a] + term_g=compone[cant_g] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif tail=='C': + cant_c=cant_c-1 + if window.endswith('A'): + cant_a=cant_a+1 + term_a=compone[cant_a] + term_c=compone[cant_c] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('T'): + cant_t=cant_t+1 + term_c=compone[cant_c] + term_t=compone[cant_t] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('G'): + cant_g=cant_g+1 + term_c=compone[cant_c] + term_g=compone[cant_g] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif tail=='T': + cant_t=cant_t-1 + if window.endswith('A'): + cant_a=cant_a+1 + term_a=compone[cant_a] + term_t=compone[cant_t] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('C'): + cant_c=cant_c+1 + term_c=compone[cant_c] + term_t=compone[cant_t] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('G'): + cant_g=cant_g+1 + term_t=compone[cant_t] + term_g=compone[cant_g] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif tail=='G': + cant_g=cant_g-1 + if window.endswith('A'): + cant_a=cant_a+1 + term_a=compone[cant_a] + term_g=compone[cant_g] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('C'): + cant_c=cant_c+1 + term_c=compone[cant_c] + term_g=compone[cant_g] + lccsal.append(-(term_a+term_c+term_t+term_g)) + elif window.endswith('T'): + cant_t=cant_t+1 + term_t=compone[cant_t] + term_g=compone[cant_g] + lccsal.append(-(term_a+term_c+term_t+term_g)) + tail=window[0] + return lccsal + +def lcc_simp(seq): + """Local Composition Complexity (LCC) for a sequence. + + seq - an unambiguous DNA sequence (a string or Seq object) + + Returns the Local Composition Complexity (LCC) value for the entire + sequence (as a float). + + Reference: + Andrzej K Konopka (2005) Sequence Complexity and Composition + DOI: 10.1038/npg.els.0005260 + """ + wsize=len(seq) + try : + #Assume its a string + upper = seq.upper() + except AttributeError : + #Should be a Seq object then + upper = seq.tostring().upper() + l2=math.log(2) + if 'A' not in seq: + term_a=0 + # Check to avoid calculating the log of 0. + else: + term_a=((upper.count('A'))/float(wsize))*((math.log((upper.count('A')) + /float(wsize)))/l2) + if 'C' not in seq: + term_c=0 + else: + term_c=((upper.count('C'))/float(wsize))*((math.log((upper.count('C')) + /float(wsize)))/l2) + if 'T' not in seq: + term_t=0 + else: + term_t=((upper.count('T'))/float(wsize))*((math.log((upper.count('T')) + /float(wsize)))/l2) + if 'G' not in seq: + term_g=0 + else: + term_g=((upper.count('G'))/float(wsize))*((math.log((upper.count('G')) + /float(wsize)))/l2) + lccsal=-(term_a+term_c+term_t+term_g) + return lccsal diff --git a/binaries/src/globplot/biopython-1.50/Bio/Std.py b/binaries/src/globplot/biopython-1.50/Bio/Std.py new file mode 100644 index 0000000..ba44af1 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Std.py @@ -0,0 +1,503 @@ +# This is a Python module. +"""This module is DEPRECATED. + +Andrew Dalke is no longer maintaining Martel or Bio.Mindy, and these modules +and associate ones like Bio.Std are now deprecated. They are no longer +used in any of the current Biopython parsers, and are likely to be removed +in a future release. +""" + +import warnings +warnings.warn("Martel and those parts of Biopython depending on it" \ + +" directly (such as Bio.Mindy and Bio.Std) are now" \ + +" deprecated, and will be removed in a future release of"\ + +" Biopython. If you want to continue to use this code,"\ + +" please get in contact with the Biopython developers via"\ + +" the mailing lists to avoid its permanent removal from"\ + +" Biopython.", \ + DeprecationWarning) +# Standard Bioformats definitions + +import Martel +Group = Martel.Group + +namespace = "bioformat" +NS = namespace + ":" +XMLNS = "http://biopython.org/bioformat" + +def _set_if_given(attrs, field, d, valid = None, convert = None): + value = attrs.get(field) + if value is not None: + if valid is not None: + if value not in valid: + raise TypeError("%s (%r) must be one of %s" % \ + (field, value, valid)) + if convert is None: + d[field] = value + else: + d[field] = convert(value) + +def _complain_if_given(attrs, name): + if attrs.has_key(name) and attrs[name] is not None: + raise NotImplementedError("Don't yet handle %r" % (name,)) + +def _must_have(expr, f): + tag = f.tag + if tag not in expr.group_names(): + raise TypeError( + "group %r not present in the expression but is required" % \ + (tag,)) + +def _must_have_set(expr, sets): + names = expr.group_names() + for set in sets: + for f in set: + tag = f.tag + if tag not in names: + break + else: + return + if len(sets) == 1: + raise TypeError("missing required tags (need %s) in expression" % + [f.tag for f in sets[0]]) + lines = ["missing required tags in expression; must have one set from:"] + for set in sets: + lines.append( str( [t.tag for f in set] ) ) + s = "\n".join(lines) + raise TypeError(s) + +def _must_not_have(expr, f): + f.tag + if tag in expr.group_names(): + raise TypeError( + "group %r present in the expression but is not allowed" % \ + (tag,)) + + +# pre- Python 2.2 functions didn't allow attributes +def _f(): + pass +try: + _f.x = 1 + _use_hack = 0 +except AttributeError: + _use_hack = 1 +del _f + +def _check_name(f, text): + if text == "record": # XXX FIXME + return + assert NS + f.func_name == text, (NS + ":" + f.func_name, text) + +def _check_attrs(attrs, names): + for name in attrs.keys(): + if name not in names: + raise TypeError("attr %r is not allowed here (valid terms: %s)" % \ + (name, names)) + d = attrs.copy() + for name in names: + if not d.has_key(name): + d[name] = None + return d + +if not _use_hack: + def _settag(f, tag): + _check_name(f, tag) + f.tag = tag +else: + # Convert the functions into callable objects + class StdTerm: + def __init__(self, func): + self._func = func + def __call__(self, *args, **kwargs): + return self._func( *args, **kwargs) + + def _settag(f, tag): + _check_name(f, tag) + x = globals()[f.func_name] = StdTerm(f) + x.tag = tag + +################ identifier, description, and cross-references +def record(expr, attrs = {}): + attrs = _check_attrs(attrs, ("format",)) + d = {"xmlns:bioformat": XMLNS} + _set_if_given(attrs, "format", d) + return Group("record", expr, d) # XXX FIXME +_settag(record, "record") # XXX AND FIXME + + +def dbid(expr, attrs = {}): + attrs = _check_attrs(attrs, ("type", "style", "dbname")) + d = {} + _set_if_given(attrs, "type", d, ("primary", "accession", "secondary")) + _set_if_given(attrs, "dbname", d) + return Group(NS + "dbid", expr, d) +_settag(dbid, NS + "dbid") + +def description_block(expr, attrs = {}): + attrs = _check_attrs(attrs, ("join",)) + _must_have(expr, description) + d = {} + _set_if_given(attrs, "join", d, ("english", "concat", "space", "newline")) + return Group(NS + "description_block", expr, d) +_settag(description_block, NS + "description_block") + +def description(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "description", expr) +_settag(description, NS + "description") + +def description_line(expr, attrs = {}): + return description_block(description(expr, attrs)) + +def fast_dbxref(expr, attrs = {}): + attrs = _check_attrs(attrs, ("style",)) + d = {} + _set_if_given(attrs, "style", d, ("sp-general", "sp-prosite", "sp-embl")) + return Group(NS + "fast_dbxref", expr, d) + +def dbxref(expr, attrs = {}): + attrs = _check_attrs(attrs, ("style",)) + _must_have(expr, dbxref_dbid) + d = {} + _complain_if_given(attrs, "style") + return Group(NS + "dbxref", expr, d) +_settag(dbxref, NS + "dbxref") + +def dbxref_dbname(expr, attrs = {}): + attrs = _check_attrs(attrs, ("style",)) + d = {} + _set_if_given(attrs, "style", d) + return Group(NS + "dbxref_dbname", expr, d) +_settag(dbxref_dbname, NS + "dbxref_dbname") + +def dbxref_dbid(expr, attrs = {}): + attrs = _check_attrs(attrs, ("dbname", "type", "style", "negate")) + d = {} + _set_if_given(attrs, "dbname", d) + _set_if_given(attrs, "type", d, ("primary", "accession", "secondary")) + _complain_if_given(attrs, "style") + _set_if_given(attrs, "negate", d, (0, 1), str) + + return Group(NS + "dbxref_dbid", expr, d) +_settag(dbxref_dbid, NS + "dbxref_dbid") + +def dbxref_negate(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "dbxref_negate", expr) +_settag(dbxref_negate, NS + "dbxref_negate") + +##################### sequences + +def _check_gapchar(s): + if not ( ord(" ") <= ord(s) <= 126 ): + raise TypeError("%r not allowed as a gap character" % (s,)) + return s + +# What about three letter codes? +def sequence_block(expr, attrs = {}): + attrs = _check_attrs(attrs, ("alphabet", "gapchar", "remove_spaces")) + _must_have(expr, sequence) + d = {} + _set_if_given(attrs, "alphabet", d, + ("iupac-protein", "iupac-dna", "iupac-rna", + "iupac-ambiguous-protein", + "iupac-ambiguous-dna", + "iupac-ambiguous-rna", + "protein", "dna", "rna", "unknown")) + _set_if_given(attrs, "gapchar", d, convert = _check_gapchar) + _set_if_given(attrs, "remove_spaces", d, (0, 1), str) + return Group(NS + "sequence_block", expr, d) +_settag(sequence_block, NS + "sequence_block") + +def sequence(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "sequence", expr) +_settag(sequence, NS + "sequence") + +def alphabet(expr, attrs = {}): + attrs = _check_attrs(attrs, ("alphabet",)) + d = {} + _set_if_given(attrs, "alphabet", d, + ("iupac-protein", "iupac-dna", "iupac-rna", + "iupac-ambiguous-protein", + "iupac-ambiguous-dna", + "iupac-ambiguous-rna", + "protein", "dna", "rna", "nucleotide", "unknown")) + return Group(NS + "alphabet", expr, d) +_settag(alphabet, NS + "alphabet") + + + +############################## features + +# In PIR + +# FEATURE +# 1-25 #domain signal sequence #status predicted #label SIG\ +# 26-737 #product procollagen-lysine 5-dioxygenase 2 #status +# predicted #label MAT\ +# 63,209,297,365,522, +# 725 #binding_site carbohydrate (Asn) (covalent) #status +# predicted + +# The whole thing is a 'feature_block' + +# One 'feature' is +# 26-737 #product procollagen-lysine 5-dioxygenase 2 #status +# predicted #label MAT\ + +# One 'feature_name' is "binding_site". + +# An example of the feature_location_block and feature_block, which I +# will abbreviate as 'flb' and 'fl', is: +# 63,209,297,365,522, +# 725 #binding_site carbohydrate ... + +# PIR doesn't have a 'feature_description' + +# Let: +# fq = feature_qualifier +# fqb = feature_qualifier +# fqn = feature_qualifier_name +# fqd = feature_qualifier_description +# then the text +# +# 26-737 #product procollagen-lysine 5-dioxygenase 2 #status +# predicted #label MAT\ +# +# can be represented as (the rather tedious) +# +# 26-737 #product procollagen-\ +# lysine 5-dioxygenase 2 #status +# predicted #label\ +# MAT\ +# + +# 'style' determines the namespace for the feature name +def feature_block(expr, attrs = {}): + attrs = _check_attrs(attrs, ("style", "location-style")) + d = {} + _set_if_given(attrs, "style", d) + _set_if_given(attrs, "location-style", d) + _must_have(expr, feature) + return Group(NS + "feature_block", expr, d) +_settag(feature_block, NS + "feature_block") + +def feature(expr, attrs = {}): + attrs = _check_attrs(attrs, ("location-style",)) + d = {} + _set_if_given(attrs, "location-style", d) + _must_have(expr, feature_name) + _must_have_set(expr, [[feature_location], + [feature_location_start, feature_location_end]]) + return Group(NS + "feature", expr, d) +_settag(feature, NS + "feature") + +def feature_name(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "feature_name", expr) +_settag(feature_name, NS + "feature_name") + +def feature_location(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "feature_location", expr) +_settag(feature_location, NS + "feature_location") + +def feature_location_start(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "feature_location_start", expr) +_settag(feature_location_start, NS + "feature_location_start") + +def feature_location_end(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "feature_location_end", expr) +_settag(feature_location_end, NS + "feature_location_end") + +def feature_description(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "feature_description", expr) +_settag(feature_description, NS + "feature_description") + + +##def feature_qualifier_block(expr, attrs = {}): +## attrs = _check_attrs(attrs, ()) +## _must_have(expr, feature_qualifier) +## return Group(NS + "feature_qualifier_block", expr) +##_settag(feature_qualifier_block, NS + "feature_qualifier_block") + +def feature_qualifier(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + _must_have(expr, feature_qualifier_name) + return Group(NS + "feature_qualifier", expr) +_settag(feature_qualifier, NS + "feature_qualifier") + +def feature_qualifier_name(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "feature_qualifier_name", expr) +_settag(feature_qualifier_name, NS + "feature_qualifier_name") + +def feature_qualifier_description(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group(NS + "feature_qualifier_description", expr) +_settag(feature_qualifier_description, NS + "feature_qualifier_description") + + +############ For homology searches + +# "BLASTN", "BLASTP" +def application_name(expr, attrs = {}): + attrs = _check_attrs(attrs, ("app",)) + return Group("bioformat:application_name", expr, attrs) + +# "2.0.11", "2.0a19MP-WashU" +def application_version(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:application_version", expr, attrs) + +def search_header(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:search_header", expr, attrs) + +def search_table(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:search_table", expr, attrs) + +def search_table_description(expr, attrs = {}): + attrs = _check_attrs(attrs, ("bioformat:decode",)) + d = {"bioformat:decode": "strip"} + _set_if_given(attrs, "bioformat:decode", d) + return Group("bioformat:search_table_description", expr, d) + +def search_table_value(expr, attrs = {}): + attrs = _check_attrs(attrs, ("name", "bioformat:decode")) + return Group("bioformat:search_table_value", expr, attrs) + +def search_table_entry(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:search_table_entry", expr, attrs) + +def query_description_block(expr, attrs = {}): + attrs = _check_attrs(attrs, ("join-query",)) + d = {"join-query": "join|fixspaces"} + _set_if_given(attrs, "join-query", d) + return Group("bioformat:query_description_block", expr, d) + +def query_description(expr, attrs = {}): + attrs = _check_attrs(attrs, ("bioformat:decode")) + d = {} + _set_if_given(attrs, "bioformat:decode", d) + return Group("bioformat:query_description", expr, d) + +def query_size(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:query_size", expr) + +def database_name(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:database_name", expr, attrs) + +def database_num_sequences(expr, attrs = {}): + attrs = _check_attrs(attrs, ("bioformat:decode",)) + return Group("bioformat:database_num_sequences", expr, attrs) + +def database_num_letters(expr, attrs = {}): + attrs = _check_attrs(attrs, ("bioformat:decode",)) + return Group("bioformat:database_num_letters", expr, attrs) + +def hit(expr, attrs = {}): + attrs = _check_attrs(attrs, ("join-description",)) + d = {"join-description": "join|fixspaces"} + _set_if_given(attrs, "join-description", d) + return Group("bioformat:hit", expr, d) + +def hit_length(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hit_length", expr, attrs) + +def hit_description(expr, attrs = {}): + attrs = _check_attrs(attrs, ("bioformat:decode")) + d = {} + _set_if_given(attrs, "bioformat:decode", d) + return Group("bioformat:hit_description", expr, d) + +def hsp(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp", expr, attrs) + +def hsp_value(expr, attrs = {}): + attrs = _check_attrs(attrs, ("name", "bioformat:decode")) + return Group("bioformat:hsp_value", expr, attrs) + +def hsp_frame(expr, attrs = {}): + attrs = _check_attrs(attrs, ("which",)) + d = {} + _set_if_given(attrs, "which", d, valid = ("query", "homology", "subject")) + return Group("bioformat:hsp_frame", expr, d) + +def hsp_strand(expr, attrs = {}): + attrs = _check_attrs(attrs, ("strand", "which")) + d = {} + _set_if_given(attrs, "which", d, valid = ("query", "homology", "subject")) + _set_if_given(attrs, "strand", d, valid = ("+1", "0", "-1", "")) + return Group("bioformat:hsp_strand", expr, d) + +def hsp_seqalign_query_seq(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_query_seq", expr, attrs) + +def hsp_seqalign_homology_seq(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_homology_seq", expr, attrs) + +def hsp_seqalign_subject_seq(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_subject_seq", expr, attrs) + +def hsp_seqalign_query_leader(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_query_leader", expr, attrs) + + +def hsp_seqalign_query_name(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_query_name", expr, attrs) + +def hsp_seqalign_subject_name(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_subject_name", expr, attrs) + +def hsp_seqalign(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign", expr, attrs) + +def hsp_seqalign_query_start(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_query_start", expr, attrs) + +def hsp_seqalign_query_end(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_query_end", expr, attrs) + +def hsp_seqalign_subject_start(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_subject_start", expr, attrs) + +def hsp_seqalign_subject_end(expr, attrs = {}): + attrs = _check_attrs(attrs, ()) + return Group("bioformat:hsp_seqalign_subject_end", expr, attrs) + +def search_parameter(expr, attrs = {}): + attrs = _check_attrs(attrs, ("name", "bioformat:decode")) + d = {} + _set_if_given(attrs, "name", d) + _set_if_given(attrs, "bioformat:decode", d) + return Group("bioformat:search_parameter", expr, d) + +def search_statistic(expr, attrs = {}): + attrs = _check_attrs(attrs, ("name", "bioformat:decode")) + d = {} + _set_if_given(attrs, "name", d) + _set_if_given(attrs, "bioformat:decode", d) + return Group("bioformat:search_statistic", expr, d) + diff --git a/binaries/src/globplot/biopython-1.50/Bio/StdHandler.py b/binaries/src/globplot/biopython-1.50/Bio/StdHandler.py new file mode 100644 index 0000000..5807021 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/StdHandler.py @@ -0,0 +1,770 @@ +# Standard Content and Dispatch handlers for the Bioformat IO system +# This is a Python module. +"""This module is DEPRECATED. + +Andrew Dalke is no longer maintaining Martel or Bio.Mindy, and these modules +and associate ones like Bio.StdHandler are now deprecated. They are no longer +used in any of the current Biopython parsers, and are likely to be removed +in a future release. +""" + +import warnings +warnings.warn("Martel and those parts of Biopython depending on it" \ + +" directly (such as Bio.Mindy and Bio.StdHandler) are now" \ + +" deprecated, and will be removed in a future release of"\ + +" Biopython. If you want to continue to use this code,"\ + +" please get in contact with the Biopython developers via"\ + +" the mailing lists to avoid its permanent removal from"\ + +" Biopython.", \ + DeprecationWarning) + +from xml.sax import handler +from Martel import Parser, Dispatch +from Bio import Std, Decode + +################################### + +# Helper functions to make functions + +def add_int_handler(klass, tag, attrname): + assert not hasattr(klass, "start_" +tag), "existing method exists" + assert not hasattr(klass, "end_" +tag), "existing method exists" + s = """if 1: + def start(self, tag, attrs): + self.save_characters() + def end(self, tag): + self.%s = int(self.get_characters()) +""" % attrname + d = {} + exec s in d + setattr(klass, "start_" + tag, d["start"]) + setattr(klass, "end_" + tag, d["end"]) + +def add_text_handler(klass, tag, attrname): + assert not hasattr(klass, "start_" +tag), "existing method exists" + assert not hasattr(klass, "end_" +tag), "existing method exists" + s = """if 1: + def start(self, tag, attrs): + self.save_characters() + def end(self, tag): + self.%s = self.get_characters() +""" % attrname + d = {} + exec s in d + setattr(klass, "start_" + tag, d["start"]) + setattr(klass, "end_" + tag, d["end"]) + +def add_text_dict_handler(klass, tag, attrname, key): + assert not hasattr(klass, "start_" +tag), "existing method exists" + assert not hasattr(klass, "end_" +tag), "existing method exists" + s = """if 1: + def start(self, tag, attrs): + self.save_characters() + def end(self, tag): + self.%s["%s"] = self.get_characters() +""" % (attrname, key) + d = {} + exec s in d + setattr(klass, "start_" + tag, d["start"]) + setattr(klass, "end_" + tag, d["end"]) + +def add_text_decode_handler(klass, tag, attrname): + assert not hasattr(klass, "start_" +tag), "existing method exists" + assert not hasattr(klass, "end_" +tag), "existing method exists" + s = """if 1: + def start(self, tag, attrs): + self.save_characters() + self._decode_%s = attrs.get("bioformat:decode", None) + def end(self, tag): + if self._decode_%s is not None: + s = Decode.make_decoder(self._decode_%s)(s) + self.%s = self.get_characters() +""" % (tag, tag, tag, attrname) + d = {"Decode": Decode} + exec s in d + setattr(klass, "start_" + tag, d["start"]) + setattr(klass, "end_" + tag, d["end"]) + +def add_first_text_handler(klass, tag, attrname): + assert not hasattr(klass, "start_" +tag), "existing method exists" + assert not hasattr(klass, "end_" +tag), "existing method exists" + s = """if 1: + def start(self, tag, attrs): + if self.%s is None: + self.save_characters() + def end(self, tag): + if self.%s is None: + self.%s = self.get_characters() +""" % (attrname, attrname, attrname) + d = {} + exec s in d + setattr(klass, "start_" + tag, d["start"]) + setattr(klass, "end_" + tag, d["end"]) + +def add_text_block_handler(klass, tag, joinattr, defaultjoin, attrname): + assert not hasattr(klass, "start_" + tag), "existing method exists" + assert not hasattr(klass, "end_" + tag), "existing method exists" + assert not hasattr(klass, "start_"+tag+"_block"), "existing method exists" + assert not hasattr(klass, "end_" +tag+"_block"), "existing method exists" + s = """if 1: + def start_block(self, tag, attrs): + self._%(tag)s_join_func = Decode.make_decoder(attrs.get(%(joinattr)r, %(defaultjoin)r)) + self._%(tag)s_lines = [] + def end_block(self, tag): + self.%(attrname)s = self._%(tag)s_join_func(self._%(tag)s_lines) + def start(self, tag, attrs): + self.save_characters() + def end(self, tag): + self._%(tag)s_lines.append(self.get_characters()) +""" % locals() + d = {"Decode": Decode} + exec s in d + setattr(klass, "start_" + tag, d["start"]) + setattr(klass, "end_" + tag, d["end"]) + setattr(klass, "start_" + tag + "_block", d["start_block"]) + setattr(klass, "end_" + tag + "_block", d["end_block"]) + +def add_value_handler(klass, tag, attrname): + assert not hasattr(klass, "start_" +tag), "existing method exists" + assert not hasattr(klass, "end_" +tag), "existing method exists" + s = """if 1: + def start(self, tag, attrs): + self._%(tag)s_name = attrs["name"] + self._%(tag)s_decode = attrs.get("bioformat:decode", None) + self.save_characters() + def end(self, tag): + s = self.get_characters() + if self._%(tag)s_decode is not None: + s = Decode.make_decoder(self._%(tag)s_decode)(s) + self.%(attrname)s[self._%(tag)s_name] = s +""" % locals() + d = {"Decode": Decode} + exec s in d + setattr(klass, "start_" + tag, d["start"]) + setattr(klass, "end_" + tag, d["end"]) + + +################################# + +class ConvertHandler(handler.ContentHandler): + """Used to read records and produce output""" + def __init__(self, record_builder, writer, record_tag = "record"): + handler.ContentHandler.__init__(self) + self.record_builder = record_builder + self.writer = writer + self.record_tag = record_tag + + def startDocument(self): + self.inside_record = 0 + self.characters = self.ignore_characters + + def startElement(self, tag, attrs): + if self.inside_record: + self.record_builder.startElement(tag, attrs) + elif tag == self.record_tag: + self.record_builder.startDocument() + self.inside_record = 1 + self.characters = self.record_builder.characters + self.record_builder.startElement(tag, attrs) + + def endElement(self, tag): + if self.inside_record: + self.record_builder.endElement(tag) + if tag == self.record_tag: + self.record_builder.endDocument() + self.writer.write(self.record_builder.document) + self.inside_record = 0 + self.characters = self.ignore_characters + + def ignore_characters(self, s): + pass + +class ConvertDispatchHandler(Dispatch.Dispatcher): + """Used to read records and produce output through a Dispatcher""" + def __init__(self, record_builder, writer, record_tag = "record"): + setattr(self, "end_" + record_tag, self.write_record) + Dispatch.Dispatcher.__init__(self, + remap = {record_tag: "bioformat:"} + ) + self.acquire(record_builder) + self.record_builder = record_builder + self.writer = writer + self.record_tag = record_tag + def write_record(self, tag): + self.writer.write(self.record_builder.document) + + + +class RecognizeHandler(handler.ContentHandler, handler.ErrorHandler): + def __init__(self): + self.recognized = 1 + self.exc = None + + def fatalError(self, exc): + if isinstance(exc, Parser.ParserIncompleteException): + pass + else: + self.recognized = 0 + self.exc = exc + raise exc + + error = fatalError + + def endElement(self, tag): + if tag == "record": + raise Parser.ParserException("we finished a record!") + + + +class Handle_dbid(Dispatch.Callback): + def start_dbid(self, tag, attrs): + self.attrs = attrs + self.save_characters() + + def end_dbid(self, tag): + text = self.get_characters() + self.callback(text, self.attrs) + + +class Handle_description(Dispatch.Callback): + def start_description_block(self, tag, attrs): + j = attrs.get("join", None) + if j is None: + self.join_fctn = Decode.join_fixspaces + else: + self.join_fctn = Decode.make_typechecked_decoder(j, list, str) + self.descriptions = [] + def start_description(self, tag, attrs): + self.save_characters() + def end_description(self, tag): + x = self.get_characters() + self.descriptions.append(x) + def end_description_block(self, tag): + self.callback(self.join_fctn(self.descriptions)) + +#### There can be multiple dbxref_dbids in a dbxref +# DR EMBL; X64411; CAA45756.1; -. +# <..dbname style="swiss">EMBL +# X64411 +# CAA45756.1 +# +### +# DR P35156, YPUI_BACSU, F; +# P35156 +# YPUI_BACSU +# +# + +def _fixup_sp_pattern(exp): + import re + import Martel + exp = Martel.select_names(exp, (Std.dbxref_dbname.tag,Std.dbxref_dbid.tag)) + + e = exp._find_groups(Std.dbxref_dbname.tag) + assert len(e) == 1 + e = e[0] + e.name = "dbname" + dbstyle = e.attrs["style"] + e.attrs = {} + e = exp._find_groups(Std.dbxref_dbid.tag) + assert len(e) == 2 + e[0].name = "primary_dbid" + primary_type = e[0].attrs["type"] + e[0].attrs = {} + e[1].name = "secondary_dbid" + secondary_type = e[1].attrs["type"] + e[1].attrs = {} + pattern = str(exp) + "$" + pat = re.compile(pattern) + return pat, dbstyle, primary_type, secondary_type + +# Turns out these 'fast' versions speed up the dbxref code by about +# a factor of 2. + +# DR PIR; S08427; S08427. +_fast_dbxref_sp_general_data = None +def _fast_dbxref_sp_general(s): + global _fast_dbxref_sp_general_data + if _fast_dbxref_sp_general_data is None: + from Bio.expressions.swissprot import sprot38 + _fast_dbxref_sp_general_data = _fixup_sp_pattern( + sprot38.real_DR_general) + + pat, dbstyle, primary_type, secondary_type = _fast_dbxref_sp_general_data + + m = pat.match(s) + assert m is not None, "Ill-formated sp-general dxbref: %r" % s + return ( + (dbstyle, m.group("dbname"), primary_type, + m.group("primary_dbid"), 0), + (dbstyle, m.group("dbname"), secondary_type, + m.group("secondary_dbid"), 0) + ) + +# DR PFAM; PF01018; GTP1_OBG; 1. +# DR PROSITE; PS00905; GTP1_OBG; 1. + +_fast_dbxref_sp_prosite_data = None +def _fast_dbxref_sp_prosite(s): + global _fast_dbxref_sp_prosite_data + + if _fast_dbxref_sp_prosite_data is None: + from Bio.expressions.swissprot import sprot38 + _fast_dbxref_sp_prosite_data = _fixup_sp_pattern( + sprot38.real_DR_prosite) + + pat, dbstyle, primary_type, secondary_type = _fast_dbxref_sp_prosite_data + m = pat.match(s) + assert m is not None, "Ill-formated sp-prosite dxbref: %r" % s + return ( + (dbstyle, m.group("dbname"), primary_type, + m.group("primary_dbid"), 0), + (dbstyle, m.group("dbname"), secondary_type, + m.group("secondary_dbid"), 0) + ) + + +# DR EMBL; M36407; AAA33110.1; -. +_fast_dbxref_sp_embl_data = None +def _fast_dbxref_sp_embl(s): + global _fast_dbxref_sp_embl_data + + if _fast_dbxref_sp_embl_data is None: + from Bio.expressions.swissprot import sprot38 + _fast_dbxref_sp_embl_data = _fixup_sp_pattern( + sprot38.real_DR_embl) + + pat, dbstyle, primary_type, secondary_type = _fast_dbxref_sp_embl_data + m = pat.match(s) + assert m is not None, "Ill-formated sp-embl dxbref: %r" % s + return ( + (dbstyle, m.group("dbname"), primary_type, + m.group("primary_dbid"), 0), + (dbstyle, m.group("dbname"), secondary_type, + m.group("secondary_dbid"), 0) + ) + +_fast_dbxref_parser_table = { + "sp-general": _fast_dbxref_sp_general, + "sp-prosite": _fast_dbxref_sp_prosite, + "sp-embl": _fast_dbxref_sp_embl, +} + +class Handle_dbxref(Dispatch.Callback): + def __init__(self, callback): + Dispatch.Callback.__init__(self, callback) + self.supported_features.append("fast-sp-dbxref") + self.slow_callback = self.callback + def start_dbxref(self, tag, attrs): + self.negate = 0 + self.dbname = None + self.dbids = [] + self.info = [] + + def start_dbxref_dbname(self, tag, attrs): + assert self.dbname is None, "cannot set the dbname twice" + self.dbname_style = attrs.get("style", "unknown") + self.save_characters() + def end_dbxref_dbname(self, tag): + self.dbname = self.get_characters() + + def start_dbxref_dbid(self, tag, attrs): + d = attrs.get("dbname", None) + if d is None: + assert self.dbname is not None, "must set the dbname" + self.info.append( (self.dbname_style, self.dbname, + attrs.get("type", "primary")) ) + else: + self.info.append( ("bioformat", d, + attrs.get("type", "primary")) ) + self.save_characters() + + def end_dbxref_dbid(self, tag): + self.dbids.append( self.get_characters()) + + def start_dbxref_negate(self, tag, attrs): + self.negate = 1 + + def end_dbxref(self, tag): + cb = self.slow_callback + if cb is None: + return + negate = self.negate + for ( (dbname_style, dbname, idtype), dbid) in zip(self.info, + self.dbids): + self.slow_callback(dbname_style, dbname, idtype, dbid, negate) + + def start_fast_dbxref(self, tag, attrs): + style = attrs["style"] + self._fast_parser = _fast_dbxref_parser_table[style] + self.save_characters() + self.slow_callback = None + def end_fast_dbxref(self, tag): + for info in self._fast_parser(self.get_characters()): + self.callback(*info) + self.slow_callback = self.callback + +################## +class Handle_sequence(Dispatch.Callback): + global_alphabet = None + def start_(self, tag, attrs): + self.global_alphabet = None + + def start_sequence_block(self, tag, attrs): + self.local_alphabet = attrs.get("alphabet", None) + self.gapchar = attrs.get("gapchar", None) + self.stopchar = attrs.get("stopchar", None) + j = attrs.get("join", None) + if j is not None: + self.join_func = Decode.make_typechecked_decoder(j, list, str) + else: + self.join_func = None + self.sequences = [] + + def end_sequence_block(self, tag): + f = self.join_func + if f is not None: + seq = self.f(self.sequences) + else: + seq = "".join(self.sequences).replace(" ", "") + alphabet = self.local_alphabet or self.global_alphabet or "unknown" + self.callback( (alphabet, seq, self.gapchar, self.stopchar) ) + + def start_alphabet(self, tag, attrs): + self.global_alphabet = attrs["alphabet"] + + def start_sequence(self, tag, attrs): + self.save_characters() + def end_sequence(self, tag): + self.sequences.append(self.get_characters()) + +class Feature: + def __init__(self, name, description, location, qualifiers): + self.name = name + self.description = description + self.location = location + self.qualifiers = qualifiers + def __str__(self): + return "Feature %r %r %s num_qualifiers = %d" % \ + (self.name, self.description, self.location, + len(self.qualifiers)) + + +class Handle_feature_location(Dispatch.Callback): + def __init__(self, callback, settings = {}): + Dispatch.Callback.__init__(self, callback) + self.settings = settings + + def start_feature(self, tag, attrs): + self.location_style = attrs.get("location-style", + self.settings["location-style"]) + j = attrs.get("join-feature", None) + if j is None: + self.text_join_func = "".join + else: + self.text_join_func = Decode.make_typechecked_decoder(j, list, str) + + self.location_start = None + self.location_end = None + self.text_lines = [] + + def end_feature(self, tag): + if self.location_start or self.location_end: + if self.text_lines: + raise TypeError("Cannot have both location text and start/end") + self.callback(self.location_style, + (self.location_start, self.location_end)) + else: + self.callback(self.location_style, + (self.text_join_func(self.text_lines), None)) + + def start_feature_location(self, tag, attrs): + self.save_characters() + def end_feature_location(self, tag): + self.text_lines.append(self.get_characters()) + +add_text_handler(Handle_feature_location, "feature_location_start", + "location_start") +add_text_handler(Handle_feature_location, "feature_location_end", + "location_end") + +################################## + +class Handle_feature_qualifier(Dispatch.Callback): + def __init__(self, callback, settings): + self.settings = settings + Dispatch.Callback.__init__(self, callback) + + def start_feature_qualifier(self, tag, attrs): + self.name = None + self.description = [] + qj = attrs.get("join-qualifier", None) + if qj is None: + self.join = self.settings["qualifier_join_func"] + else: + self.join = Decode.make_typechecked_decoder(qj, list, str) + + def end_feature_qualifier(self, tag): + self.callback(self.name, self.join(self.description)) + + def start_feature_qualifier_description(self, tag, attrs): + self.save_characters() + def end_feature_qualifier_description(self, tag): + self.description.append(self.get_characters()) + +add_text_handler(Handle_feature_qualifier, "feature_qualifier_name", "name") + +#################### + +class Handle_features(Dispatch.Callback): + def __init__(self, callback): + Dispatch.Callback.__init__(self, callback) + self.settings = {} + + self.acquire(Handle_feature_location(self.add_location, self.settings)) + + self.acquire(Handle_feature_qualifier(self.add_feature_qualifier, + self.settings)) + + def start_feature_block(self, tag, attrs): + jf = attrs.get("join-description", None) + if jf is None: + self.join_feature_description = Decode.join_fixspaces + else: + self.join_feature_description = Decode.make_typechecked_decoder( + jf, list, str) + + self.settings["location-style"] = attrs.get("location-style", None) + + jq = attrs.get("join-qualifier", None) + if jq is None: + self.settings["qualifier_join_func"] = Decode.join_fixspaces + else: + self.settings["qualifier_join_func"] = \ + Decode.make_typechecked_decoder(jq, list, str) + self.features = [] + + def end_feature_block(self, tag): + self.callback(self.features) + self.features = None + + def start_feature(self, tag, attrs): + self.name = None + self.description = [] + self.location = None + self.qualifiers = [] + + def start_feature_description(self, tag, attrs): + self.save_characters() + def end_feature_description(self, tag): + self.description.append(self.get_characters()) + + def end_feature(self, tag): + self.features.append(Feature( + self.name, + self.join_feature_description(self.description), + self.location, + self.qualifiers)) + + def add_feature_qualifier(self, name, description): + self.qualifiers.append((name, description)) + + def add_location(self, style, location_info): + self.location = (style, location_info) + +add_text_handler(Handle_features, "feature_name", "name") + + +############## Search handlers + +class Handle_hsp_seqalign(Dispatch.Callback): + def start_hsp(self, tag, attrs): + self.query_name = None # "Query" + self.subject_name = None # "Sbjct" + + self.query_seq = "" # the actual text of the sequence + self.homology_seq = "" + self.subject_seq = "" + + self.query_start_loc = None + self.query_end_loc = None + + self.subject_start_loc = None + self.subject_end_loc = None + + def end_hsp(self, tag): + self.callback(self) + + def start_hsp_seqalign(self, tag, attrs): + self.sub_leader = None + + def start_hsp_seqalign_query_seq(self, tag, attrs): + self.save_characters() + def end_hsp_seqalign_query_seq(self, tag): + s = self.get_characters() + self.query_seq += s + self.sub_query_seq_len = len(s) + + def start_hsp_seqalign_homology_seq(self, tag, attrs): + self.save_characters() + def end_hsp_seqalign_homology_seq(self, tag): + query_leader = self.leader_size + query_seq_len = self.sub_query_seq_len + line = self.get_characters() + s = line[query_leader:query_leader+query_seq_len] + assert len(s) == query_seq_len, (len(s), query_seq_len, line) + self.homology_seq += s + + def start_hsp_seqalign_subject_seq(self, tag, attrs): + self.save_characters() + def end_hsp_seqalign_subject_seq(self, tag): + self.subject_seq += self.get_characters() + + def start_hsp_seqalign_query_leader(self, tag, attrs): + self.save_characters() + def end_hsp_seqalign_query_leader(self, tag): + self.leader_size = len(self.get_characters()) + +add_first_text_handler(Handle_hsp_seqalign, "hsp_seqalign_query_name", + "query_name") + +add_first_text_handler(Handle_hsp_seqalign, "hsp_seqalign_subject_name", + "subject_name") + +add_first_text_handler(Handle_hsp_seqalign, "hsp_seqalign_query_start", + "query_start_loc") +add_text_handler(Handle_hsp_seqalign, "hsp_seqalign_query_end", + "query_end_loc") + +add_first_text_handler(Handle_hsp_seqalign, "hsp_seqalign_subject_start", + "subject_start_loc") +add_text_handler(Handle_hsp_seqalign, "hsp_seqalign_subject_end", + "subject_end_loc") + + + + +############################# + +class Handle_hsp(Dispatch.Callback): + def __init__(self, callback): + Dispatch.Callback.__init__(self, callback) + self.acquire(Handle_hsp_seqalign(self.add_hsp_seqs)) + + def start_hsp(self, tag, attrs): + self.hsp_values = {} # expect, p, identities, ... + self.strands = {} + self.frames = {} + + def end_hsp(self, tag): + self.callback(self.hsp_values, + self.hsp_info, + self.strands, self.frames, + ) + + def start_hsp_strand(self, tag, attrs): + self.strands[attrs["which"]] = attrs["strand"] + + def start_hsp_frame(self, tag, attrs): + self.getting_frame = attrs["which"] + self.save_characters() + + def end_hsp_frame(self, tag): + self.frames[self.getting_frame] = self.get_characters() + self.getting_frame = None + + def add_hsp_seqs(self, hsp_info): + self.hsp_info = hsp_info + + def start_hsp_value(self, tag, attrs): + self.value_convert = attrs.get("bioformat:decode", None) + self.value_name = attrs["name"] + self.save_characters() + + def end_hsp_value(self, tag): + s = self.get_characters() + if self.value_name is not None: + if self.value_name == "float": + s = float(s) + else: + s = Decode.make_decoder(self.value_convert)(s) + self.hsp_values[self.value_name] = s + +############################# + + +class Handle_search_table(Dispatch.Callback): + def start_search_table_value(self, tag, attrs): + self.value_name = attrs["name"] + self.value_decode = attrs.get("bioformat:decode", None) + self.save_characters() + def end_search_table_value(self, tag): + s = self.get_characters() + if self.value_decode is not None: + x = self.value_decode + if x == "int": + s = int(s) + elif x == "float": + s = float(s) + else: + s = Decode.make_decoder(x)(s) + self.values[self.value_name] = s + + def start_search_table(self, tag, attrs): + self.data = [] + def end_search_table(self, tag): + self.callback(self.data) + self.data = None + + def start_search_table_entry(self, tag, attrs): + self.description = None + self.values = {} + + def end_search_table_entry(self, tag): + self.data.append( (self.description, self.values) ) + self.description = self.values = None + +add_text_handler(Handle_search_table, "search_table_description", + "description") + +############################# + +class Handle_search_header(Dispatch.Callback): + def start_(self, tag, attrs): + self.dict = {} + self.query_description = None + + def end_search_header(self, tag): + d = self.dict + d["query_description"] = self.query_description + self.callback(d) + +add_text_block_handler(Handle_search_header, "query_description", + "join-query", "join|fixspaces", "query_description") + +add_text_dict_handler(Handle_search_header, "application_name", + "dict", "appname") +add_text_dict_handler(Handle_search_header, "application_version", + "dict", "appversion") +add_text_dict_handler(Handle_search_header, "database_name", + "dict", "dbname") +add_text_dict_handler(Handle_search_header, "database_num_sequences", + "dict", "db_num_sequences") +add_text_dict_handler(Handle_search_header, "database_num_letters", + "dict", "db_num_letters") +add_text_dict_handler(Handle_search_header, "query_size", + "dict", "query_size") + + +############################# + +class Handle_search_info(Dispatch.Callback): + def start_(self, tag, attrs): + self.parameters = {} + self.statistics = {} + + def end_(self, tag): + self.callback(self.parameters, self.statistics) + +add_value_handler(Handle_search_info, "search_parameter", "parameters") +add_value_handler(Handle_search_info, "search_statistic", "statistics") diff --git a/binaries/src/globplot/biopython-1.50/Bio/Transcribe.py b/binaries/src/globplot/biopython-1.50/Bio/Transcribe.py new file mode 100644 index 0000000..9cc0b48 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Transcribe.py @@ -0,0 +1,34 @@ +"""Code to transcribe DNA into RNA or back (OBSOLETE). + +You are now encouraged to use the Seq object methods or the functions +in Bio.Seq instead. + +This module is now considered to be obsolete, and is likely to be deprecated +in a future release of Biopython, and later removed. +""" + +from Bio import Alphabet, Seq +from Bio.Alphabet import IUPAC + +class Transcribe: + def __init__(self, dna_alphabet, rna_alphabet): + self.dna_alphabet = dna_alphabet + self.rna_alphabet = rna_alphabet + + def transcribe(self, dna): + assert dna.alphabet == self.dna_alphabet, \ + "transcribe has the wrong DNA alphabet" + s = dna.data + return Seq.Seq(s.replace("T", "U"), self.rna_alphabet) + def back_transcribe(self, rna): + assert rna.alphabet == self.rna_alphabet, \ + "back transcribe has the wrong RNA alphabet" + s = rna.data + return Seq.Seq(s.replace("U", "T"), self.dna_alphabet) + +generic_transcriber = Transcribe(Alphabet.generic_dna, + Alphabet.generic_rna) +ambiguous_transcriber = Transcribe(IUPAC.ambiguous_dna, + IUPAC.ambiguous_rna) +unambiguous_transcriber = Transcribe(IUPAC.unambiguous_dna, + IUPAC.unambiguous_rna) diff --git a/binaries/src/globplot/biopython-1.50/Bio/Translate.py b/binaries/src/globplot/biopython-1.50/Bio/Translate.py new file mode 100644 index 0000000..05da460 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Translate.py @@ -0,0 +1,133 @@ +"""Code to translate DNA or RNA into proteins (OBSOLETE). + +Instead of Bio.Translate, for translation you are now encouraged to use the +Seq object's translate method, or the translate function in the Bio.Seq +module. Translate-to-stop functionality is via an optional argument. + +Bio.Seq does not offer any back-translation function like the one here. It +was concluded that a since a simple back-translation giving a Seq or python +string could only capture some of the possible back translations, there were +no practical uses for such a method/function. + +This module is now considered to be obsolete, and is likely to be deprecated +in a future release of Biopython, and later removed. +""" +from Bio import Alphabet, Seq +from Bio.Data import CodonTable + +class Translator: + def __init__(self, table): + self.table = table + self._encoded = {} + + def __str__(self) : + return "Translator object\n" + str(self.table) + + def translate(self, seq, stop_symbol = "*"): + #Allow different instances of the same class to be used: + assert seq.alphabet.__class__ == \ + self.table.nucleotide_alphabet.__class__, \ + "cannot translate from given alphabet (have %s, need %s)" %\ + (seq.alphabet, self.table.nucleotide_alphabet) + s = seq.data + letters = [] + append = letters.append + table = self.table + get = table.forward_table.get + n = len(seq) + for i in range(0, n-n%3, 3): + append(get(s[i:i+3], stop_symbol)) + + # return with the correct alphabet encoding (cache the encoding) + try: + alphabet = self._encoded[stop_symbol] + except KeyError: + alphabet = Alphabet.HasStopCodon(table.protein_alphabet, + stop_symbol) + self._encoded[stop_symbol] = alphabet + + return Seq.Seq("".join(letters), alphabet) + + def translate_to_stop(self, seq): + # This doesn't have a stop encoding + + #Allow different instances of the same class to be used: + assert seq.alphabet.__class__ == \ + self.table.nucleotide_alphabet.__class__, \ + "cannot translate from given alphabet (have %s, need %s)" %\ + (seq.alphabet, self.table.nucleotide_alphabet) + s = seq.data + letters = [] + append = letters.append + table = self.table.forward_table + n = len(seq) + try: + for i in range(0, n-n%3, 3): + append(table[s[i:i+3]]) + except KeyError: + # Stop at the first codon failure + pass + return Seq.Seq("".join(letters), self.table.protein_alphabet) + + def back_translate(self, seq): + # includes the stop codon + if not isinstance(seq.alphabet, Alphabet.HasStopCodon): + return self._back_translate_no_stop(seq) + assert seq.alphabet.alphabet == self.table.protein_alphabet, \ + "cannot back translate from the given alphabet (%s)" % \ + seq.alphabet.alphabet + s = seq.data + letter = seq.alphabet.stop_symbol + letters = [] + append = letters.append + table = self.table.back_table + for c in seq.data: + if c == letter: + append(table[None]) + else: + append(table[c]) + return Seq.Seq("".join(letters), + self.table.nucleotide_alphabet) + + def _back_translate_no_stop(self, seq): + # does not allow a stop codon + assert seq.alphabet == self.table.protein_alphabet, \ + "cannot back translate from the given alphabet (%s)" % \ + seq.alphabet + s = seq.data + letters = [] + append = letters.append + table = self.table.back_table + for c in seq.data: + append(table[c]) + return Seq.Seq("".join(letters), + self.table.nucleotide_alphabet) + +unambiguous_dna_by_name = {} +for key, value in CodonTable.unambiguous_dna_by_name.items(): + unambiguous_dna_by_name[key] = Translator(value) +unambiguous_dna_by_id = {} +for key, value in CodonTable.unambiguous_dna_by_id.items(): + unambiguous_dna_by_id[key] = Translator(value) + +unambiguous_rna_by_name = {} +for key, value in CodonTable.unambiguous_rna_by_name.items(): + unambiguous_rna_by_name[key] = Translator(value) +unambiguous_rna_by_id = {} +for key, value in CodonTable.unambiguous_rna_by_id.items(): + unambiguous_rna_by_id[key] = Translator(value) + +# XXX Ambiguous - can be done the same except for stop codons! +ambiguous_dna_by_name = {} +for key, value in CodonTable.ambiguous_dna_by_name.items(): + ambiguous_dna_by_name[key] = Translator(value) +ambiguous_dna_by_id = {} +for key, value in CodonTable.ambiguous_dna_by_id.items(): + ambiguous_dna_by_id[key] = Translator(value) + +ambiguous_rna_by_name = {} +for key, value in CodonTable.ambiguous_rna_by_name.items(): + ambiguous_rna_by_name[key] = Translator(value) +ambiguous_rna_by_id = {} +for key, value in CodonTable.ambiguous_rna_by_id.items(): + ambiguous_rna_by_id[key] = Translator(value) diff --git a/binaries/src/globplot/biopython-1.50/Bio/Writer.py b/binaries/src/globplot/biopython-1.50/Bio/Writer.py new file mode 100644 index 0000000..9247de2 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/Writer.py @@ -0,0 +1,17 @@ +"""Part of an old unused and undocumented sequence writing framework (DEPRECATED).""" + +import warnings +warnings.warn("Bio.Writer and Bio.writer.* are deprecated. If you do use"\ + +" these modules, please get in touch via the mailing list or"\ + +" bugzilla to avoid their permanent removal from Biopython.", \ + DeprecationWarning) + +class Writer: + def __init__(self, outfile): + self.outfile = outfile + def writeHeader(self): + pass + def write(self, record): + pass + def writeFooter(self): + pass diff --git a/binaries/src/globplot/biopython-1.50/Bio/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/__init__.py new file mode 100644 index 0000000..dd350ee --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2000 by Jeffrey Chang. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. +"""Collection of modules for dealing with biological data in Python. + +The Biopython Project is an international association of developers +of freely available Python tools for computational molecular biology. + +http://biopython.org +""" + +__version__ = "1.50" + +class MissingExternalDependencyError(Exception): + pass diff --git a/binaries/src/globplot/biopython-1.50/Bio/__init__.pyc b/binaries/src/globplot/biopython-1.50/Bio/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1249ba9c1be33645e6947a3eb9962cc4d15da28 GIT binary patch literal 624 zcmYLH!EV$r5OuO;fgr>mAfHe!B&|d*TtM6Ip@LS`mJ55a+<3Avay*eeyKTp2W#!Rd;VR8YvXgQgQA3}T= z;_G@Q%!#-wpQQc27%tE`Xvm_M*vcNw)o@YjQYY^%I^5VsJ83j&X}!E1 zsY72C#g`7`!s0M-c^mK>Hyedjoel?eBWvl1hWB;Na`$p744WdJ@Stba`6<2T log(x+y)\n"; + +static PyObject *cMarkovModel__logadd(PyObject *self, PyObject *args) +{ + PyObject *py_logx, *py_logy; + double logx, logy, minxy; + double sum; + + if(!PyArg_ParseTuple(args, "OO", &py_logx, &py_logy)) + return NULL; + logx = PyNumber_AsDouble(py_logx); + logy = PyNumber_AsDouble(py_logy); + if(PyErr_Occurred()) + return NULL; + + if(logy-logx > 100.0) { + Py_INCREF(py_logy); + return py_logy; + } else if (logx-logy > 100.0) { + Py_INCREF(py_logx); + return py_logx; + } + minxy = (logx < logy) ? logx : logy; + sum = minxy + log(exp(logx-minxy) + exp(logy-minxy)); + return PyFloat_FromDouble(sum); +} + + +/* Module definition stuff */ + +static PyMethodDef CMarkovModelMethods[] = { + {"_logadd", cMarkovModel__logadd, METH_VARARGS, cMarkovModel__logadd__doc__}, + {NULL, NULL} +}; + +static char cMarkovModel__doc__[] = +"This module provides optimized replacement functions for MarkovModel.\n\ +"; + +void initcMarkovModel(void) +{ + Py_InitModule3("cMarkovModel", CMarkovModelMethods, cMarkovModel__doc__); +} + + + diff --git a/binaries/src/globplot/biopython-1.50/Bio/clistfnsmodule.c b/binaries/src/globplot/biopython-1.50/Bio/clistfnsmodule.c new file mode 100644 index 0000000..8d86b44 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/clistfnsmodule.c @@ -0,0 +1,161 @@ +/* Copyright 2000 by Jeffrey Chang. All rights reserved. + * This code is part of the Biopython distribution and governed by its + * license. Please see the LICENSE file that should have been included + * as part of this package. + * + * clistfnsmodule.c + * Created 3 Jun 2000 + */ + +#include "Python.h" +#include + + + + +/************************************** Exported Functions ***********/ + +static char clistfns_count__doc__[] = +"count(items) -> dict of counts of each item\n\ +\n\ +Count the number of times each item appears in a list of data.\n\ +\n\ +"; + +static PyObject *clistfns_count(PyObject *self, PyObject *args) +{ + int i; + PyObject *items, *counts; + PyObject *item, *count, *newcount; + long int current; + + if(!PyArg_ParseTuple(args, "O", &items)) + return NULL; + if(!PySequence_Check(items)) { + PyErr_SetString(PyExc_TypeError, "expected sequence type"); + return NULL; + } + + if(!(counts = PyDict_New())) + return NULL; + + /* Go through the loop, counting how often each item appears. */ + i = 0; + while(1) { + if(!(item = PySequence_GetItem(items, i))) { + PyErr_Clear(); /* clear the exception set by PySequence_GetItem */ + break; /* no more numbers */ + } + + if(!(count = PyDict_GetItem(counts, item))) { + newcount = PyInt_FromLong(1); /* New item, set count to 1 */ + } + else { + current = PyInt_AsLong(count); + newcount = PyInt_FromLong(current+1); + } + + PyDict_SetItem(counts, item, newcount); + Py_DECREF(newcount); + Py_DECREF(item); + if(PyErr_Occurred()) + return NULL; + + i++; + } + + return counts; +} + + +static char clistfns_contents__doc__[] = +"contents(items) -> dict of item -> percentage\n\ +\n\ +Summarize the contents of the list in terms of the percentages of each\n\ +item. For example, if an item appears 3 times in a list with 10 items,\n\ +it is in 0.3 of the list\n\ +\n\ +"; + +static PyObject *clistfns_contents(PyObject *self, PyObject *args) +{ + int i; + PyObject *items, *counts, *percentages; + PyObject *countitems, *countitem; + PyObject *key, *count, *perc; + long c; + double total; + + if(!PyArg_ParseTuple(args, "O", &items)) + return NULL; + if(!PySequence_Check(items)) { + PyErr_SetString(PyExc_TypeError, "expected mapping type"); + return NULL; + } + if((total = PySequence_Length(items)) == -1) { + PyErr_SetString(PyExc_ValueError, "I couldn't get length of item."); + return NULL; + } + + counts = clistfns_count(self, args); + if(!counts || PyErr_Occurred()) + return NULL; + + if(!(percentages = PyDict_New())) { + Py_DECREF(counts); + return NULL; + } + + /* Loop through every element in counts, calculating the probabilities. */ + if(!(countitems = PyMapping_Items(counts))) { + Py_DECREF(counts); + Py_DECREF(percentages); + return NULL; + } + + /* Go through the loop, counting how often each item appears. */ + i = 0; + while(1) { + if(!(countitem = PyList_GetItem(countitems, i))) { + PyErr_Clear(); /* clear the exception set by PyList_GetItem */ + break; /* no more numbers */ + } + key = PyTuple_GetItem(countitem, 0); + count = PyTuple_GetItem(countitem, 1); + c = PyInt_AsLong(count); + perc = PyFloat_FromDouble((double)c / total); + PyDict_SetItem(percentages, key, perc); + Py_DECREF(perc); + if(PyErr_Occurred()) /* PyDict_SetItem failed */ + break; + i++; + } + if(PyErr_Occurred()) { + Py_DECREF(percentages); + percentages = NULL; + } + Py_DECREF(countitems); + Py_DECREF(counts); + + return percentages; +} + + +/************************************** Module definition stuff ******/ + +static PyMethodDef clistfnsMethods[] = { + {"count", clistfns_count, METH_VARARGS, clistfns_count__doc__}, + {"contents", clistfns_contents, METH_VARARGS, clistfns_contents__doc__}, + {NULL, NULL} +}; + +static char clistfns__doc__[] = +"This provides helper functions for the listfns module.\n\ +You should never import this module on its own.\n\ +\n\ +"; + +void initclistfns(void) +{ + (void) Py_InitModule3("clistfns", clistfnsMethods, clistfns__doc__); +} diff --git a/binaries/src/globplot/biopython-1.50/Bio/cmathfnsmodule.c b/binaries/src/globplot/biopython-1.50/Bio/cmathfnsmodule.c new file mode 100644 index 0000000..d3549e2 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/cmathfnsmodule.c @@ -0,0 +1,140 @@ +/* Copyright 2000 by Jeffrey Chang. All rights reserved. + * This code is part of the Biopython distribution and governed by its + * license. Please see the LICENSE file that should have been included + * as part of this package. + * + * cmathfnsmodule.c + * Created 3 Jun 2000 + */ + +#include "Python.h" +#include + +#include "csupport.h" + + + +/************************************** Exported Functions ***********/ + +static char cmathfns_intd__doc__[] = +"intd(x[, digits_after_decimal]) -> int x, rounded\n\ +\n\ +Represent a floating point number with some digits after the\n\ +decimal point as an integer. This is useful when floating point\n\ +comparisons are failing due to precision problems. e.g.\n\ +intd(5.35, 1) -> 54.\n\ +\n\ +"; + +static PyObject *cmathfns_intd( + PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *digits_after_decimal = Py_None; + double x, digits; + double precision; + + static char *kwlist[] = {"x", "digits_after_decimal", NULL}; + if(!PyArg_ParseTupleAndKeywords(args, keywds, "d|O", kwlist, + &x, &digits_after_decimal)) + return NULL; + + if(digits_after_decimal == Py_None) + digits = 0; + else { + digits = PyNumber_AsDouble(digits_after_decimal); + if(PyErr_Occurred()) { + return NULL; + } + } + precision = pow(10, digits); + if(x >= 0) + x = (int)(x * precision + 0.5); + else + x = (int)(x * precision - 0.5); + return PyFloat_FromDouble(x); +} + + + + +static char cmathfns_fcmp__doc__[] = +"fcmp(x, y, precision) -> -1, 0, or 1"; + +static PyObject *cmathfns_fcmp( + PyObject *self, PyObject *args, PyObject *keywds) +{ + double x, y, precision; + int result; + + static char *kwlist[] = {"x", "y", "precision", NULL}; + if(!PyArg_ParseTupleAndKeywords(args, keywds, "ddd", kwlist, + &x, &y, &precision)) + return NULL; + + if(fabs(x-y) < precision) + result = 0; + else if(x < y) + result = -1; + else result = 1; + return PyInt_FromLong(result); +} + + + +static char cmathfns_safe_log__doc__[] = +"safe_log(n, zero=None, neg=None) -> log(n)\n\ +\n\ +Calculate the log of n. If n is 0, returns the value of zero. If n is\n\ +negative, returns the value of neg.\n\ +\n\ +"; + +static PyObject *cmathfns_safe_log( + PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *zero = Py_None, + *neg = Py_None; + double n; + + static char *kwlist[] = {"n", "zero", "neg", NULL}; + + if(!PyArg_ParseTupleAndKeywords(args, keywds, "d|OO", kwlist, + &n, &zero, &neg)) + return NULL; + + if(n < 0) { + Py_INCREF(neg); + return neg; + } else if(n < 1E-100) { + Py_INCREF(zero); + return zero; + } + + return PyFloat_FromDouble(log(n)); +} + + + + +/************************************** Module definition stuff ******/ + +static PyMethodDef cmathfnsMethods[] = { + {"fcmp", (PyCFunction)cmathfns_fcmp, METH_VARARGS|METH_KEYWORDS, + cmathfns_fcmp__doc__}, + {"intd", (PyCFunction)cmathfns_intd, METH_VARARGS|METH_KEYWORDS, + cmathfns_intd__doc__}, + {"safe_log", (PyCFunction)cmathfns_safe_log, METH_VARARGS|METH_KEYWORDS, + cmathfns_safe_log__doc__}, + {NULL, NULL} +}; + +static char cmathfns__doc__[] = +"This provides helper functions for the mathfns module.\n\ +You should never import this module on its own.\n\ +\n\ +"; + +void initcmathfns(void) +{ + (void) Py_InitModule3("cmathfns", cmathfnsMethods, cmathfns__doc__); +} diff --git a/binaries/src/globplot/biopython-1.50/Bio/cstringfnsmodule.c b/binaries/src/globplot/biopython-1.50/Bio/cstringfnsmodule.c new file mode 100644 index 0000000..af098fd --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/cstringfnsmodule.c @@ -0,0 +1,122 @@ +/* Copyright 2000 by Jeffrey Chang. All rights reserved. + * This code is part of the Biopython distribution and governed by its + * license. Please see the LICENSE file that should have been included + * as part of this package. + * + * cstringfnsmodule.c + * Created 7 Jun 2000 + */ + +#include "Python.h" +#include /* memset */ + + +/* Functions in this module. */ + +static char cstringfns_splitany__doc__[] = +"splitany(str [,sep [,maxsplit [,negate]]]) -> list of strings\n\ +\n\ +Split a string. Similar to string.split, except that this considers\n\ +any one of the characters in sep to be a delimiter. If negate is\n\ +true, then everything but sep will be a separator.\n\ +\n\ +"; + +static PyObject *cstringfns_splitany( + PyObject *self, PyObject *args, PyObject *keywds) +{ + int i, prev; + int nsplit, maxsplit=0; + /*int negate=0;*/ + PyObject *py_negate=NULL; + PyObject *strlist, *newstr; + unsigned char *str, + *sep=" \011\012\013\014\015"; /* whitespace */ + char tosplit[256]; + static char *kwlist[] = {"str", "sep", "maxsplit", "negate", NULL}; + + if(!PyArg_ParseTupleAndKeywords(args, keywds, "s|siO", kwlist, + &str, &sep, &maxsplit, &py_negate)) + return NULL; + if(maxsplit < 0) + maxsplit = 1; + /* negate = (py_negate && PyObject_IsTrue(py_negate));*/ + /* XXX NO MORE NEGATE */ + + /* Set the tosplit array to 1 for characters to split on. */ + memset(tosplit, 0, 256); + while(*sep) { + tosplit[(unsigned char)*sep++] = 1; + } + if(py_negate && PyObject_IsTrue(py_negate)) { + for(i=0; i<256; i++) + tosplit[i] = !tosplit[i]; + } + + /* Create a new list to store the variables. */ + if(!(strlist = PyList_New(0))) { + PyErr_SetString(PyExc_SystemError, "I could not create a new list"); + return NULL; + } + + prev = 0; + nsplit = 0; + for(i=0; str[i] && (maxsplit == 0 || nsplit < maxsplit); i++) { + /*if(!(tosplit[(int)str[i]] == !negate)) + continue; */ + if(!tosplit[(int)str[i]]) + continue; + + /* Split the string here. */ + if(!(newstr = PyString_FromStringAndSize(&str[prev], i-prev))) { + PyErr_SetString(PyExc_SystemError, + "I could not create a new string"); + break; + } + if(PyList_Append(strlist, newstr) == -1) { + Py_DECREF(newstr); + break; + } + Py_DECREF(newstr); + prev = i+1; + nsplit++; + } + if(!PyErr_Occurred()) { + i = strlen(str); + /* Add the last one. */ + if(!(newstr = PyString_FromStringAndSize(&str[prev], i-prev))) { + PyErr_SetString(PyExc_SystemError, + "I could not create a new string"); + } else { + PyList_Append(strlist, newstr); + Py_DECREF(newstr); + } + } else { + Py_DECREF(strlist); + return NULL; + } + + + return strlist; +} + + + +/* Module definition stuff */ + +static PyMethodDef cstringfnsMethods[] = { + {"splitany", (PyCFunction)cstringfns_splitany, METH_VARARGS|METH_KEYWORDS, + cstringfns_splitany__doc__}, + {NULL, NULL} +}; + +static char cstringfns__doc__[] = +"This provides helper functions for the stringfns module.\n\ +You should never import this module on its own.\n\ +\n\ +"; + +void initcstringfns(void) +{ + (void) Py_InitModule3("cstringfns", cstringfnsMethods, cstringfns__doc__); +} diff --git a/binaries/src/globplot/biopython-1.50/Bio/csupport.c b/binaries/src/globplot/biopython-1.50/Bio/csupport.c new file mode 100644 index 0000000..981ba63 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/csupport.c @@ -0,0 +1,30 @@ +/* Copyright 2002 by Jeffrey Chang. All rights reserved. + * This code is part of the Biopython distribution and governed by its + * license. Please see the LICENSE file that should have been included + * as part of this package. + * + * csupport.c + * Created 27 January 2002 + * + * Miscellaneous useful C functions not to be exported as a python + * module. + * + */ + +#include "Python.h" + + +/* Return a PyNumber as a double. + * Raises a TypeError if I can't do it. + */ +double PyNumber_AsDouble(PyObject *py_num) +{ + double val; + PyObject *floatobj; + + if((floatobj = PyNumber_Float(py_num)) == NULL) + return(0.0); + val = PyFloat_AsDouble(floatobj); + Py_DECREF(floatobj); + return val; +} diff --git a/binaries/src/globplot/biopython-1.50/Bio/csupport.h b/binaries/src/globplot/biopython-1.50/Bio/csupport.h new file mode 100644 index 0000000..9b8eefb --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/csupport.h @@ -0,0 +1,2 @@ + +double PyNumber_AsDouble(PyObject *py_num); diff --git a/binaries/src/globplot/biopython-1.50/Bio/distance.py b/binaries/src/globplot/biopython-1.50/Bio/distance.py new file mode 100644 index 0000000..4bec5cb --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/distance.py @@ -0,0 +1,35 @@ +""" +This module provides code for various distance measures. + +Functions: +euclidean Euclidean distance between two points +euclidean_py Pure Python implementation of euclidean. + +""" +# XXX cosine distance + +import warnings +warnings.warn("Bio.distance is deprecated. If you use this module, please notify the Biopython developers at biopython-dev@biopython.org", DeprecationWarning) + +from numpy import * + +def euclidean(x, y): + """euclidean(x, y) -> euclidean distance between x and y""" + if len(x) != len(y): + raise ValueError("vectors must be same length") + #return sqrt(sum((x-y)**2)) + # Optimization by John Corradi (JCorradi@msn.com) + d = x-y + return sqrt(dot(d, d)) + +def euclidean_py(x, y): + """euclidean_py(x, y) -> euclidean distance between x and y""" + # lightly modified from implementation by Thomas Sicheritz-Ponten. + # This works faster than the Numeric implementation on shorter + # vectors. + if len(x) != len(y): + raise ValueError("vectors must be same length") + sum = 0 + for i in range(len(x)): + sum += (x[i]-y[i])**2 + return sqrt(sum) diff --git a/binaries/src/globplot/biopython-1.50/Bio/kNN.py b/binaries/src/globplot/biopython-1.50/Bio/kNN.py new file mode 100644 index 0000000..4bd2d8d --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/kNN.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python + +""" +This module provides code for doing k-nearest-neighbors classification. + +k Nearest Neighbors is a supervised learning algorithm that classifies +a new observation based the classes in its surrounding neighborhood. + +Glossary: +distance The distance between two points in the feature space. +weight The importance given to each point for classification. + + +Classes: +kNN Holds information for a nearest neighbors classifier. + + +Functions: +train Train a new kNN classifier. +calculate Calculate the probabilities of each class, given an observation. +classify Classify an observation into a class. + + Weighting Functions: +equal_weight Every example is given a weight of 1. + +""" + +#TODO - Remove this work around once we drop python 2.3 support +try: + set = set +except NameError: + from sets import Set as set + +import numpy + +class kNN: + """Holds information necessary to do nearest neighbors classification. + + Members: + classes Set of the possible classes. + xs List of the neighbors. + ys List of the classes that the neighbors belong to. + k Number of neighbors to look at. + + """ + def __init__(self): + """kNN()""" + self.classes = set() + self.xs = [] + self.ys = [] + self.k = None + +def equal_weight(x, y): + """equal_weight(x, y) -> 1""" + # everything gets 1 vote + return 1 + +def train(xs, ys, k, typecode=None): + """train(xs, ys, k) -> kNN + + Train a k nearest neighbors classifier on a training set. xs is a + list of observations and ys is a list of the class assignments. + Thus, xs and ys should contain the same number of elements. k is + the number of neighbors that should be examined when doing the + classification. + + """ + knn = kNN() + knn.classes = set(ys) + knn.xs = numpy.asarray(xs, typecode) + knn.ys = ys + knn.k = k + return knn + +def calculate(knn, x, weight_fn=equal_weight, distance_fn=None): + """calculate(knn, x[, weight_fn][, distance_fn]) -> weight dict + + Calculate the probability for each class. knn is a kNN object. x + is the observed data. weight_fn is an optional function that + takes x and a training example, and returns a weight. distance_fn + is an optional function that takes two points and returns the + distance between them. If distance_fn is None (the default), the + Euclidean distance is used. Returns a dictionary of the class to + the weight given to the class. + + """ + x = numpy.asarray(x) + + order = [] # list of (distance, index) + if distance_fn: + for i in range(len(knn.xs)): + dist = distance_fn(x, knn.xs[i]) + order.append((dist, i)) + else: + # Default: Use a fast implementation of the Euclidean distance + temp = numpy.zeros(len(x)) + # Predefining temp allows reuse of this array, making this + # function about twice as fast. + for i in range(len(knn.xs)): + temp[:] = x - knn.xs[i] + dist = numpy.sqrt(numpy.dot(temp,temp)) + order.append((dist, i)) + order.sort() + + # first 'k' are the ones I want. + weights = {} # class -> number of votes + for k in knn.classes: + weights[k] = 0.0 + for dist, i in order[:knn.k]: + klass = knn.ys[i] + weights[klass] = weights[klass] + weight_fn(x, knn.xs[i]) + + return weights + +def classify(knn, x, weight_fn=equal_weight, distance_fn=None): + """classify(knn, x[, weight_fn][, distance_fn]) -> class + + Classify an observation into a class. If not specified, weight_fn will + give all neighbors equal weight. distance_fn is an optional function + that takes two points and returns the distance between them. If + distance_fn is None (the default), the Euclidean distance is used. + """ + weights = calculate( + knn, x, weight_fn=weight_fn, distance_fn=distance_fn) + + most_class = None + most_weight = None + for klass, weight in weights.items(): + if most_class is None or weight > most_weight: + most_class = klass + most_weight = weight + return most_class diff --git a/binaries/src/globplot/biopython-1.50/Bio/listfns.py b/binaries/src/globplot/biopython-1.50/Bio/listfns.py new file mode 100644 index 0000000..ff7db6f --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/listfns.py @@ -0,0 +1,158 @@ +# Copyright 2000 by Jeffrey Chang. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""This provides useful general functions for working with lists (OBSOLETE). + +This module and its C code equivalent are considered to be obsolete, and +are likely to be deprecated in a future release of Biopython, before being +removed. Please get in touch via the mailing list if this will affect you. +Many of these functions can be avoided using the python set object. + +Functions: +asdict Make the list into a dictionary (for fast testing of membership). +items Get one of each item in a list. +count Count the number of times each item appears. +contents Calculate percentage each item appears in a list. +itemindex Make an index of the items in the list. +intersection Get the items in common between 2 lists. +difference Get the items in 1 list, but not the other. +indexesof Get a list of the indexes of some items in a list. +take Take some items from a list. + +""" + +def asdict(l): + """asdict(l) -> dictionary + + Return a dictionary where the keys are the items in the list, with + arbitrary values. This is useful for quick testing of membership. + + """ + return count(l) + +def items(l): + """items(l) -> list of items + + Generate a list of one of each item in l. The items are returned + in arbitrary order. + + """ + try: + return asdict(l).keys() + except TypeError, x: + if str(x).find("unhashable") == -1: + raise + # asdict failed because l is unhashable. Back up to a naive + # implementation. + l = l[:] + l.sort() + i = 0 + while i < len(l)-1: + if l[i] == l[i+1]: + del l[i] + else: + i += 1 + return l + +def count(items): + """count(items) -> dict of counts of each item + + Count the number of times each item appears in a list of data. + + """ + c = {} + for i in items: + c[i] = c.get(i, 0) + 1 + return c + +def contents(items): + """contents(items) -> dict of item:percentage + + Summarize the contents of the list in terms of the percentages of each + item. For example, if an item appears 3 times in a list with 10 items, + it is in 0.3 of the list. + + """ + counts = count(items) + l = float(len(items)) + contents = {} + for i, c in counts.items(): + contents[i] = c / l + return contents + +def intersection(l1, l2): + """intersection(l1, l2) -> list of common items + + Return a list of the items in both l1 and l2. The list is in + arbitrary order. + + """ + inter = [] + words1 = count(l1) + for w in l2: + if words1.has_key(w): + inter.append(w) + del words1[w] # don't add the same word twice + return inter + +def difference(l1, l2): + """difference(l1, l2) -> list of items in l1, but not l2 + + Return a list of the items in l1, but not l2. The list is in + arbitrary order. + + """ + diff = [] + words2 = count(l2) + for w in l1: + if not words2.has_key(w): + diff.append(w) + words2[w] = 1 # don't add the same word twice + return diff + +def itemindex(l): + """itemindex(l) -> dict of item : index of item + + Make an index of the items in the list. The dictionary contains + the items in the list as the keys, and the index of the first + occurrence of the item as the value. + + """ + dict = {} + for i in range(len(l)): + if not dict.has_key(l[i]): + dict[l[i]] = i + return dict + +def indexesof(l, fn, opposite=0): + """indexesof(l, fn) -> list of indexes + + Return a list of indexes i where fn(l[i]) is true. + + """ + indexes = [] + for i in range(len(l)): + f = fn(l[i]) + if (not opposite and f) or (opposite and not f): + indexes.append(i) + return indexes + +def take(l, indexes): + """take(l, indexes) -> list of just the indexes from l""" + items = [] + for i in indexes: + items.append(l[i]) + return items + +def take_byfn(l, fn, opposite=0): + indexes = indexesof(l, fn, opposite=opposite) + return take(l, indexes) + +# Try and load C implementations of functions. If I can't, +# then just ignore and use the pure python implementations. +try: + from clistfns import * +except ImportError: + pass diff --git a/binaries/src/globplot/biopython-1.50/Bio/mathfns.py b/binaries/src/globplot/biopython-1.50/Bio/mathfns.py new file mode 100644 index 0000000..4474232 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/mathfns.py @@ -0,0 +1,100 @@ +# Copyright 2000 by Jeffrey Chang. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""This provides useful general math tools (DEPRECATED). + +This module and its C code equivalent are considered to be deprecated, and +are likely to be removed in a future release of Biopython. Please get in +touch via the mailing list if this will affect you. + +Functions: +fcmp Compare two floating point numbers, up to a specified precision. +intd Represent a floating point number as an integer. +safe_log log, but returns an arbitrarily small number for log(0). +safe_exp exp, but returns a large or small number instead of overflows. + +""" +import warnings +warnings.warn("Bio.mathfns and its C code equivalent Bio.cmathfns are" \ + +" deprecated, and will be removed in a future release of"\ + +" Biopython. If you want to continue to use this code,"\ + +" please get in contact with the Biopython developers via"\ + +" the mailing lists to avoid its permanent removal from"\ + +" Biopython.", \ + DeprecationWarning) + +import math + +def fcmp(x, y, precision): + """fcmp(x, y, precision) -> -1, 0, or 1""" + if math.fabs(x-y) < precision: + return 0 + elif x < y: + return -1 + return 1 + +def intd(x, digits_after_decimal=0): + """intd(x[, digits_after_decimal]) -> int x, rounded + + Represent a floating point number with some digits after the + decimal point as an integer. This is useful when floating point + comparisons are failing due to precision problems. e.g. + intd(5.35, 1) -> 54. + + """ + precision = 10.**digits_after_decimal + if x >= 0: + x = int(x * precision + 0.5) + else: + x = int(x * precision - 0.5) + return x + +def safe_log(n, zero=None, neg=None): + """safe_log(n, zero=None, neg=None) -> log(n) + + Calculate the log of n. If n is 0, returns the value of zero. If n is + negative, returns the value of neg. + + """ + if n < 0: + return neg + elif n < 1E-100: + return zero + return math.log(n) + +LOG2 = math.log(2) +def safe_log2(n, zero=None, neg=None): + """safe_log2(n, zero=None, neg=None) -> log(n) + + Calculate the log base 2 of n. If n is 0, returns the value of + zero. If n is negative, returns the value of neg. + + """ + l = safe_log(n, zero=zero, neg=neg) + if l is None: + return l + return l/LOG2 + +def safe_exp(n, under=None, over=None): + """safe_exp(n, under=None, over=None) -> e**n + + Guaranteed not to overflow. Instead of overflowing, it returns + the values of 'under' for underflows or 'over' for overflows. + + """ + try: + return math.exp(n) + except OverflowError: + if n < 0: + return under + return over + raise "How did I get here?" + +# Try and load C implementations of functions. If I can't, +# then just ignore and use the pure python implementations. +try: + from cmathfns import * +except ImportError: + pass diff --git a/binaries/src/globplot/biopython-1.50/Bio/stringfns.py b/binaries/src/globplot/biopython-1.50/Bio/stringfns.py new file mode 100644 index 0000000..24e461f --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/stringfns.py @@ -0,0 +1,90 @@ +# Copyright 2000 by Jeffrey Chang. All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""This provides useful general functions for working with strings (DEPRECATED). + +This module and its C code equivalent are considered to be deprecated, and +are likely to be removed in a future release of Biopython. Please get in +touch via the mailing list if this will affect you. + +Functions: +splitany Split a string using many delimiters. +find_anychar Find one of a list of characters in a string. +rfind_anychar Find one of a list of characters in a string, from end to start. + +""" +import warnings +warnings.warn("Bio.stringfns and its C code equivalent Bio.cstringfns are" \ + +" deprecated, and will be removed in a future release of"\ + +" Biopython. If you want to continue to use this code,"\ + +" please get in contact with the Biopython developers via"\ + +" the mailing lists to avoid its permanent removal from"\ + +" Biopython.", \ + DeprecationWarning) + +def splitany(s, sep=" \011\012\013\014\015", maxsplit=None, negate=0): + """splitany(s [,sep [,maxsplit [,negate]]]) -> list of strings + + Split a string. Similar to string.split, except that this considers + any one of the characters in sep to be a delimiter. If negate is + true, then everything but sep will be a separator. + + """ + strlist = [] + prev = 0 + for i in range(len(s)): + if maxsplit is not None and len(strlist) >= maxsplit: + break + if (s[i] in sep) == (not negate): + strlist.append(s[prev:i]) + prev = i+1 + strlist.append(s[prev:]) + return strlist + +def find_anychar(string, chars, index=None, negate=0): + """find_anychar(string, chars[, index]) -> index of a character or -1 + + Find a character in string. chars is a list of characters to look + for. Return the index of the first occurrence of any of the + characters, or -1 if not found. index is the index where the + search should start. By default, I search from the beginning of + the string. + + """ + if index is None: + index = 0 + while index < len(string) and \ + ((not negate and string[index] not in chars) or + (negate and string[index] in chars)): + index += 1 + if index == len(string): + return -1 + return index + +def rfind_anychar(string, chars, index=None, negate=0): + """rfind_anychar(string, chars[, index]) -> index of a character or -1 + + Find a character in string, looking from the end to the start. + chars is a list of characters to look for. Return the index of + the first occurrence of any of the characters, or -1 if not found. + index is the index where the search should start. By default, I + search from the end of the string. + + """ + if index is None: + index = len(string)-1 + while index >= 0 and \ + ((not negate and string[index] not in chars) or + (negate and string[index] in chars)): + index -= 1 + # If not found, index will already be -1. + return index + +# Try and load C implementations of functions. If I can't, +# then just ignore and use the pure python implementations. +try: + from cstringfns import * +except ImportError: + pass diff --git a/binaries/src/globplot/biopython-1.50/Bio/trie.c b/binaries/src/globplot/biopython-1.50/Bio/trie.c new file mode 100644 index 0000000..d73d784 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/trie.c @@ -0,0 +1,778 @@ +#include /* printf */ +#include /* malloc */ +#include /* strcmp, strlen */ + +#include "trie.h" + +/* The following is necessary to make sure that trie.pyd won't link + * to msvcrt.dll in addition to msvcr71.dll on Windows. + * See Bug #1767 on Bugzilla. + */ +#ifdef __MINGW32__ +# define strdup _strdup +#endif + +struct _Transition; /* Forward declaration, needed in _Trie. */ + + +/* _Trie is a recursive data structure. A _Trie contains zero or more + * _Transitions that lead to more _Tries. The transitions are stored + * in alphabetical order of the suffix member of the data structure. + * _Trie also contains a pointer called value where the user can store + * arbitrary data. If value is NULL, then no data is stored here. + */ +struct _Trie { + struct _Transition *transitions; + unsigned char num_transitions; + void *value; /* specified by user, never freed or allocated by me! */ +}; + +/* _Transition holds information about the transitions leading from + * one _Trie to another. The trie structure here is different from + * typical ones, because the transitions between nodes can contain + * strings of arbitrary length, not just single characters. Suffix is + * the string that is matched from one node to the next. + */ +typedef struct _Transition { + unsigned char *suffix; + Trie next; +} *Transition; + + +#define MAX_KEY_LENGTH 1000 +static unsigned char KEY[MAX_KEY_LENGTH]; + + +Trie Trie_new(void) { + Trie trie; + + if(!(trie = (Trie)malloc(sizeof(struct _Trie)))) + return NULL; + trie->transitions = NULL; + trie->num_transitions = 0; + trie->value = NULL; + return trie; +} + +int Trie_set(Trie trie, const unsigned char *key, const void *value) { + int i; + Transition transition=NULL; + unsigned char *suffix=NULL; + int retval = 0; + int first, last, mid; + + if(!key[0]) { + trie->value = (void *)value; + return 0; + } + + /* Insert the key in alphabetical order. Do a binary search to + find the proper place. */ + first = 0; + last = trie->num_transitions-1; + i = -1; + while(first <= last) { + mid = (first+last)/2; + transition = &trie->transitions[mid]; + suffix = transition->suffix; + if(key[0] < suffix[0]) + last = mid-1; + else if(key[0] > suffix[0]) + first = mid+1; + else { + i = mid; + break; + } + } + + /* If no place was found for it, then the indexes will be in the + order last,first. Place it at index first. */ + if(i == -1) + i = first; + + /* If nothing matches, then insert a new trie here. */ + if((i >= trie->num_transitions) || (key[0] != suffix[0])) { + unsigned char *new_suffix=NULL; + Trie newtrie=NULL; + Transition new_transitions=NULL; + + /* Create some variables for the new transition. I'm going to + allocate these first so that if I can detect memory errors + before I mess up the data structure of the transitions. + */ + if(!(new_suffix = (unsigned char *)strdup(key))) + goto insert_memerror; + if(!(newtrie = Trie_new())) + goto insert_memerror; + + /* Create some space for the next transition. Allocate some + memory and shift the old transitions over to make room for + this one. + */ + if(!(new_transitions = malloc(sizeof(struct _Transition) * + (trie->num_transitions+1)))) + goto insert_memerror; + memcpy(new_transitions, trie->transitions, + sizeof(struct _Transition)*i); + memcpy(&new_transitions[i+1], &trie->transitions[i], + sizeof(struct _Transition)*(trie->num_transitions-i)); + free(trie->transitions); + trie->transitions = new_transitions; + new_transitions = NULL; + trie->num_transitions += 1; + + /* Initialize the new transition. */ + transition = &trie->transitions[i]; + transition->suffix = new_suffix; + transition->next = newtrie; + transition->next->value = (void *)value; + + if(0) { + insert_memerror: + if(new_transitions) free(new_transitions); + if(newtrie) free(newtrie); + if(new_suffix) free(new_suffix); + return 1; + } + } + /* There are three cases where the key and suffix share some + letters. + 1. suffix is proper substring of key. + 2. key is proper substring of suffix. + 3. neither is proper substring of other. + + For cases 2 and 3, I need to first split up the transition + based on the number of characters shared. Then, I can insert + the rest of the key into the next trie. + */ + else { + /* Count the number of characters shared between key + and suffix. */ + int chars_shared = 0; + while(key[chars_shared] && key[chars_shared] == suffix[chars_shared]) + chars_shared++; + + /* Case 2 or 3, split this sucker! */ + if(chars_shared < strlen(suffix)) { + Trie newtrie=NULL; + unsigned char *new_suffix1=NULL, *new_suffix2=NULL; + + if(!(new_suffix1 = (unsigned char *)malloc(chars_shared+1))) + goto split_memerror; + strncpy(new_suffix1, key, chars_shared); + new_suffix1[chars_shared] = 0; + if(!(new_suffix2 = (unsigned char *)strdup(suffix+chars_shared))) + goto split_memerror; + if(!(newtrie = Trie_new())) + goto split_memerror; + if(!(newtrie->transitions = + (Transition)malloc(sizeof(struct _Transition)))) + goto split_memerror; + newtrie->num_transitions = 1; + newtrie->transitions[0].next = transition->next; + newtrie->transitions[0].suffix = new_suffix2; + + free(transition->suffix); + transition->suffix = new_suffix1; + transition->next = newtrie; + + if(0) { + split_memerror: + if(newtrie && newtrie->transitions) free(newtrie->transitions); + if(newtrie) free(newtrie); + if(new_suffix2) free(new_suffix2); + if(new_suffix1) free(new_suffix1); + return 1; + } + } + retval = Trie_set(transition->next, key+chars_shared, value); + } + + return retval; +} + +void Trie_del(Trie trie) { + int i; + if(!trie) + return; + for(i=0; inum_transitions; i++) { + Transition transition = &trie->transitions[i]; + if(transition->suffix) + free(transition->suffix); + Trie_del(transition->next); + } + free(trie); +} + +void *Trie_get(const Trie trie, const unsigned char *key) { + int first, last, mid; + + if(!key[0]) { + return trie->value; + } + + /* The transitions are stored in alphabetical order. Do a binary + * search to find the proper one. + */ + first = 0; + last = trie->num_transitions-1; + while(first <= last) { + Transition transition; + unsigned char *suffix; + int c; + mid = (first+last)/2; + transition = &trie->transitions[mid]; + suffix = transition->suffix; + /* If suffix is a substring of key, then get the value from + the next trie. + */ + c = strncmp(key, suffix, strlen(suffix)); + if(c < 0) + last = mid-1; + else if(c > 0) + first = mid+1; + else + return Trie_get(transition->next, key+strlen(suffix)); + } + return NULL; +} + + +/* Mutually recursive, so need to make a forward declaration. */ +void +_get_approximate_trie(const Trie trie, const unsigned char *key, const int k, + void (*callback)(const unsigned char *key, + const void *value, + const int mismatches, + void *data), + void *data, + const int mismatches, + unsigned char *current_key, const int max_key + ); + +void +_get_approximate_transition(const unsigned char *key, + const int k, + const Transition transition, + const unsigned char *suffix, + void (*callback)(const unsigned char *key, + const void *value, + const int mismatches, + void *data), + void *data, + const int mismatches, + unsigned char *current_key, const int max_key + ) +{ + int i; + int prev_keylen = strlen(current_key); + + /* Short circuit optimization. If there's too many characters to + possibly be a match, then don't even try to match things. */ + if((int)(strlen(suffix) - strlen(key)) > k) + return; + + /* Match as many characters as possible. */ + i = 0; + while(suffix[i] && (key[i] == suffix[i])) { + i++; + } + /* Check to make sure the key is not too long. BUG: If it is, + fails silently. */ + if((prev_keylen+i) >= max_key) + return; + strncat(current_key, suffix, i); + + /* If all the letters in the suffix matched, then move to the + next trie. */ + if(!suffix[i]) { + _get_approximate_trie(transition->next, &key[i], k, callback, data, + mismatches, current_key, max_key); + } + /* Otherwise, try out different kinds of mismatches. */ + else if(k) { + int new_keylen = prev_keylen+i; + + /* Letter replacement, skip the next letter in both the key and + suffix. */ + if((new_keylen+1 < max_key) && key[i] && suffix[i]) { + current_key[new_keylen] = suffix[i]; + current_key[new_keylen+1] = 0; + _get_approximate_transition(&key[i+1], k-1, + transition, &suffix[i+1], + callback, data, + mismatches+1, current_key, max_key); + current_key[new_keylen] = 0; + } + + /* Insertion in key, skip the next letter in the key. */ + if(key[i]) { + _get_approximate_transition(&key[i+1], k-1, + transition, &suffix[i], + callback, data, + mismatches+1, current_key, max_key); + } + + /* Deletion from key, skip the next letter in the suffix. */ + if((new_keylen+1 < max_key) && suffix[i]) { + current_key[new_keylen] = suffix[i]; + current_key[new_keylen+1] = 0; + _get_approximate_transition(&key[i], k-1, + transition, &suffix[i+1], + callback, data, + mismatches+1, current_key, max_key); + current_key[new_keylen] = 0; + } + } + current_key[prev_keylen] = 0; +} + +void +_get_approximate_trie(const Trie trie, const unsigned char *key, const int k, + void (*callback)(const unsigned char *key, + const void *value, + const int mismatches, + void *data), + void *data, + const int mismatches, + unsigned char *current_key, const int max_key + ) +{ + int i; + + /* If there's no more key to match, then I'm done. */ + if(!key[0]) { + if(trie->value) + (*callback)(current_key, trie->value, mismatches, data); + } + /* If there are no more mismatches allowed, then fall back to the + faster Trie_get. */ + else if(!k) { + void *value = Trie_get(trie, key); + if(value) { + int l = strlen(current_key); + /* Make sure I have enough space for the full key. */ + if(l + strlen(key) < max_key) { + strcat(current_key, key); + (*callback)(current_key, value, mismatches, data); + current_key[l] = 0; + } + /* BUG: Ran out of space for the key. This fails + silently, but should signal an error. */ + } + } + /* If there are no more transitions, then all the characters left + in the key are mismatches. */ + else if(!trie->num_transitions) { + if(trie->value && (strlen(key) <= k)) { + (*callback)(current_key, trie->value, + mismatches+strlen(key), data); + } + } + /* Otherwise, try to match each of the transitions. */ + else { + for(i=0; inum_transitions; i++) { + Transition transition = &trie->transitions[i]; + unsigned char *suffix = transition->suffix; + _get_approximate_transition(key, k, transition, suffix, + callback, data, + mismatches, current_key, max_key); + } + } + +} + + +void +Trie_get_approximate(const Trie trie, const unsigned char *key, const int k, + void (*callback)(const unsigned char *key, + const void *value, + const int mismatches, + void *data), + void *data + ) +{ + KEY[0] = 0; + _get_approximate_trie(trie, key, k, callback, data, 0, KEY,MAX_KEY_LENGTH); +} + +int Trie_len(const Trie trie) +{ + int i; + int length = 0; + + if(!trie) + return 0; + if(trie->value) + length += 1; + for(i=0; inum_transitions; i++) { + length += Trie_len(trie->transitions[i].next); + } + return length; +} + +int Trie_has_key(const Trie trie, const unsigned char *key) +{ + return Trie_get(trie, key) != NULL; +} + +int Trie_has_prefix(const Trie trie, const unsigned char *prefix) +{ + int first, last, mid; + + if(!prefix[0]) { + return 1; + } + + /* The transitions are stored in alphabetical order. Do a binary + * search to find the proper one. + */ + first = 0; + last = trie->num_transitions-1; + while(first <= last) { + Transition transition; + unsigned char *suffix; + int suffixlen, prefixlen, minlen; + int c; + mid = (first+last)/2; + transition = &trie->transitions[mid]; + suffix = transition->suffix; + suffixlen = strlen(suffix); + prefixlen = strlen(prefix); + minlen = (suffixlen < prefixlen) ? suffixlen : prefixlen; + c = strncmp(prefix, suffix, minlen); + if(c < 0) + last = mid-1; + else if(c > 0) + first = mid+1; + else + return Trie_has_prefix(transition->next, prefix+minlen); + } + return 0; +} + +static void +_iterate_helper(const Trie trie, + void (*callback)(const unsigned char *key, + const void *value, + void *data), + void *data, + unsigned char *current_key, const int max_key) +{ + int i; + if(trie->value) + (*callback)(current_key, trie->value, data); + for(i=0; inum_transitions; i++) { + Transition transition = &trie->transitions[i]; + unsigned char *suffix = transition->suffix; + int keylen = strlen(current_key); + + if(keylen + strlen(suffix) >= max_key) { + /* BUG: This will fail silently. It should raise some + sort of error. */ + continue; + } + strcat(current_key, suffix); + _iterate_helper(transition->next, callback, data, + current_key, max_key); + current_key[keylen] = 0; + } +} + +void +Trie_iterate(const Trie trie, + void (*callback)(const unsigned char *key, + const void *value, + void *data), + void *data) +{ + KEY[0] = 0; + _iterate_helper(trie, callback, data, KEY, MAX_KEY_LENGTH); +} + +static void +_with_prefix_helper(const Trie trie, const unsigned char *prefix, + void (*callback)(const unsigned char *key, + const void *value, + void *data), + void *data, + unsigned char *current_key, const int max_key) +{ + int first, last, mid; + + if(!prefix[0]) { + _iterate_helper(trie, callback, data, current_key, max_key); + return; + } + + /* The transitions are stored in alphabetical order. Do a binary + * search to find the proper one. + */ + first = 0; + last = trie->num_transitions-1; + while(first <= last) { + Transition transition; + unsigned char *suffix; + int suffixlen, prefixlen, minlen; + int c; + mid = (first+last)/2; + transition = &trie->transitions[mid]; + suffix = transition->suffix; + suffixlen = strlen(suffix); + prefixlen = strlen(prefix); + minlen = (suffixlen < prefixlen) ? suffixlen : prefixlen; + c = strncmp(prefix, suffix, minlen); + if(c < 0) + last = mid-1; + else if(c > 0) + first = mid+1; + else { + int keylen = strlen(current_key); + if(keylen + minlen >= max_key) { + /* BUG: This will fail silently. It should raise some + sort of error. */ + break; + } + strncat(current_key, suffix, minlen); + _with_prefix_helper(transition->next, prefix+minlen, + callback, data, current_key, max_key); + current_key[keylen] = 0; + break; + } + } +} + +void +Trie_with_prefix(const Trie trie, const unsigned char *prefix, + void (*callback)(const unsigned char *key, + const void *value, + void *data), + void *data + ) +{ + KEY[0] = 0; + _with_prefix_helper(trie, prefix, callback, data, KEY, MAX_KEY_LENGTH); +} + + + +/* Need to declare _serialize_transition here so it can be called from + _serialize_trie. */ +int _serialize_transition(const Transition transition, + int (*write)(const void *towrite, const int length, + void *data), + int (*write_value)(const void *value, void *data), + void *data); + +/* This library also provides code for flattening tries so that they + * can be saved and read back in later. The format of a serialized + * trie is: + * TYPE NBYTES DESCRIPTION + * byte 1 Whether or not there is a value + * variable variable If there is a value, let the client store it. + * byte 1 Number of transitions for this Trie. + * transition variable + * int 4 Number of characters in the suffix. + * suffix variable the suffix for this transition + * byte 1 Whether or not there is a trie + * trie variable Recursively points to another trie. + * + * The number of bytes and the endian may vary from platform to + * platform. + */ + +int _serialize_trie(const Trie trie, + int (*write)(const void *towrite, const int length, + void *data), + int (*write_value)(const void *value, void *data), + void *data) +{ + int i; + unsigned char has_value; + + has_value = (trie->value != NULL); + if(!(*write)(&has_value, sizeof(has_value), data)) + return 0; + if(has_value) { + if(!(*write_value)(trie->value, data)) + return 0; + } + + if(!(*write)(&trie->num_transitions, sizeof(trie->num_transitions), data)) + return 0; + for(i=0; inum_transitions; i++) { + if(!_serialize_transition(&trie->transitions[i], + write, write_value, data)) + return 0; + } + + return 1; +} + +int _serialize_transition(const Transition transition, + int (*write)(const void *towrite, const int length, + void *data), + int (*write_value)(const void *value, void *data), + void *data) +{ + int suffixlen; + unsigned char has_trie; + + suffixlen = strlen(transition->suffix); + if(!(*write)(&suffixlen, sizeof(suffixlen), data)) + return 0; + if(!(*write)(transition->suffix, suffixlen, data)) + return 0; + + has_trie = (transition->next != NULL); + if(!(*write)(&has_trie, sizeof(has_trie), data)) + return 0; + if(has_trie) { + if(!_serialize_trie(transition->next, write, write_value, data)) + return 0; + } + return 1; +} + +int Trie_serialize(const Trie trie, + int (*write)(const void *towrite, const int length, + void *data), + int (*write_value)(const void *value, void *data), + void *data) +{ + int success = _serialize_trie(trie, write, write_value, data); + (*write)(NULL, 0, data); + return success; +} + +int _deserialize_transition(Transition transition, + int (*read)(void *wasread, const int length, + void *data), + void *(*read_value)(void *data), + void *data); + +int _deserialize_trie(Trie trie, + int (*read)(void *wasread, const int length, void *data), + void *(*read_value)(void *data), + void *data) +{ + int i; + unsigned char has_value; + + if(!(*read)(&has_value, sizeof(has_value), data)) + goto _deserialize_trie_error; + if(has_value != 0 && has_value != 1) + goto _deserialize_trie_error; + if(has_value) { + if(!(trie->value = (*read_value)(data))) + goto _deserialize_trie_error; + } + if(!(*read)(&trie->num_transitions, sizeof(trie->num_transitions), data)) + goto _deserialize_trie_error; + if(!(trie->transitions = + malloc(trie->num_transitions*sizeof(struct _Transition)))) + goto _deserialize_trie_error; + for(i=0; inum_transitions; i++) { + if(!_deserialize_transition(&trie->transitions[i], + read, read_value, data)) + goto _deserialize_trie_error; + } + return 1; + + _deserialize_trie_error: + trie->num_transitions = 0; + if(trie->transitions) { + free(trie->transitions); + trie->transitions = NULL; + } + trie->value = NULL; + return 0; +} + +int _deserialize_transition(Transition transition, + int (*read)(void *wasread, const int length, + void *data), + void *(*read_value)(void *data), + void *data) +{ + int suffixlen; + unsigned char has_trie; + + if(!(*read)(&suffixlen, sizeof(suffixlen), data)) + goto _deserialize_transition_error; + if(suffixlen < 0 || suffixlen >= MAX_KEY_LENGTH) + goto _deserialize_transition_error; + if(!(*read)(KEY, suffixlen, data)) + goto _deserialize_transition_error; + KEY[suffixlen] = 0; + if(!(transition->suffix = (unsigned char *)strdup(KEY))) + goto _deserialize_transition_error; + if(!(*read)(&has_trie, sizeof(has_trie), data)) + goto _deserialize_transition_error; + if(has_trie != 0 && has_trie != 1) + goto _deserialize_transition_error; + if(has_trie) { + transition->next = Trie_new(); + if(!_deserialize_trie(transition->next, read, read_value, data)) + goto _deserialize_transition_error; + } + return 1; + + _deserialize_transition_error: + if(transition->suffix) { + free(transition->suffix); + transition->suffix = NULL; + } + if(transition->next) { + Trie_del(transition->next); + transition->next = NULL; + } + return 0; +} + +Trie Trie_deserialize(int (*read)(void *wasread, const int length, void *data), + void *(*read_value)(void *data), + void *data) +{ + Trie trie = Trie_new(); + if(!_deserialize_trie(trie, read, read_value, data)) { + Trie_del(trie); + return NULL; + } + return trie; +} + +void test(void) { + Trie trie; + + printf("Hello world!\n"); + + trie = Trie_new(); + printf("New trie %p\n", trie); + Trie_set(trie, "hello world", "s1"); + Trie_set(trie, "bye", "s2"); + Trie_set(trie, "hell sucks", "s3"); + Trie_set(trie, "hebee", "s4"); + + printf("%s\n", (char *)Trie_get(trie, "hello world")); + printf("%s\n", (char *)Trie_get(trie, "bye")); + printf("%s\n", (char *)Trie_get(trie, "hell sucks")); + printf("%s\n", (char *)Trie_get(trie, "hebee")); + + Trie_set(trie, "blah", "s5"); + printf("%s\n", (char *)Trie_get(trie, "blah")); + + printf("%p\n", Trie_get(trie, "foobar")); + printf("%d\n", Trie_len(trie)); + + Trie_set(trie, "blah", "snew"); + printf("%s\n", (char *)Trie_get(trie, "blah")); + + Trie_del(trie); +} + +#if 0 +int main() { + test(); +} +#endif diff --git a/binaries/src/globplot/biopython-1.50/Bio/trie.h b/binaries/src/globplot/biopython-1.50/Bio/trie.h new file mode 100644 index 0000000..eb85549 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/trie.h @@ -0,0 +1,129 @@ +typedef struct _Trie *Trie; + + + +/* Trie_new + * -------- + * Create a new trie. Return a Trie structure, which is an abstract + * data structure. The client should not have to know about the + * details of this structure. When finished, each Trie should be + * freed with Trie_del. + */ +Trie Trie_new(void); + + +/* Trie_del + * -------- + * Free a Trie data structure. + */ +void Trie_del(Trie trie); + + +/* Trie_set + * -------- + * Set a string in the Trie to some value. Returns a 0 if the + * function succeeded. + */ +int Trie_set(Trie trie, const unsigned char *key, const void *value); + +/* Trie_get + * -------- + * Lookup whether a key exists in the Trie. Returns the value that + * was previous set in the Trie, or NULL if it doesn't exist. + */ +void *Trie_get(const Trie trie, const unsigned char *key); + + +/* Trie_get_approximate + * -------------------- + * Lookup whether a key exists in the Trie, allowing for mismatches to + * the dictionary. Passes back values using a callback function. + */ +void +Trie_get_approximate(const Trie trie, const unsigned char *key, const int k, + void (*callback)(const unsigned char *key, + const void *value, + const int mismatches, + void *data), + void *data + ); + +/* Trie_len + * -------- + * Return the number of strings in the trie. + */ +int Trie_len(const Trie trie); + + +/* Trie_has_key + * ------------ + * Return whether a key exists in the trie. + */ +int Trie_has_key(const Trie trie, const unsigned char *key); + + +/* Trie_has_prefix + * --------------- + * Return whether a string is a prefix of a key in the trie. + */ +int Trie_has_prefix(const Trie trie, const unsigned char *prefix); + + +/* Trie_with_prefix + * ---------------- + * Iterate over all the keys in the trie that start with a prefix. + */ +void Trie_with_prefix(const Trie trie, const unsigned char *prefix, + void (*callback)(const unsigned char *key, + const void *value, + void *data), + void *data + ); + + +/* Trie_iterate + * ------------ + * Iterate through everything stored in the trie. callback is a + * function that gets called for each thing in the trie. It is called + * in arbitrary order. data is a pointer to some arbitrary data and + * gets passed unchanged to the callback. + */ +void Trie_iterate(const Trie trie, + void (*callback)(const unsigned char *key, + const void *value, + void *data), + void *data + ); + +/* Trie_serialize + * -------------- + * Serialize a tree into a stream of bytes. This function takes a + * callback 'write' that should take a pointer to data and the length + * of the data in bytes. This will be called repeatedly until the + * whole Trie is serialized. When it is done, this function will call + * 'write' with a length of 0. Since the values are handled by the + * client, this function also takes a callback function 'write_value' + * so that the client can serialize their own values. + * + * This function is platform-dependent, so byte streams created on one + * machine may not necessarily port to another. + */ +int Trie_serialize(const Trie trie, + int (*write)(const void *towrite, const int length, + void *data), + int (*write_value)(const void *value, void *data), + void *data); + + + +/* Trie_deserialize + * ---------------- + * Deserialize a tree that was previously serialized with + * Trie_serialize. This function takes a callback 'read' that should + * read 'length' bytes and save it to 'wasread'. 'read_value' should + * read a value and return a pointer to it. 'data' is a pointer that + * will be passed unchanged to 'read' and 'read_value'. + */ +Trie Trie_deserialize(int (*read)(void *wasread, const int length, void *data), + void *(*read_value)(void *data), + void *data); diff --git a/binaries/src/globplot/biopython-1.50/Bio/triefind.py b/binaries/src/globplot/biopython-1.50/Bio/triefind.py new file mode 100644 index 0000000..da862e5 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/triefind.py @@ -0,0 +1,92 @@ +""" +Given a trie, find all occurrences of a word in the trie in a string. + +Like searching a string for a substring, except that the substring is +any word in a trie. + +Functions: +match Find longest key in a trie matching the beginning of the string. +match_all Find all keys in a trie matching the beginning of the string. +find Find keys in a trie matching anywhere in a string. +find_words Find keys in a trie matching whole words in a string. + +""" +import string +import re + +def match(string, trie): + """match(string, trie) -> longest key or None + + Find the longest key in the trie that matches the beginning of the + string. + + """ + longest = None + for i in range(len(string)): + substr = string[:i+1] + if not trie.has_prefix(substr): + break + if trie.has_key(substr): + longest = substr + return longest + +def match_all(string, trie): + """match_all(string, trie) -> list of keys + + Find all the keys in the trie that matches the beginning of the + string. + + """ + matches = [] + for i in range(len(string)): + substr = string[:i+1] + if not trie.has_prefix(substr): + break + if trie.has_key(substr): + matches.append(substr) + return matches + +def find(string, trie): + """find(string, trie) -> list of tuples (key, start, end) + + Find all the keys in the trie that match anywhere in the string. + + """ + results = [] + start = 0 # index to start the search + while start < len(string): + # Look for a match. + keys = match_all(string[start:], trie) + for key in keys: + results.append((key, start, start+len(key))) + start += 1 + return results + +DEFAULT_BOUNDARY_CHARS = string.punctuation + string.whitespace + +def find_words(string, trie): + """find_words(string, trie) -> list of tuples (key, start, end) + + Find all the keys in the trie that match full words in the string. + Word boundaries are defined as any punctuation or whitespace. + + """ + _boundary_re = re.compile(r"[%s]+" % re.escape(DEFAULT_BOUNDARY_CHARS)) + + results = [] + start = 0 # index of word boundary + while start < len(string): + # Look for a match. + keys = match_all(string[start:], trie) + for key in keys: + l = len(key) + # Make sure it ends at a boundary. + if start+l == len(string) or \ + _boundary_re.match(string[start+l]): + results.append((key, start, start+l)) + # Move forward to the next boundary. + m = _boundary_re.search(string, start) + if m is None: + break + start = m.end() + return results diff --git a/binaries/src/globplot/biopython-1.50/Bio/triemodule.c b/binaries/src/globplot/biopython-1.50/Bio/triemodule.c new file mode 100644 index 0000000..a33214b --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/triemodule.c @@ -0,0 +1,661 @@ +#include +#include +#include "trie.h" + +#if PY_VERSION_HEX < 0x02050000 +#define Py_ssize_t int +#endif + + + +staticforward PyTypeObject Trie_Type; + +typedef struct { + PyObject_HEAD + Trie trie; +} trieobject; + +static PyObject* +trie_trie(PyObject* self, PyObject* args) +{ + trieobject* trieobj; + Trie trie; + + if (!PyArg_ParseTuple(args,":trie")) + return NULL; + if(!(trie = Trie_new())) + return PyErr_NoMemory(); + if(!(trieobj = PyObject_New(trieobject, &Trie_Type))) + return NULL; + trieobj->trie = trie; + return (PyObject*)trieobj; +} + +static void +_decref_objects(const unsigned char *key, const void *value, void *data) +{ + Py_DECREF((PyObject *)value); +} + +static void +trie_dealloc(PyObject* self) +{ + trieobject *mp = (trieobject *)self; + Trie_iterate(mp->trie, _decref_objects, NULL); + Trie_del(mp->trie); + PyObject_Del(self); +} + +static Py_ssize_t +trie_length(trieobject *mp) +{ + return Trie_len(mp->trie); +} + +static PyObject * +trie_subscript(trieobject *mp, PyObject *py_key) +{ + unsigned char *key; + PyObject *py_value; + + /* Make sure key is a string. */ + if(!PyString_Check(py_key)) { + PyErr_SetString(PyExc_TypeError, "key must be a string"); + return NULL; + } + key = (unsigned char *)PyString_AS_STRING(py_key); + py_value = (PyObject *)Trie_get(mp->trie, key); + if(py_value == NULL) + PyErr_SetString(PyExc_KeyError, (char *)key); + else + Py_INCREF(py_value); + return py_value; +} + +static int +trie_ass_sub(trieobject *mp, PyObject *py_key, PyObject *py_value) +{ + unsigned char *key; + PyObject *py_prev; + + /* Make sure key is a string. */ + if(!PyString_Check(py_key)) { + PyErr_SetString(PyExc_TypeError, "key must be a string"); + return -1; + } + key = (unsigned char *)PyString_AS_STRING((char *)py_key); + + /* Check to see whether something already exists at that key. If + there's already an object there, then I will have to remove it. + */ + py_prev = (PyObject *)Trie_get(mp->trie, key); + if(py_prev) { + Py_DECREF(py_prev); + } + + /* The client wants to delete a key from a dictionary. The Trie + API doesn't support this, so I will just overwrite it with + NULL. */ + if(!py_value) { + /* If the key doesn't exist, raise a KeyError. */ + if(!py_prev) { + PyErr_SetString(PyExc_KeyError, (char *)key); + return -1; + } + Trie_set(mp->trie, key, NULL); + } + /* The client wants to set a key in the dictionary. */ + else { + Py_INCREF(py_value); + if(Trie_set(mp->trie, key, py_value)) { + PyErr_SetString(PyExc_AssertionError, "error setting trie"); + return -1; + } + } + return 0; +} + +static char has_key__doc__[] = +"D.has_key(k) -> 1 if D has a key k, else 0"; + +static PyObject * +trie_has_key(trieobject *mp, PyObject *py_key) +{ + unsigned char *key; + int has_key; + + /* Make sure key is a string. */ + if(!PyString_Check(py_key)) { + PyErr_SetString(PyExc_TypeError, "key must be a string"); + return NULL; + } + key = (unsigned char *)PyString_AS_STRING(py_key); + has_key = Trie_has_key(mp->trie, key); + return PyInt_FromLong((long)has_key); +} + +static PyObject * +trie_has_key_onearg(trieobject *mp, PyObject *py_args) +{ + PyObject *py_arg; + if(!PyArg_ParseTuple(py_args, "O", &py_arg)) + return NULL; + return trie_has_key(mp, py_arg); +} + + + +static char has_prefix__doc__[] = +"D.has_prefix(k) -> 1 if D has a prefix k, else 0"; + +static PyObject * +trie_has_prefix(trieobject *mp, PyObject *py_prefix) +{ + unsigned char *prefix; + int has_prefix; + + /* Make sure prefix is a string. */ + if(!PyString_Check(py_prefix)) { + PyErr_SetString(PyExc_TypeError, "k must be a string"); + return NULL; + } + prefix = (unsigned char *)PyString_AS_STRING(py_prefix); + has_prefix = Trie_has_prefix(mp->trie, prefix); + return PyInt_FromLong((long)has_prefix); +} + +static PyObject * +trie_has_prefix_onearg(trieobject *mp, PyObject *py_args) +{ + PyObject *py_arg; + if(!PyArg_ParseTuple(py_args, "O", &py_arg)) + return NULL; + return trie_has_prefix(mp, py_arg); +} + +static char with_prefix__doc__[] = +"D.with_prefix(prefix) -> list of D's keys that begins with prefix"; + +static void +_trie_with_prefix_helper(const unsigned char *key, const void *value, + void *data) +{ + PyObject *py_list = (PyObject *)data; + PyObject *py_key; + + if(PyErr_Occurred()) + return; + + if(!(py_key = PyString_FromString((const char *)key))) + return; + PyList_Append(py_list, py_key); + Py_DECREF(py_key); +} + +static PyObject * +trie_with_prefix(trieobject *mp, PyObject *py_prefix) +{ + unsigned char *prefix; + PyObject *py_list; + + /* Make sure prefix is a string. */ + if(!PyString_Check(py_prefix)) { + PyErr_SetString(PyExc_TypeError, "k must be a string"); + return NULL; + } + prefix = (unsigned char *)PyString_AS_STRING(py_prefix); + + if(!(py_list = PyList_New(0))) + return NULL; + Trie_with_prefix(mp->trie, prefix, + _trie_with_prefix_helper, (void *)py_list); + if(PyErr_Occurred()) { + Py_DECREF(py_list); + return NULL; + } + return py_list; +} + +static PyObject * +trie_with_prefix_onearg(trieobject *mp, PyObject *py_args) +{ + PyObject *py_arg; + if(!PyArg_ParseTuple(py_args, "O", &py_arg)) + return NULL; + return trie_with_prefix(mp, py_arg); +} + + +static char keys__doc__[] = +"D.keys() -> list of D's keys"; + +static void +_trie_keys_helper(const unsigned char *key, const void *value, void *data) +{ + PyObject *py_list = (PyObject *)data; + PyObject *py_key; + + if(PyErr_Occurred()) + return; + + if(!(py_key = PyString_FromString((char *)key))) + return; + PyList_Append(py_list, py_key); + Py_DECREF(py_key); +} + +static PyObject * +trie_keys(trieobject *mp) +{ + PyObject *py_list; + + if(!(py_list = PyList_New(0))) + return NULL; + Trie_iterate(mp->trie, _trie_keys_helper, (void *)py_list); + if(PyErr_Occurred()) { + Py_DECREF(py_list); + return NULL; + } + return py_list; +} + +static PyObject * +trie_keys_noargs(trieobject *mp, PyObject *py_args) +{ + if(PyTuple_Size(py_args) != 0) { + PyErr_SetString(PyExc_ValueError, "no args expected"); + return NULL; + } + return trie_keys(mp); +} + +static char values__doc__[] = +"D.values() -> list of D's values"; + +static void +_trie_values_helper(const unsigned char *key, const void *value, void *data) +{ + PyObject *py_list = (PyObject *)data; + if(PyErr_Occurred()) + return; + PyList_Append(py_list, (PyObject *)value); +} + +static PyObject * +trie_values(trieobject *mp) +{ + PyObject *py_list; + + if(!(py_list = PyList_New(0))) + return NULL; + Trie_iterate(mp->trie, _trie_values_helper, (void *)py_list); + if(PyErr_Occurred()) { + Py_DECREF(py_list); + return NULL; + } + return py_list; +} + +static PyObject * +trie_values_noargs(trieobject *mp, PyObject *py_args) +{ + if(PyTuple_Size(py_args) != 0) { + PyErr_SetString(PyExc_ValueError, "no args expected"); + return NULL; + } + return trie_values(mp); +} + +static char get__doc__[] = +"D.get(k[,d]) -> D[k] if D.has_key(k), else d. d defaults to None."; + +static PyObject * +trie_get(trieobject *mp, PyObject *args) +{ + unsigned char *key; + PyObject *py_value; + PyObject *py_failobj = Py_None; + + if (!PyArg_ParseTuple(args, "s|O:get", &key, &py_failobj)) + return NULL; + py_value = (PyObject *)Trie_get(mp->trie, key); + if(!py_value) + py_value = py_failobj; + Py_INCREF(py_value); + return py_value; +} + +static char get_approximate__doc__[] = +"D.get_approximate(key, k) -> List of (key, value, mismatches) in D, allowing up to k mismatches in key."; + +void +_trie_get_approximate_helper(const unsigned char *key, const void *value, + const int mismatches, void *data) +{ + /* Append a tuple of (key, value) to data, which is a PyList. */ + PyObject *py_list = (PyObject *)data, + *py_value = (PyObject *)value, + *py_key, + *py_tuple, + *py_mismatches; + + if(PyErr_Occurred()) + return; + + if(!(py_key = PyString_FromString((const char *)key))) + return; + if(!(py_mismatches = PyInt_FromLong(mismatches))) { + Py_DECREF(py_key); + return; + } + Py_INCREF(py_value); + + if(!(py_tuple = PyTuple_New(3))) { + Py_DECREF(py_key); + Py_DECREF(py_mismatches); + Py_DECREF(py_value); + return; + } + PyTuple_SetItem(py_tuple, 0, py_key); + PyTuple_SetItem(py_tuple, 1, py_value); + PyTuple_SetItem(py_tuple, 2, py_mismatches); + PyList_Append(py_list, py_tuple); + Py_DECREF(py_tuple); +} + +static PyObject * +trie_get_approximate(trieobject *mp, PyObject *args) +{ + unsigned char *key; + int k; + PyObject *py_list; + + if (!PyArg_ParseTuple(args, "si:get_approximate", &key, &k)) + return NULL; + + if(!(py_list = PyList_New(0))) + return NULL; + Trie_get_approximate(mp->trie, key, k, + _trie_get_approximate_helper, (void *)py_list); + if(PyErr_Occurred()) { + Py_DECREF(py_list); + return NULL; + } + return py_list; +} + +static long +trie_nohash(PyObject *self) +{ + PyErr_SetString(PyExc_TypeError, "trie objects are unhashable"); + return -1; +} + +static PyMappingMethods trie_as_mapping = { +/* The first member of PyMappingMethods was redefined in Python 2.5. */ +#if PY_VERSION_HEX < 0x02050000 + (inquiry)trie_length, /*mp_length*/ +#else + (lenfunc)trie_length, /*mp_length*/ +#endif + (binaryfunc)trie_subscript, /*mp_subscript*/ + (objobjargproc)trie_ass_sub /*mp_ass_subscript*/ +}; + +static PyMethodDef trieobj_methods[] = { + /* METH_O and METH_NOARGS require Python 2.2. + {"has_key", (PyCFunction)trie_has_key, METH_O, + has_key__doc__}, + {"has_prefix", (PyCFunction)trie_has_prefix, METH_O, + has_prefix__doc__}, + {"with_prefix", (PyCFunction)trie_with_prefix, METH_O, + with_prefix__doc__}, + {"keys", (PyCFunction)trie_keys, METH_NOARGS, + keys__doc__}, + {"values", (PyCFunction)trie_values, METH_NOARGS, + values__doc__}, + */ + + {"has_key", (PyCFunction)trie_has_key_onearg, METH_VARARGS, + has_key__doc__}, + {"has_prefix", (PyCFunction)trie_has_prefix_onearg, METH_VARARGS, + has_prefix__doc__}, + {"with_prefix", (PyCFunction)trie_with_prefix_onearg, METH_VARARGS, + with_prefix__doc__}, + {"keys", (PyCFunction)trie_keys_noargs, METH_VARARGS, + keys__doc__}, + {"values", (PyCFunction)trie_values_noargs, METH_VARARGS, + values__doc__}, + + {"get", (PyCFunction)trie_get, METH_VARARGS, + get__doc__}, + {"get_approximate", (PyCFunction)trie_get_approximate, METH_VARARGS, + get_approximate__doc__}, + {NULL, NULL} /* sentinel */ +}; + +static PyObject *trie_getattr(PyObject *obj, char *name) +{ + return Py_FindMethod(trieobj_methods, (PyObject *)obj, name); + +} + +static PyTypeObject Trie_Type = { + PyObject_HEAD_INIT(NULL) + 0, + "trie", + sizeof(trieobject), + 0, + trie_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + trie_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + &trie_as_mapping, /*tp_as_mapping*/ + trie_nohash, /*tp_hash */ +}; + +static int +_write_to_handle(const void *towrite, const int length, void *handle) +{ + PyObject *py_handle = (PyObject *)handle, + *py_retval = NULL; + int success = 0; + + if(!length) + return 1; + + if(!(py_retval = PyObject_CallMethod(py_handle, "write", "s#", + towrite, length))) + goto _write_to_handle_cleanup; + success = 1; + + _write_to_handle_cleanup: + if(py_retval) { + Py_DECREF(py_retval); + } + return success; +} + +int _write_value_to_handle(const void *value, void *handle) +{ + PyObject *py_value = (PyObject *)value, + *py_marshalled = NULL; + char *marshalled; + Py_ssize_t length; + int success = 0; + +#ifdef Py_MARSHAL_VERSION + if(!(py_marshalled = + PyMarshal_WriteObjectToString(py_value, Py_MARSHAL_VERSION))) + goto _write_value_to_handle_cleanup; +#else + if(!(py_marshalled = PyMarshal_WriteObjectToString(py_value))) + goto _write_value_to_handle_cleanup; +#endif + if(PyString_AsStringAndSize(py_marshalled, &marshalled, &length) == -1) + goto _write_value_to_handle_cleanup; + if(!_write_to_handle(&length, sizeof(length), handle)) + goto _write_value_to_handle_cleanup; + if (length != (int)length) + goto _write_value_to_handle_cleanup; + if(!_write_to_handle(marshalled, (int)length, handle)) + goto _write_value_to_handle_cleanup; + success = 1; + + _write_value_to_handle_cleanup: + if(py_marshalled) { + Py_DECREF(py_marshalled); + } + + return success; +} + +static PyObject * +trie_save(PyObject *self, PyObject *args) +{ + PyObject *py_handle, + *py_trie; + trieobject *mp; + + if(!PyArg_ParseTuple(args, "OO:save", &py_handle, &py_trie)) + return NULL; + mp = (trieobject *)py_trie; + if(!Trie_serialize(mp->trie, _write_to_handle, _write_value_to_handle, + (void *)py_handle)) { + if(!PyErr_Occurred()) + PyErr_SetString(PyExc_RuntimeError, + "saving failed for some reason"); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static int +_read_from_handle(void *wasread, const int length, void *handle) +{ + PyObject *py_handle = (PyObject *)handle, + *py_retval = NULL; + void *retval; + int success = 0; + PyBufferProcs *buffer; + int segment; + int bytes_read, bytes_left; + + if(!length) + return 1; + + if(!(py_retval = PyObject_CallMethod(py_handle, "read", "i", length))) + goto _read_from_handle_cleanup; + if(!py_retval->ob_type->tp_as_buffer) { + PyErr_SetString(PyExc_ValueError, "read method should return buffer"); + goto _read_from_handle_cleanup; + } + if(!(py_retval->ob_type->tp_flags & Py_TPFLAGS_DEFAULT)) { + PyErr_SetString(PyExc_ValueError, "no bf_getcharbuffer slot"); + goto _read_from_handle_cleanup; + } + buffer = py_retval->ob_type->tp_as_buffer; + if(!buffer->bf_getreadbuffer) { + PyErr_SetString(PyExc_ValueError, "no bf_getreadbuffer"); + goto _read_from_handle_cleanup; + } + + bytes_left = length; + segment = 0; + while(bytes_left > 0) { + if((bytes_read = buffer->bf_getreadbuffer(py_retval, + segment, &retval)) == -1) + goto _read_from_handle_cleanup; + memcpy(wasread, retval, bytes_read); + wasread = (void *)((char *)wasread + bytes_read); + bytes_left -= bytes_read; + segment += 1; + } + + success = 1; + + _read_from_handle_cleanup: + if(py_retval) { + Py_DECREF(py_retval); + } + return success; +} + +#define MAX_KEY_LENGTH 2000 +static void * +_read_value_from_handle(void *handle) +{ + Py_ssize_t length; + char KEY[MAX_KEY_LENGTH]; + + if(!_read_from_handle((void *)&length, sizeof(length), (void *)handle)) + return NULL; + if(length < 0 || length >= MAX_KEY_LENGTH) + return NULL; + if(!_read_from_handle((void *)KEY, length, (void *)handle)) + return NULL; + return PyMarshal_ReadObjectFromString(KEY, length); +} + + +static PyObject * +trie_load(PyObject *self, PyObject *args) +{ + PyObject *py_handle; + Trie trie; + trieobject *trieobj; + + if(!PyArg_ParseTuple(args, "O:load", &py_handle)) + return NULL; + + if(!(trie = Trie_deserialize(_read_from_handle, _read_value_from_handle, + py_handle))) { + if(!PyErr_Occurred()) + PyErr_SetString(PyExc_RuntimeError, + "loading failed for some reason"); + return NULL; + } + + if(!(trieobj = PyObject_New(trieobject, &Trie_Type))) { + Trie_del(trie); + return NULL; + } + trieobj->trie = trie; + return (PyObject *)trieobj; +} + +static PyMethodDef trie_methods[] = { + {"trie", trie_trie, METH_VARARGS, + "trie() -> new Trie object."}, + {"load", trie_load, METH_VARARGS, + "load(handle) -> trie object"}, + {"save", trie_save, METH_VARARGS, + "save(handle, trie), save a trie object to a handle"}, + {NULL, NULL, 0, NULL} +}; + +static char trie__doc__[] = +"\ +This module implements a trie data structure. This allows an O(M)\n\ +lookup of a string in a dictionary, where M is the length of the\n\ +string. It also supports approximate matches.\n\ +\n\ +Functions:\n\ +trie Create a new trie object.\n\ +save Save a trie to a handle.\n\ +load Load a trie from a handle.\n\ +\n\ +"; + +DL_EXPORT(void) +inittrie(void) +{ + Trie_Type.ob_type = &PyType_Type; + + (void) Py_InitModule3("trie", trie_methods, trie__doc__); +} diff --git a/binaries/src/globplot/biopython-1.50/Bio/utils.py b/binaries/src/globplot/biopython-1.50/Bio/utils.py new file mode 100644 index 0000000..bf0d317 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/utils.py @@ -0,0 +1,163 @@ +# Copyright 2000 by Andrew Dalke. +# All rights reserved. +# This code is part of the Biopython distribution and governed by its +# license. Please see the LICENSE file that should have been included +# as part of this package. + +"""Miscellaneous functions for dealing with sequences (obsolete?).""" + +import Seq +import Alphabet + +from PropertyManager import default_manager + +def translate(seq, id = None): + """Translate a sequence (DEPRECATED).""" + import warnings + warnings.warn("Bio.utils.translate() has been deprecated, and we" \ + +" intend to remove it in a future release of Biopython."\ + +" Please use the translate method or function in Bio.Seq"\ + +" instead, as described in the Tutorial.", + DeprecationWarning) + if id is None: + s = "translator" + else: + s = "translator.id.%d" % id + translator = default_manager.resolve(seq.alphabet, s) + return translator.translate(seq) + +def translate_to_stop(seq, id = None): + """Translate a sequence up to the first in frame stop codon (DEPRECATED).""" + import warnings + warnings.warn("Bio.utils.translate_to_stop() has been deprecated, and we" \ + +" intend to remove it in a future release of Biopython."\ + +" Please use the translate method or function in Bio.Seq"\ + +" instead, as described in the Tutorial.", + DeprecationWarning) + if id is None: + s = "translator" + else: + s = "translator.id.%d" % id + translator = default_manager.resolve(seq.alphabet, s) + return translator.translate_to_stop(seq) + +def back_translate(seq, id = None): + """Back-translate a sequence (DEPRECATED).""" + import warnings + warnings.warn("Bio.utils.back_translate() has been deprecated, and we" \ + +" intend to remove it in a future release of Biopython."\ + +" If you use it, please tell us on the mailing list.", + DeprecationWarning) + if id is None: + s = "translator" + else: + s = "translator.id.%d" % id + translator = default_manager.resolve(seq.alphabet, s) + return translator.back_translate(seq) + + +def transcribe(seq): + """Transcribe a sequence (DEPRECATED).""" + import warnings + warnings.warn("Bio.utils.transcribe() has been deprecated, and we" \ + +" intend to remove it in a future release of Biopython."\ + +" Please use the transcribe method or function in"\ + +" Bio.Seq instead, as described in the Tutorial.", + DeprecationWarning) + transcriber = default_manager.resolve(seq.alphabet, "transcriber") + return transcriber.transcribe(seq) + +def back_transcribe(seq): + """Back-transcribe a sequence (DEPRECATED).""" + import warnings + warnings.warn("Bio.utils.back_transcribe() has been deprecated, and we" \ + +" intend to remove it in a future release of Biopython."\ + +" Please use the back_transcribe method or function in"\ + +" Bio.Seq instead, as described in the Tutorial.", + DeprecationWarning) + transcriber = default_manager.resolve(seq.alphabet, "transcriber") + return transcriber.back_transcribe(seq) + +def ungap(seq): + """given a sequence with gap encoding, return the ungapped sequence""" + #TODO - Fix this? It currently assumes the outmost AlphabetEncoder + #is for the gap. Consider HasStopCodon(Gapped(Protein())) as a test case. + gap = seq.gap_char + letters = [] + for c in seq.data: + if c != gap: + letters.append(c) + return Seq.Seq("".join(letters), seq.alphabet.alphabet) + +def verify_alphabet(seq): + letters = {} + for c in seq.alphabet.letters: + letters[c] = 1 + try: + for c in seq.data: + letters[c] + except KeyError: + return 0 + return 1 + +def count_monomers(seq): + dict = {} +# bugfix: string.count(s,c) raises an AttributeError. Iddo Friedberg 16 Mar. 04 +# s = buffer(seq.data) # works for strings and array.arrays + for c in seq.alphabet.letters: + dict[c] = seq.data.count(c) + return dict + +def percent_monomers(seq): + dict2 = {} + seq_len = len(seq) + dict = count_monomers(seq) + for m in dict: + dict2[m] = dict[m] * 100. / seq_len + return dict2 + +def sum(seq, table, zero = 0.0): + total = zero + for c in getattr(seq, "data", seq): + total = total + table[c] + return total + +# For ranged addition +def sum_2ple(seq, table, zero = (0.0, 0.0)): + x, y = zero + data = getattr(seq, "data", seq) + for c in data: + x2, y2 = table[c] + x = x + x2 + y = y + y2 + return (x, y) + +def total_weight(seq, weight_table = None): + if weight_table is None: + weight_table = default_manager.resolve(seq.alphabet, "weight_table") + return sum(seq, weight_table) + +def total_weight_range(seq, weight_table = None): + if weight_table is None: + weight_table = default_manager.resolve(seq.alphabet, "weight_range_table") + return sum_2ple(seq, weight_table) + +def reduce_sequence(seq, reduction_table,new_alphabet=None): + """ given an amino-acid sequence, return it in reduced alphabet form based + on the letter-translation table passed. Some "standard" tables are in + Alphabet.Reduced. + seq: a Seq.Seq type sequence + reduction_table: a dictionary whose keys are the "from" alphabet, and values + are the "to" alphabet""" + if new_alphabet is None: + new_alphabet = Alphabet.single_letter_alphabet + new_alphabet.letters = '' + for letter in reduction_table: + new_alphabet.letters += letter + new_alphabet.size = len(new_alphabet.letters) + new_seq = Seq.Seq('',new_alphabet) + for letter in seq: + new_seq += reduction_table[letter] + return new_seq + + diff --git a/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/__init__.py new file mode 100644 index 0000000..b2f2e70 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/__init__.py @@ -0,0 +1,2 @@ +"""Part of an old unused and undocumented sequence writing framework (DEPRECATED).""" +# This is a Python module. diff --git a/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/embl.py b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/embl.py new file mode 100644 index 0000000..b30b368 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/embl.py @@ -0,0 +1,85 @@ +"""Part of an old unused and undocumented sequence writing framework (DEPRECATED).""" +# Not clear on the distinction, if any, between 'embl' and 'embl/65'. This +# code might apply to either or both. + +# See 'http://www.ebi.ac.uk/embl/Documentation/User_manual/usrman.html' for a +# definition of this file format. + +# This code only makes a best effort--the output may not be strictly valid. +# So, for example, the EMBL ID is supposed to be alphanumeric, starting with a +# letter, but we don't check for this, etc. + + +# Example: +# ID AA03518 standard; DNA; FUN; 237 BP. +# XX +# AC U03518; +# XX +# DE Aspergillus awamori internal transcribed spacer 1 (ITS1) and 18S +# DE rRNA and 5.8S rRNA genes, partial sequence. +# XX +# SQ Sequence 237 BP; 41 A; 77 C; 67 G; 52 T; 0 other; +# aacctgcgga aggatcatta ccgagtgcgg gtcctttggg cccaacctcc catccgtgtc 60 +# tattgtaccc tgttgcttcg gcgggcccgc cgcttgtcgg ccgccggggg ggcgcctctg 120 +# ccccccgggc ccgtgcccgc cggagacccc aacacgaaca ctgtctgaaa gcgtgcagtc 180 +# tgagttgatt gaatgcaatc agttaaaact ttcaacaatg gatctcttgg ttccggc 237 +# // + + +import textwrap + +from Bio import Alphabet +from Bio import Writer + +class WriteEmbl(Writer.Writer): + def __init__(self, outfile): + Writer.Writer.__init__(self, outfile) + + def write(self, record): + seq = record.seq + assert seq.alphabet.size == 1, "cannot handle alphabet of size %d" % \ + seq.alphabet.size + data = seq.data + upperdata = data.upper() + +# It'd be nice if the alphabet was usefully set, but for many interesting +# cases (e.g., reading from FASTA files), it's not. + + if isinstance(seq.alphabet, Alphabet.RNAAlphabet): + molecule = 'mRNA' + letters = ['A', 'C', 'G', 'U'] + else: + molecule = 'DNA' + letters = ['A', 'C', 'G', 'T'] + + division = 'UNC' # unknown + + self.outfile.write("ID %s standard; %s; %s; %d BP.\n" + % (record.id, molecule, division, len(data))) + + desclist = textwrap.wrap(record.description, 74) + for l in desclist: + self.outfile.write("DE %s\n" % l) + + counts = [ upperdata.count(l) for l in letters ] + othercount = len(upperdata) - sum(counts) + + countstring = ''.join([ " %d %s;" % p for p in zip(counts, letters) ]) + + self.outfile.write("SQ Sequence %s BP;%s %d other;\n" + % (len(data), countstring, othercount)) + + rowlength = 60 + blocklength = 10 + for i in xrange(0, len(data), rowlength): + self.outfile.write(" " * 5) + row = data[i:i+rowlength] + for b in xrange(0, rowlength, blocklength): + block = row[b:b+blocklength] + self.outfile.write("%-*s" % (blocklength+1, block)) + self.outfile.write("%9d\n" % min(i+rowlength, len(data))) + + self.outfile.write("//\n") + + +make_writer = WriteEmbl diff --git a/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/empty.py b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/empty.py new file mode 100644 index 0000000..b54f072 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/empty.py @@ -0,0 +1,7 @@ +"""Part of an old unused and undocumented sequence writing framework (DEPRECATED).""" +from Bio import Writer + +class WriteEmpty(Writer.Writer): + pass + +make_writer = WriteEmpty diff --git a/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/fasta.py b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/fasta.py new file mode 100644 index 0000000..0b48366 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/writers/SeqRecord/fasta.py @@ -0,0 +1,21 @@ +"""Part of an old unused and undocumented sequence writing framework (DEPRECATED).""" +from Bio import Writer + +class WriteFasta(Writer.Writer): + def __init__(self, outfile, seqwidth = 72): + Writer.Writer.__init__(self, outfile) + assert seqwidth > 0, seqwidth + self.seqwidth = seqwidth + + def write(self, record): + self.outfile.write(">%s %s\n" % (record.id, record.description)) + seq = record.seq + assert seq.alphabet.size == 1, "cannot handle alphabet of size %d" % \ + seq.alphabet.size + seq = seq.data + seqwidth = self.seqwidth + for i in range(0, len(seq), seqwidth): + self.outfile.write(seq[i:i+seqwidth]) + self.outfile.write("\n") + +make_writer = WriteFasta diff --git a/binaries/src/globplot/biopython-1.50/Bio/writers/__init__.py b/binaries/src/globplot/biopython-1.50/Bio/writers/__init__.py new file mode 100644 index 0000000..98bb12f --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/Bio/writers/__init__.py @@ -0,0 +1,3 @@ +"""Part of an old unused and undocumented sequence writing framework (DEPRECATED).""" +# This is a Python module. +# (there are more files underneath this directory) diff --git a/binaries/src/globplot/biopython-1.50/CONTRIB b/binaries/src/globplot/biopython-1.50/CONTRIB new file mode 100644 index 0000000..8791f7d --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/CONTRIB @@ -0,0 +1,53 @@ +CONTRIBUTORS +============ + +This is a list of people who have made contributions to Biopython. +This is certainly not comprehensive, and if you've been overlooked +(sorry!), please mention it on the development mailing list. + +Cecilia Alsmark +Tiago Antao +Sebastian Bassi +Bill Barnard +Yves Bastide +Yair Benita +Peter Bienstman +Bob Bussell +Diego Brouard +James Casbon +Hye-Shik Chang +Jeffrey Chang +Brad Chapman +Peter Cock +Marc Colosimo +Cymon J Cox +Gavin E Crooks +Andrew Dalke +Michiel de Hoon +Sjoerd de Vries +Iddo Friedberg +Bertrand Frottier +Jason A. Hackney +Thomas Hamelryck +Michael Hoffman +Yu Huang +Frank Kauff +Andreas Kuntzagk +Michal Kurowski +Chris Lasher +Gaetan Lehman +Katharine Lindner +Tarjei Mikkelsen +Cheng Soon Ong +Mike Poidinger +Leighton Pritchard +Wolfgang Schueler +Peter Slickers +Thomas Sicheritz-Ponten +Frederic Sohm +Thomas Rosleff Soerensen +Johann Visagie +Dan Vogel +David Weisman +Bartek Wilczynski +Harry Zuzan diff --git a/binaries/src/globplot/biopython-1.50/LICENSE b/binaries/src/globplot/biopython-1.50/LICENSE new file mode 100644 index 0000000..3595ec5 --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/LICENSE @@ -0,0 +1,19 @@ + Biopython License Agreement + +Permission to use, copy, modify, and distribute this software and its +documentation with or without modifications and for any purpose and +without fee is hereby granted, provided that any copyright notices +appear in all copies and that both those copyright notices and this +permission notice appear in supporting documentation, and that the +names of the contributors or copyright holders not be used in +advertising or publicity pertaining to distribution of the software +without specific prior permission. + +THE CONTRIBUTORS AND COPYRIGHT HOLDERS OF THIS SOFTWARE DISCLAIM ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT +OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE +OR PERFORMANCE OF THIS SOFTWARE. diff --git a/binaries/src/globplot/biopython-1.50/PKG-INFO b/binaries/src/globplot/biopython-1.50/PKG-INFO new file mode 100644 index 0000000..7fbacbb --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 1.0 +Name: biopython +Version: 1.50 +Summary: Freely available tools for computational molecular biology. +Home-page: http://www.biopython.org/ +Author: The Biopython Consortium +Author-email: biopython@biopython.org +License: UNKNOWN +Download-URL: http://biopython.org/DIST/ +Description: UNKNOWN +Platform: UNKNOWN diff --git a/binaries/src/globplot/biopython-1.50/README b/binaries/src/globplot/biopython-1.50/README new file mode 100644 index 0000000..9c473ae --- /dev/null +++ b/binaries/src/globplot/biopython-1.50/README @@ -0,0 +1,164 @@ +#### THIS IS A REDUCED DESTRIBUTION OF BIOPYTHON #### + +**Biopython README file** + + "The Biopython Project":http://www.biopython.org/ is an +international association of developers of freely available Python +tools for computational molecular biology. + +biopython.org provides an online resource for modules, scripts, and +web links for developers of Python-based software for life science +research. Thanks to bioperl, we can also provide web, FTP and CVS +space for individuals and organizations wishing to distribute or +otherwise make available standalone scripts & code. + +This Biopython package is made available under generous terms. Please +see the LICENSE file for further details. + + +**For the impatient** + +To build and install Biopython, download and unzip the source code, +go to this directory at the command line, and type: + +python setup.py build +python setup.py test +python setup.py install + +**System Requirements** + +o "Python 2.3, 2.4, 2.5 or 2.6":http://www.python.org/ + Note that Biopython 1.50 is expected to the our final release to support + Python 2.3. Given that Python 2.6 is still very new, it would be prudent + to opt for Python 2.5 or 2.4 at this time. + +o "NumPy":http://numpy.scipy.org/ (optional, but strongly recommended) + This package is only used in the computationally-oriented modules. + It is required for Bio.Cluster, Bio.PDB and a few other modules. If you + think you might need these modules, then please install NumPy first BEFORE + installing Biopython. The older Numeric library is no longer supported in + Biopython. + +o "ReportLab":http://www.reportlab.org/downloads.html (optional) + This package is only used in Bio.Graphics, so if you do not need this + functionality, you will not need to install this package. You can install + it later if needed. + +o "psycopg":http://initd.org/software/psycopg/ (optional) or + "pgdb":http://www.druid.net/pygresql/ (optional) + These packages are used by BioSQL to access a PostgreSQL database. + +o "MySQLdb":http://sourceforge.net/projects/mysql-python (optional) + This package is used by BioSQL or Bio.GFF to access a MySQL database. + +o "mxTextTools":http://www.egenix.com/files/python/mxTextTools.html (unlikely) + You probably won't need mxTextTools. This was used in some of Biopython's + older parsers, and Martel/Mindy, but these have all been deprecated now. + +In addition there are a number of useful third party tools you may wish to +install such as standalone NCBI BLAST or ClustalW. + + +**Installation** + +*** Make sure that Python is installed correctly *** + +Installation should be as simple as going to the biopython source code +directory, and typing: + + 'python setup.py build' + 'python setup.py test' + 'sudo python setup.py install' + +If you need to do additional configuration, e.g. changing the base +directory, please type 'python setup.py', or see the documentation for +Distutils. + + +**Testing** + +Biopython includes a suite of regression tests to check if everything is +running correctly. To do the tests, go to the biopython source code directory +and type: + + 'python setup.py test' + +Do not panic if you see messages warning of skipped tests: + test_DocSQL ... skipping. Install MySQLdb if you want to use Bio.DocSQL. + +This most likely means that a package is not installed. You can +ignore this if it occurs in the tests for a module that you were not +planning on using. If you did want to use that module, please install +the required dependency and re-run the tests. + + +**Bugs** + +While we try to ship a robust package, bugs inevitably pop up. If you +are having problems that might be caused by a bug in Biopython, it is +possible that it has already been identified. Search the +"bug database":http://bugzilla.open-bio.org/ and mailing lists +to see if it has already been reported (and hopefully fixed). + +If you suspect the problem lies within a parser, it is likely that the +data format has changed and broken the parsing code. (The BLAST and +GenBank formats seem to be particularly fragile.) Thus, the parsing +code in Biopython is sometimes updated faster than we can build Biopython +releases. You can get the most recent parser by pulling the relevant +files (e.g. the ones in Bio.SeqIO or Bio.Blast) out of +"anonymous cvs":http://cvs.biopython.org/ . +However, be careful when doing this, because the code in CVS is not as +well-tested as released code, and may contain new dependencies. + +Finally, you can send a bug report to the bug database or +biopython@biopython.org. In the bug report, please let us know 1) +which operating system and hardware you are using, 2) Python version, +3) Biopython version (or CVS version/date), 4) traceback that occurs, +5) offending code, and 6) data file that causes the problem. + + + +**Contributing, Bug Reports** + +Biopython is run by volunteers from all over the world, with many +types of backgrounds. We are always looking for people interested in +helping with code development, web-site management, documentation +writing, technical administration, and whatever else comes up. + +If you wish to contribute, please visit the +"web site":http://www.biopython.org +and join our "mailing list":http://biopython.org/wiki/Mailing_lists + + + +**Distribution Structure** + +README -- This file. + +NEWS -- Release notes and news + +LICENSE -- What you can do with the code. + +CONTRIB -- An (incomplete) list of people who helped Biopython in + one way or another. + +DEPRECATED -- Contains information about modules in Biopython that are + removed or no longer recommended for use, and how to update + code that uses those modules. + +MANIFEST.in -- Tells distutils what files to distribute + +setup.py -- Installation file. + +Bio/ -- The main code base code. + +Martel/ -- Code for the Martel parsing system, once used in many + Biopython parsers but now deprecated. + +BioSQL/ -- Code for using Biopython with BioSQL databases. + +Doc/ -- Documentation. + +Scripts/ -- Miscellaneous, possibly useful, standalone scripts + +Tests/ -- Regression testing code diff --git a/binaries/src/globplot/sav_gol b/binaries/src/globplot/sav_gol new file mode 100644 index 0000000000000000000000000000000000000000..ef46f551a691120abeb4b589deca54386a576230 GIT binary patch literal 448988 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 B$r%6u literal 0 HcmV?d00001 -- 1.7.10.2