source: orange-bioinformatics/orangecontrib/bio/obiTaxonomy.py @ 1873:0810c5708cc5

Revision 1873:0810c5708cc5, 16.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 7 months ago (diff)

Moved '_bioinformatics' into orangecontrib namespace.

RevLine 
[1632]1from __future__ import absolute_import, division
[579]2
3from collections import defaultdict
[1632]4import cPickle, os, shutil, sys, StringIO, tarfile, urllib2
5
6from Orange.orng import orngEnviron, orngServerFiles
7
8from . import obiData, obiGenomicsUpdate
[579]9
[707]10# list of common organisms from http://www.ncbi.nlm.nih.gov/Taxonomy
11def common_taxids():
12    """Return taxonomy IDs for common organisms."""
13    return ["3702",  # Arabidopsis thaliana
14            "9913",  # Bos taurus
15            "6239",  # Caenorhabditis elegans
16            "3055",  # Chlamydomonas reinhardtii
17            "7955",  # Danio rerio (zebrafish)
[784]18            "352472", # Dictyostelium discoideum
[707]19            "7227",  # Drosophila melanogaster
20            "562",   # Escherichia coli
21            "11103", # Hepatitis C virus
22            "9606",  # Homo sapiens
23            "10090", # Mus musculus
24            "2104",  # Mycoplasma pneumoniae
25            "4530",  # Oryza sativa
26            "5833",  # Plasmodium falciparum
27            "4754",  # Pneumocystis carinii
28            "10116", # Rattus norvegicus
29            "4932",  # Saccharomyces cerevisiae
30            "4896",  # Schizosaccharomyces pombe
31            "31033", # Takifugu rubripes
32            "8355",  # Xenopus laevis
33            "4577",  # Zea mays
34             ] 
35
[1834]36def taxname_to_taxid(name):
37    """Return taxonomy ID for a taxonomy name"""
38    ids = {
39    "Arabidopsis thaliana": "3702",
40    "Bos taurus": "9913",
41    "Caenorhabditis elegans": "6239", 
42    "Chlamydomonas reinhardtii": "3055",
43    "Danio rerio": "7955",
44    "Dictyostelium discoideum": "352472", 
45    "Drosophila melanogaster": "7227",
46    "Escherichia coli": "562",
47    "Hepatitis C virus": "11103",
48    "Homo sapiens": "9606",
49    "Mus musculus": "10090", 
50    "Mycoplasma pneumoniae": "2104",
51    "Oryza sativa": "4530", 
52    "Plasmodium falciparum": "5833", 
53    "Pneumocystis carinii": "4754",
54    "Rattus norvegicus": "10116", 
55    "Saccharomyces cerevisiae": "4932", 
56    "Schizosaccharomyces pombe": "4896", 
57    "Takifugu rubripes": "31033",
58    "Xenopus laevis": "8355",
59    "Zea mays": "4577" }
60    if name in ids.keys():
61        return ids[name]
62    return []
63
[707]64def essential_taxids():
65    """Return taxonomy IDs for organisms that are included in (default) Orange Bioinformatics installation."""
[784]66    return ["352472", # Dictyostelium discoideum
[707]67            "7227",  # Drosophila melanogaster
68            "9606",  # Homo sapiens
69            "10090", # Mus musculus
70            "4932",  # Saccharomyces cerevisiae
71            ] 
72
[1828]73def shortname(taxid):
74    """ Short names for common_taxids organisms """
75    names = {
76    "3702"  : ["arabidopsis", "thaliana", "plant"],
77    "9913"  : ["cattle", "cow"],
78    "6239"  : ["nematode", "roundworm"],
79    "3055"  : ["algae"],
80    "7955"  : ["zebrafish"],
81    "352472": ["dicty", "amoeba", "slime mold"],
82    "7227"  : ["fly", "fruit fly", "vinegar fly"],
83    "562"   : ["ecoli", "coli", "bacterium"],
84    "11103" : ["virus, hepatitis"],
85    "9606"  : ["human"],
86    "10090" : ["mouse", "mus"],
87    "2104"  : ["bacterium", "mycoplasma"],
88    "4530"  : ["asian rice", "rice", "cereal", "plant"],
89    "5833"  : ["plasmodium", "malaria", "parasite"],
90    "4754"  : ["pneumonia", "fungus"],
91    "10116" : ["rat", "laboratory rat"],
92    "4932"  : ["yeast", "baker yeast", "brewer yeast"],
93    "4896"  : ["yeast", "fission yeast"],
94    "31033" : ["fish", "pufferfish"],
95    "8355"  : ["frog", "african clawed frog"],
96    "4577"  : ["corn", "cereal grain", "plant"]
97    }
98    if taxid in names.keys():
99        return names[taxid]
100    return []
101
[579]102default_database_path = os.path.join(orngEnviron.bufferDir, "bigfiles", "Taxonomy")
103
[611]104class MultipleSpeciesException(Exception):
[581]105    pass
106
[611]107class UnknownSpeciesIdentifier(Exception):
108    pass
[579]109
[798]110def pickled_cache(filename=None, dependencies=[], version=1, maxSize=30):
111    """ Return a cache function decorator.
112    """
[804]113    def datetime_info(domain, filename):
114        try:
115            return orngServerFiles.info(domain, filename)["datetime"]
116        except IOError:
117            return orngServerFiles.ServerFiles().info(domain, filename)["datetime"]
118           
[753]119    def cached(func):
120        default_filename = os.path.join(orngEnviron.bufferDir, func.__module__ + "_" + func.__name__ + "_cache.pickle")
121        def f(*args, **kwargs):
[804]122            currentVersion = tuple([datetime_info(domain, file) for domain, file in dependencies]) + (version,)
[753]123            try:
124                cachedVersion, cache = cPickle.load(open(filename or default_filename, "rb"))
[798]125                if cachedVersion != currentVersion:
[753]126                    cache = {}
127            except IOError, er:
128                cacheVersion, cache = "no version", {}
129            allArgs = args + tuple([(key, tuple(value) if type(value) in [set, list] else value)\
130                                     for key, value in kwargs.items()])
131            if allArgs in cache:
132                return cache[allArgs]
133            else:
134                res = func(*args, **kwargs)
135                if len(cache) > maxSize:
136                    del cache[iter(cache).next()]
137                cache[allArgs] = res
[798]138                cPickle.dump((currentVersion, cache), open(filename or default_filename, "wb"), protocol=cPickle.HIGHEST_PROTOCOL)
[753]139                return res
140        return f
141
142    return cached
143
[620]144def cached(func):
145    """Cached one arg method
146    """
147    def f(self, arg):
148        if arg not in self._cache:
149            self._cache[arg] = func(self, arg)
150        return self._cache[arg]
151    f._cache = {}
152    f.__name__ = "Cached " + func.__name__
153    return f
154   
155class TextDB(object):
156    entry_start_string = chr(255)
157    entry_end_string = chr(254)+"\n"
158    entry_separator_string = chr(253)
159   
160    @property
161    def _text_lower(self):
162        if id(self._text) == self._lower_text_id:
163            return self._lower_text
164        else:
165            self._lower_text_id = id(self._text)
166            self._lower_text = self._text.lower()
167            return self._lower_text
168       
169    def __init__(self, file=None, **kwargs):
170        self._text = ""
171        self._lower_text_id = id(self._text) - 1
172        self._cache = {}
173        self.__dict__.update(kwargs)
174       
175        if file != None:
[632]176            self._text = open(file, "rb").read()
[620]177
178    def _find_all(self, string, start=0, text=None, unique=True):
179        text = text if text != None else self._text_lower
180        while True:
181            index = text.find(string, start)
182            if index != -1:
183                yield index
184                if unique:
185                    start = text.find(self.entry_start_string, index + 1)
186                else:
187                    start = index + 1
188            else:
189                raise StopIteration
190
191    def _get_entry_at(self, index, text=None):
192        text = text if text != None else self._text
193        start = text.rfind(self.entry_start_string, 0, index + 1)
194        end = text.find(self.entry_end_string, index)
195        return self._text[start+1:end]
196
197    @cached
198    def get_entry(self, id):
199        try:
200            index = self._find_all(self.entry_start_string + id + self.entry_separator_string).next()
201        except StopIteration:
202            raise KeyError, id
203        return self._get_entry_at(index)
204               
205    def search(self, string):
206        string = string.lower()
207        res = []
208        for idx in self._find_all(string):
209            entry = self._get_entry_at(idx)
210            id , rest = entry.split(self.entry_separator_string, 1)
211            self._cache[id] = entry
212            res.append(id)
213        return res
214
215    def insert(self, entry):
216        self._text += self.entry_start_string + self.entry_separator_string.join(entry) + self.entry_end_string
217
218    def __iter__(self):
219        for idx in self._find_all(self.entry_start_string):
220            entry = self._get_entry_at(idx)
221            if entry:
222                yield entry.split(self.entry_separator_string ,1)[0]
223
224    def __getitem__(self, id):
225        entry = self.get_entry(id)
226        return entry.split(self.entry_separator_string)[1:]
227
228    def __setitem__(self, id, entry):
229        self.insert([id] + list(entry))
230
231    def create(self, filename):
[632]232        f = open(filename, "wb")
[620]233        f.write(self._text)
234        def write(entry):
235            f.write(self.entry_start_string + self.entry_separator_string.join(entry) + self.entry_end_string)
236        return write
237   
[611]238class Taxonomy(object):
[620]239    __shared_state = {"_text":None, "_info":None}
240    def __init__(self):
241        self.__dict__ = self.__shared_state
242        if not self._text:
243            self.Load()
244           
245    def Load(self):
246        try:
247            self._text = TextDB(os.path.join(default_database_path, "ncbi_taxonomy.tar.gz", "ncbi_taxonomy.db"))
248            self._info = TextDB(os.path.join(default_database_path, "ncbi_taxonomy.tar.gz", "ncbi_taxonomy_inf.db"))
249            return
250        except Exception, ex:
[880]251            pass
[620]252        try:
[1693]253            orngServerFiles.download("Taxonomy", "ncbi_taxonomy.tar.gz")
[620]254            self._text = TextDB(os.path.join(default_database_path, "ncbi_taxonomy.tar.gz", "ncbi_taxonomy.db"))
255            self._info = TextDB(os.path.join(default_database_path, "ncbi_taxonomy.tar.gz", "ncbi_taxonomy_inf.db"))
256            return
257        except Exception, ex:
258            raise
[656]259
260    def get_entry(self, id):
261        try:
262            entry = self._text[id]
263        except KeyError:
[753]264            raise UnknownSpeciesIdentifier
[656]265        return entry
[620]266               
267    def search(self, string, onlySpecies=True):
268        res = self._text.search(string)
269        if onlySpecies:
[632]270            res = [r for r in res if "species" in self._text[r][1]]
[620]271        return res
[579]272
[620]273    def __iter__(self):
274        return iter(self._text)
275
276    def __getitem__(self, id):
[656]277        entry = self.get_entry(id)
[620]278        return entry[2] ## item with index 2 is allways scientific name
279
280    def other_names(self, id):
[656]281        entry = self.get_entry(id)
[620]282        info = self._info[id]
[661]283        names = entry[2:] ## index 2 and larger are names
[620]284        return list(zip(names, info))[1:] ## exclude scientific name
[656]285
286    def rank(self, id):
287        entry = self.get_entry(id)
288        return entry[1]
289
290    def parent(self, id):
291        entry = self.get_entry(id)
292        return entry[0]
[661]293
294    def subnodes(self, id, levels=1):
295        res = self._text.search(self._text.entry_separator_string + id + self._text.entry_separator_string)
296        res = [r for r in res if self.get_entry(r)[0] == id]
297        if levels > 1:
298            for r in list(res):
299                res.extend(self.subnodes(r, levels-1))
300        return res
[708]301
302    def taxids(self):
303        return list(self)
[661]304   
[620]305    @staticmethod
306    def ParseTaxdumpFile(file=None, outputdir=None, callback=None):
307        from cStringIO import StringIO
[1720]308        import Orange.utils
[620]309        if file == None:
[1720]310            so = StringIO()
311            Orange.utils.wget("ftp://ftp.ncbi.nih.gov/pub/taxonomy/taxdump.tar.gz", dst_obj=so)
312            file = tarfile.open(None, "r:gz", StringIO(so.getvalue()))
313            so.close()
314        elif type(file) == str:
[620]315            file = tarfile.open(file)
316        names = file.extractfile("names.dmp").readlines()
317        nodes = file.extractfile("nodes.dmp").readlines()
318        namesDict = defaultdict(list)
[611]319        for line in names:
320            if not line.strip():
321                continue
322            line = line.rstrip("\t\n|").split("\t|\t")
323            id, name, unique_name, name_class = line
324            if unique_name:
325                namesDict[id].append((unique_name , name_class))
326            else:
327                namesDict[id].append((name , name_class))
328
[620]329        nodesDict = {}
[611]330        for line in nodes:
331            if not line.strip():
332                continue
333            line = line.split("\t|\t")[:3]
334            id, parent, rank = line
335            nodesDict[id] = (parent, rank)
[620]336       
337        name_class_codes = defaultdict(iter(range(255)).next)
338        name_class_codes["scientific name"] ## Force scientific name to be first
339        if outputdir == None:
340            outputdir = default_database_path
341        text = TextDB().create(os.path.join(outputdir, "ncbi_taxonomy.db"))
342        info = TextDB().create(os.path.join(outputdir, "ncbi_taxonomy_inf.db"))
[1720]343        milestones = set(range(0, len(namesDict), max(int(len(namesDict)/100), 1)))
[620]344        for i, (id, names) in enumerate(namesDict.items()):
345            parent, rank = nodesDict[id]
346            ## id, parent and rank go first
347            entry = [id, parent, rank]
348            ## all names and name class codes pairs follow ordered so scientific name is first
349            names = sorted(names, key=lambda (name, class_): name_class_codes[class_])
350            entry.extend([name for name ,class_ in names])
351            info_entry = [id] + [class_ for name, class_ in names]
352            text(entry)
353            info(info_entry)
354            if callback and i in milestones:
355                callback(i)
[579]356   
357def name(taxid):
[649]358    """ Return the scientific name for organism with taxid.
359    """
[620]360    return Taxonomy()[taxid]
[579]361
362def other_names(taxid):
[649]363    """ Return a list of (name, name_type) tuples but exclude the scientific name.
364    """
[622]365    return  Taxonomy().other_names(taxid)
[579]366
[798]367@pickled_cache(None, [("Taxonomy", "ncbi_taxonomy.tar.gz")], version=1)
[713]368def search(string, onlySpecies=True, exact=False):
[649]369    """ Search the NCBI taxonomy database for an organism
370    Arguments::
371            - *string*      Search string
372            - *onlySpecies* Return only taxids of species (and subspecies)
[713]373            - *exact*       Return only taxids of organism that exactly match the string
[649]374    """
[713]375    ids = Taxonomy().search(string, onlySpecies)
376    if exact:
[753]377        ids = [id for id in ids if string in [name(id)] + [t[0] for t in other_names(id)]]
[713]378    return ids
[620]379
[656]380def lineage(taxid):
381    """ Return a list of taxids ordered from the topmost node (root) to taxid.
382    """
383    tax = Taxonomy()
384    result = [taxid]
385    while True:
386        parent = tax.parent(result[-1])
387        result.append(parent)
388        if tax[parent] == "root" or parent=="1":
389            break
390    result.reverse()
391    return result
392   
[658]393def to_taxid(code, mapTo=None):
[753]394    """ See if the code is a valid code in any database and return a set of its taxids.
[649]395    """
[1716]396    from . import obiKEGG, obiGO
[649]397    results = set()
[1716]398    for test in [obiKEGG.to_taxid, obiGO.to_taxid]:
[649]399        try:
400            r = test(code)
401            if type(r) == set:
402                results.update(r)
403            else:
404                results.add(r)
405        except Exception, ex:
406            pass
407
[658]408    if mapTo:
[661]409        mapped = [[parent_id for parent_id in mapTo if parent_id in lineage(r)].pop() for r in results if any(parent_id in lineage(r) for parent_id in mapTo)]
410        if not mapped:
411            t = Taxonomy()
412            subnodes = dict([(r, t.subnodes(r, 4)) for r in results])
413            mapped = [id for id in mapTo if any(id in subnodes[r] for r in results)]
414        results = mapped
[658]415
[649]416    return results
417
[708]418def taxids():
419    return Taxonomy().taxids()
420
[1632]421from . import obiGenomicsUpdate
[620]422
423class Update(obiGenomicsUpdate.Update):
424    def GetDownloadable(self):
425        return [Update.UpdateTaxonomy]
426   
427    def IsUpdatable(self, func, args):
428        from datetime import datetime
[1632]429        from . import obiData
[620]430        if func == Update.UpdateTaxonomy:
431##            stream = urllib2.urlopen("ftp://ftp.ncbi.nih.gov/pub/taxonomy/taxdump.tar.gz")
432##            date = datetime.strptime(stream.headers.get("Last-Modified"), "%a, %d %b %Y %H:%M:%S %Z")
433            ftp = obiData.FtpWorker("ftp.ncbi.nih.gov")
434            size, date = ftp.statFtp("pub/taxonomy/taxdump.tar.gz")
435            return date > self.GetLastUpdateTime(func, args)
436
437    def UpdateTaxonomy(self):
438        Taxonomy.ParseTaxdumpFile(outputdir=self.local_database_path)
439        import tarfile
440        tFile = tarfile.open(os.path.join(self.local_database_path, "ncbi_taxonomy.tar.gz"), "w:gz")
441        tFile.add(os.path.join(self.local_database_path, "ncbi_taxonomy.db"), "ncbi_taxonomy.db")
442        tFile.add(os.path.join(self.local_database_path, "ncbi_taxonomy_inf.db"), "ncbi_taxonomy_inf.db")
443        tFile.close()
[579]444
445if __name__ == "__main__":
[581]446    ids = search("Homo sapiens")
447    print ids
448    print other_names(ids[0])
[632]449   
Note: See TracBrowser for help on using the repository browser.