source: orange-bioinformatics/_bioinformatics/obiOntology.py @ 1636:10d234fdadb9

Revision 1636:10d234fdadb9, 29.4 KB checked in by mitar, 2 years ago (diff)

Restructuring because we will not be using namespaces.

RevLine 
[1349]1"""\
2obiOntology
3-----------
4
[1326]5Python module for manipulating OBO Ontology files (http://www.obofoundry.org/)
6
[1355]7You can construct an ontology from scratch with custom terms ::
[1326]8
9    >>> term = OBOObject("Term", id="foo:bar", name="Foo bar")
10    >>> print term
11    [Term]
12    id: foo:bar
13    name: Foo bar
14   
15    >>> ontology = OBOOntology()
[1349]16    >>> ontology.add_object(term)
[1355]17    >>> ontology.add_header_tag("created-by", "ales") # add a header tag
18    >>> from StringIO import StringIO
19    >>> buffer = StringIO()
20    >>> ontology.dump(buffer) # Save the ontology to a file like object
21    >>> print buffer.getvalue() # Print the contents of the buffer
22    created-by: ales
23    <BLANKLINE>
24    [Term]
25    id: foo:bar
26    name: Foo bar
27    <BLANKLINE>
[1326]28   
[1355]29To load an ontology from a file pass the file or filename to the OBOOntology
30constructor or call its load method ::
31
32    >>> buffer.seek(0) # rewind
33    >>> ontology = OBOOntology(buffer)
34    >>> # Or
35    >>> buffer.seek(0) # rewind
36    >>> ontology = OBOOntology()
37    >>> ontology.load(buffer)
38   
39See OBOOntology docstring for more details.
40
[1326]41"""
42
43from itertools import chain
44from collections import defaultdict
[1349]45from StringIO import StringIO
[1326]46import itertools
[1349]47import re
[1326]48
[1349]49BUILTIN_OBO_OBJECTS = [
[1326]50"""[Typedef]
51id: is_a
52name: is_a
53range: OBO:TERM_OR_TYPE
54domain: OBO:TERM_OR_TYPE
55definition: The basic subclassing relationship [OBO:defs]"""
56,
57"""[Typedef]
58id: disjoint_from
59name: disjoint_from
60range: OBO:TERM
61domain: OBO:TERM
62definition: Indicates that two classes are disjoint [OBO:defs]"""
63,
64"""[Typedef]
65id: instance_of
66name: instance_of
67range: OBO:TERM
68domain: OBO:INSTANCE
69definition: Indicates the type of an instance [OBO:defs]"""
70,
71"""[Typedef]
72id: inverse_of
73name: inverse_of
74range: OBO:TYPE
75domain: OBO:TYPE
76definition: Indicates that one relationship type is the inverse of another [OBO:defs]"""
77,
78"""[Typedef]
79id: union_of
80name: union_of
81range: OBO:TERM
82domain: OBO:TERM
83definition: Indicates that a term is the union of several others [OBO:defs]"""
84,
85"""[Typedef]
86id: intersection_of
87name: intersection_of
88range: OBO:TERM
89domain: OBO:TERM
90definition: Indicates that a term is the intersection of several others [OBO:defs]"""
91]
[1349]92   
93def _split_and_strip(string, sep):
[1326]94    head, tail = string.split(sep, 1)
95    return head.rstrip(" "), tail.lstrip(" ")
96
97
98class OBOObject(object):
99    """ Represents a generic OBO object (e.g. Term, Typedef, Instance, ...)
100    Example::
[1349]101        >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar")
[1326]102    """
[1349]103    def __init__(self, stanza_type="Term", **kwargs):
[1326]104        """ Init from keyword arguments.
105        Example::
[1349]106            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_="Example definition { modifier=frob } ! Comment")
107            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_=("Example definition", [("modifier", "frob")], "Comment"))
108            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_=("Example definition", [("modifier", "frob")])) # without the comment
109            >>> term = OBOObject(stanza_type="Term", id="FOO:001", name="bar", def_=("Example definition",)) # without the modifiers and comment
[1326]110        """
[1349]111        self.stanza_type = stanza_type
[1326]112       
113        self.modifiers = []
114        self.comments = []
[1349]115        self.tag_values = []
[1326]116        self.values = {}
117       
[1349]118        sorted_tags = sorted(kwargs.iteritems(), key=lambda key_val: chr(1) if key_val[0] == "id" else key_val[0])
119        for tag, value in sorted_tags:
[1326]120            if isinstance(value, basestring):
[1349]121                tag, value, modifiers, comment = self.parse_tag_value(self.name_demangle(tag), value)
[1326]122            elif isinstance(value, tuple):
123                tag, value, modifiers, comment = ((self.name_demangle(tag),) + value + (None, None))[:4]
[1349]124            self.add_tag(tag, value, modifiers, comment)
[1326]125       
126        self.related = set()
[1349]127#        self.related_to = set()
[1326]128           
129    @property
130    def is_annonymous(self):
[1349]131        value = self.get_value("is_annonymous")
[1326]132        return bool(value)
133   
134    def name_mangle(self, tag):
135        """ Mangle tag name if it conflicts with python keyword
136        Example::
137            >>> term.name_mangle("def"), term.name_mangle("class")
138            ('def_', 'class_')
139        """
140        if tag in ["def", "class", "in", "not"]:
141            return tag + "_"
142        else:
143            return tag
144       
145    def name_demangle(self, tag):
146        """ Reverse of name_mangle
147        """
148        if tag in ["def_", "class_", "in_", "not_"]:
149            return tag[:-1]
150        else:
151            return tag
152       
[1349]153    def add_tag(self, tag, value, modifiers=None, comment=None):
[1326]154        """ Add `tag`, `value` pair to the object with optional modifiers and
155        comment.
156        Example::
157            >>> term = OBOObject("Term")
[1349]158            >>> term.add_tag("id", "FOO:002", comment="This is an id")
[1326]159            >>> print term
160            [Term]
161            id: FOO:002 ! This is an id
162             
163        """
[1349]164        tag = intern(tag) # a small speed and memory benefit
165        self.tag_values.append((tag, value))
[1326]166        self.modifiers.append(modifiers)
167        self.comments.append(comment)
168        self.values.setdefault(tag, []).append(value)
169       
170        #  TODO: fix multiple tags grouping
171        if hasattr(self, tag):
172            if isinstance(getattr(self, tag), list):
173                getattr(self, tag).append(value)
174            else:
175                setattr(self, tag, [getattr(self, tag)] + [value])
176        else:
177            setattr(self, self.name_mangle(tag), value)
178           
179    def update(self, other):
180        """ Update the term with tag value pairs from `other`
181        (a OBOObject instance). The tag value pairs are appended
182        to the end except for the `id` tag.
183        """ 
[1349]184        for (tag, value), modifiers, comment in zip(other.tag_values, other.modifiers, other.comments):
[1326]185            if tag != "id":
[1349]186                self.add_tag(tag, value, modifiers, comment)
[1326]187       
[1349]188    def get_value(self, tag, group=True):
[1326]189        if group:
[1349]190            pairs = [pair for pair in self.tag_values if pair[0] == tag]
[1326]191            return pairs
192        else:
193            tag = self.name_mangle(tag)
194            if tag in self.__dict__:
195                return self.__dict__[tag]
196            else:
197                raise ValueError("No value for tag: %s" % tag)
198       
[1349]199    def tag_count(self):
[1326]200        """ Retrun the number of tags in this object
201        """
[1349]202        return len(self.tag_values)
[1326]203   
204    def tags(self):
205        """ Retrun an iterator over the (tag, value) pairs.
206        """
[1349]207        for i in range(self.tag_count()):
208            yield self.tag_values[i] + (self.modifiers[i], self.comments[i])
[1326]209       
[1349]210    def format_single_tag(self, index):
[1326]211        """Return a formated string representing index-th tag pair value
212        Example::
213            >>> term = OBOObject("Term", id="FOO:001", name="bar", def_="Example definition {modifier=frob} ! Comment")
[1349]214            >>> term.format_single_tag(0)
[1326]215            'id: FOO:001'
[1349]216            >>> term.format_single_tag(1)
[1326]217            'def: Example definition { modifier=frob } ! Comment'
218        """
[1349]219        tag, value = self.tag_values[index]
[1326]220        modifiers = self.modifiers[index]
221        comment = self.comments[index]
222        res = ["%s: %s" % (tag, value)]
223        if modifiers:
224            res.append("{ %s }" % modifiers)
225        if comment:
226            res.append("! " + comment)
227        return " ".join(res)
228   
[1349]229    def format_stanza(self):
[1326]230        """ Return a string stanza representation of this object
231        """
[1349]232        stanza = ["[%s]" % self.stanza_type]
233        for i in range(self.tag_count()):
234            stanza.append(self.format_single_tag(i))
[1326]235        return "\n".join(stanza)
236           
237    @classmethod     
[1349]238    def parse_stanza(cls, stanza):
[1326]239        r""" Parse and return an OBOObject instance from a single stanza.
240        Example::
[1349]241            >>> term = OBOObject.parse_stanza("[Term]\nid: FOO:001\nname:bar")
[1326]242            >>> print term.id, term.name
243            FOO:001 bar
244           
245        """
246        lines = stanza.splitlines()
[1349]247        stanza_type = lines[0].strip("[]")
[1326]248        tag_values = []
249        for line in lines[1:]:
250            if ":" in line:
[1349]251                tag_values.append(cls.parse_tag_value(line))
[1326]252       
[1349]253        obo = OBOObject(stanza_type)
[1326]254        for i, (tag, value, modifiers, comment) in enumerate(tag_values):
255#            print tag, value, modifiers, comment
[1349]256            obo.add_tag(tag, value, modifiers, comment)
[1326]257        return obo
258   
259       
260    @classmethod
[1349]261    def parse_tag_value_1(cls, tag_value_pair, *args):
[1326]262        """ Parse and return a four-tuple containing a tag, value, a list of modifier pairs, comment.
263        If no modifiers or comments are present the corresponding entries will be None.
264       
265        Example::
[1349]266            >>> OBOObject.parse_tag_value("foo: bar {modifier=frob} ! Comment")
[1326]267            ('foo', 'bar', 'modifier=frob', 'Comment')
[1349]268            >>> OBOObject.parse_tag_value("foo: bar")
[1326]269            ('foo', 'bar', None, None)
270            >>> #  Can also pass tag, value pair already split   
[1349]271            >>> OBOObject.parse_tag_value("foo", "bar {modifier=frob} ! Comment")
[1326]272            ('foo', 'bar', 'modifier=frob', 'Comment')
273        """
[1349]274        if args and ":" not in tag_value_pair:
275            tag, rest = tag_value_pair, args[0]
[1326]276        else:
[1349]277            tag, rest = _split_and_strip(tag_value_pair, ":")
[1326]278        value, modifiers, comment = None, None, None
279       
280        if "{" in rest:
[1349]281            value, rest = _split_and_strip(rest, "{",)
282            modifiers, rest = _split_and_strip(rest, "}")
[1326]283        if "!" in rest:
284            if value is None:
[1349]285                value, comment = _split_and_strip(rest, "!")
[1326]286            else:
[1349]287                _, comment = _split_and_strip(rest, "!")
[1326]288        if value is None:
289            value = rest
290           
291        if modifiers is not None:
292            modifiers = modifiers #TODO: split modifiers in a list
293           
294        return tag, value, modifiers, comment
[1349]295   
296    _RE_TAG_VALUE = re.compile(r"^(?P<tag>.+?[^\\])\s*:\s*(?P<value>.+?)\s*(?P<modifiers>[^\\]{.+?[^\\]})?\s*(?P<comment>[^\\]!.*)?$")
297    _RE_VALUE = re.compile(r"^\s*(?P<value>.+?)\s*(?P<modifiers>[^\\]{.+?[^\\]})?\s*(?P<comment>[^\\]!.*)?$")
298   
299    @classmethod
300    def parse_tag_value(cls, tag_value_pair, arg=None):
301        """ Parse and return a four-tuple containing a tag, value, a list of modifier pairs, comment.
302        If no modifiers or comments are present the corresponding entries will be None.
303       
304        Example::
305            >>> OBOObject.parse_tag_value("foo: bar {modifier=frob} ! Comment")
306            ('foo', 'bar', 'modifier=frob', 'Comment')
307            >>> OBOObject.parse_tag_value("foo: bar")
308            ('foo', 'bar', None, None)
309            >>> #  Can also pass tag, value pair already split   
310            >>> OBOObject.parse_tag_value("foo", "bar {modifier=frob} ! Comment")
311            ('foo', 'bar', 'modifier=frob', 'Comment')
312           
313        .. warning: This function assumes comment an modifiers are prefixed
314            with a whitespace i.e. 'tag: bla! comment' will be parsed incorrectly!
315        """
316        if arg is not None: # tag_value_pair is actually a tag only
317            tag = tag_value_pair
318            value, modifiers, comment =  cls._RE_VALUE.findall(arg)[0]
319        else:
320            tag, value, modifiers, comment = cls._RE_TAG_VALUE.findall(tag_value_pair)[0]
321        none_if_empyt = lambda val: None if not val.strip() else val.strip()
322        modifiers = modifiers.strip(" {}")
323        comment = comment.lstrip(" !")
324        return (none_if_empyt(tag), none_if_empyt(value),
325                none_if_empyt(modifiers), none_if_empyt(comment))
[1326]326         
[1349]327    def related_objects(self):
[1326]328        """ Return a list of tuple pairs where the first element is relationship (typedef id)
329        is and the second object id whom the relationship applies to.
330        """
[1349]331        result = [(type_id, id) for type_id in ["is_a"] for id in self.values.get(type_id, [])] ##TODO add other defined Typedef ids
[1326]332        result = result + [tuple(r.split(None, 1)) for r in self.values.get("relationship", [])]
333        return result
334
335    def __repr__(self):
336        """ Return a string representation of the object in OBO format
337        """
[1349]338        return self.format_stanza()
[1326]339
340    def __iter__(self):
341        """ Iterates over sub terms
342        """
[1349]343        for type_id, id in self.related_objects():
344            yield (type_id, id)
345       
[1326]346       
347class Term(OBOObject):
348    def __init__(self, *args, **kwargs):
349        OBOObject.__init__(self, "Term", *args, **kwargs)
350
351class Typedef(OBOObject):
352    def __init__(self, *args, **kwargs):
353        OBOObject.__init__(self, "Typedef", *args, **kwargs)
354
355class Instance(OBOObject):
356    def __init__(self, *args, **kwargs):
357        OBOObject.__init__(self, "Instance", *args, **kwargs)
358
[1349]359
[1326]360import re
361
[1349]362class OBOParser(object):
363    r""" A simple parser for .obo files (inspired by xml.dom.pulldom)
[1326]364   
[1349]365    Example::
366        >>> from StringIO import StringIO
367        >>> file = StringIO("header_tag: header_value\n[Term]\nid: FOO { modifier=bar } ! comment\n\n")
368        >>> parser = OBOParser(file)
369        >>> for event, value in parser:
370        ...     print event, value
371        ...     
[1355]372        HEADER_TAG ['header_tag', 'header_value']
[1349]373        START_STANZA Term
374        TAG_VALUE ('id', 'FOO', 'modifier=bar', 'comment')
375        CLOSE_STANZA None
376               
377    """
378    def __init__(self, file):
379        self.file = file
[1326]380       
[1349]381    def parse(self, progress_callback=None):
382        """ Parse the file and yield parse events.
[1326]383       
[1355]384        .. TODO: List events and values
[1326]385        """
[1349]386        data = self.file.read()
[1326]387        header = data[: data.index("\n[")]
388        body = data[data.index("\n[") + 1:]
389        for line in header.splitlines():
390            if line.strip():
[1355]391                yield "HEADER_TAG", line.split(": ", 1)
[1326]392               
393        current = None
394        #  For speed make these functions local
395        startswith = str.startswith
396        endswith = str.endswith
[1349]397        parse_tag_value = OBOObject.parse_tag_value
[1326]398       
[1349]399        for line in body.splitlines():
[1326]400#            line = line.strip()
401            if startswith(line, "[") and endswith(line, "]"):
[1349]402                yield "START_STANZA", line.strip("[]")
403                current = line
[1326]404            elif startswith(line, "!"):
[1349]405                yield "COMMENT", line[1:]
[1326]406            elif line:
[1349]407                yield "TAG_VALUE", parse_tag_value(line)
[1326]408            else: #  empty line is the end of a term
[1349]409                yield "CLOSE_STANZA", None
410                current = None
411        if current is not None:
412            yield "CLOSE_STANZA", None
413   
414    def __iter__(self):
415        """ Iterate over parse events (same as parse())
416        """
417        return self.parse() 
418       
419       
420class OBOOntology(object):
[1355]421    """ The main ontology object.
422    """
423   
[1349]424    BUILTINS = BUILTIN_OBO_OBJECTS
425    def __init__(self, file=None):
426        """ Init an ontology instance from a file like object (.obo format)
427       
428        """
429        self.objects = []
430        self.header_tags = []
431        self.id2term = {}
432        self.alt2id = {}
433        self._resolved_imports = []
434        self._invalid_cache_flag = False
435        self._related_to = {}
436       
437        # First load the built in OBO objects
438        builtins = StringIO("\n" + "\n\n".join(self.BUILTINS) + "\n")
439        self.load(builtins)
440        if file:
441            self.load(file)
442       
443    def add_object(self, object):
444        """ Add OBOObject instance to this ontology.
445        """
446        if object.id in self.id2term:
447            raise ValueError("OBOObject with id: %s already in the ontology" % object.id)
448        self.objects.append(object)
449        self.id2term[object.id] = object
450        self._invalid_cache_flag = True
451       
452    def add_header_tag(self, tag, value):
453        """ Add header tag, value pair to this ontology
454        """
455        self.header_tags.append((tag, value))
456   
457    def load(self, file, progress_callback=None):
458        """ Load terms from a file.
459        """
460        if isinstance(file, basestring):
461            file = open(file, "rb")
462        parser = OBOParser(file)
463        current = None
464        for event, value in parser.parse(progress_callback=progress_callback):
465            if event == "TAG_VALUE":
466                current.add_tag(*value)
467            elif event == "START_STANZA":
468                current = OBOObject(value)
469            elif event == "CLOSE_STANZA":
470                self.add_object(current)
471                current = None
472            elif event == "HEADER_TAG":
473                self.add_header_tag(*value)
474            elif event != "COMMENT":
475                raise Exception("Parse Error! Unknown parse event {0}".format(event))
476           
477        imports = [value for tag, value, in self.header_tags if tag == "import"]
[1326]478       
479        while imports:
480            url = imports.pop(0)
[1349]481            if uri not in self._resolved_imports:
482                imported = self.parse_file(open(url, "rb"))
483                ontology.update(imported)
484                self._resolved_imports.append(uri)
485               
486    def dump(self, file):
487        """ Dump the contents of the ontology to a .obo `file`.
488        """
489        if isinstance(file, basestring):
490            file = open(file, "wb")
491           
492        for key, value in self.header_tags:
[1355]493            file.write(key + ": " + value + "\n")
494           
495        # Skip the builtins
496        for object in self.objects[len(self.BUILTINS):]:
[1349]497            file.write("\n")
498            file.write(object.format_stanza())
499            file.write("\n")
[1326]500   
501    def update(self, other):
502        """ Update this ontology with the terms from another.
503        """
504        for term in other:
505            if term.id in self:
506                if not term.is_annonymous:
507                    self.term(term.id).update(term)
508                else: #  Do nothing
509                    pass 
510            else:
[1349]511                self.add_object(term)
512        self._invalid_cache_flag = True
513       
514    def _cache_validate(self, force=False):
515        """ Update the relations cache if `self._invalid_cache` flag is set.
516        """
517        if self._invalid_cache_flag or force:
518            self._cache_relations()
519           
520    def _cache_relations(self):
521        """ Collect all relations from parent to a child and store it in
522        `self._related_to` member.
523       
524        """
525        related_to = defaultdict(list)
526        for obj in self.objects:
527            for rel_type, id in self.related_terms(obj):
528                term = self.term(id)
529                related_to[term].append((rel_type, obj))
[1326]530               
[1349]531        self._related_to = related_to
532        self._invalid_cache_flag = False
533       
[1326]534    def term(self, id):
535        """ Return the OBOObject associated with this id.
536        """
[1349]537        if isinstance(id, basestring):
538            if id in self.id2term:
539                return self.id2term[id]
540            elif id in self.alt2id:
541                return self.id2term[self.alt2id[id]]
542            else:
[1531]543                raise ValueError("Unknown term id: %r" % id)
[1349]544        elif isinstance(id, OBOObject):
545            return id
[1326]546       
547    def terms(self):
548        """ Return all `Term` instances in the ontology.
549        """
[1349]550        return [obj for obj in self.objects if obj.stanza_type == "Term"]
[1326]551   
[1531]552    def term_by_name(self, name):
553        """ Return the term with name ``name``.
554        """
555        terms = [t for t in self.terms() if t.name == name]
556        if len(terms) != 1:
557            raise ValueError("Unknown term name: %r" % name)
558        return terms[0]
559   
[1326]560    def typedefs(self):
561        """ Return all `Typedef` instances in the ontology.
562        """
[1349]563        return [obj for obj in self.objects if obj.stanza_type == "Typedef"]
[1326]564   
565    def instances(self):
566        """ Return all `Instance` instances in the ontology.
567        """
[1349]568        return [obj for obj in self.objects if obj.stanza_type == "Instance"]
[1326]569       
[1349]570    def related_terms(self, term):
[1326]571        """ Return a list of (rel_type, term_id) tuples where rel_type is
572        relationship type (e.g. 'is_a', 'has_part', ...) and term_id is the
573        id of the term in the relationship.
[1349]574       
[1326]575        """
576        term = self.term(term) if not isinstance(term, OBOObject) else term
577        related = [(tag, value) for tag in ["is_a"] for value in term.values.get(tag, [])] #TODO: add other typedef ids
578        relationships = term.values.get("relationship", [])
579        for rel in relationships:
580            related.append(tuple(rel.split(None, 1)))
581        return related
[1355]582       
583    def edge_types(self):
584        """ Return a list of all edge types in the ontology
585        """
586        return [obj.id for obj in self.objects if obj.stanza_type == "Typedef"]
587   
588    def parent_edges(self, term):
589        """ Return a list of (rel_type, parent_term) tuples
590        """
591        term = self.term(term)
592        parents = []
593        for rel_type, parent in self.related_terms(term):
594            parents.append((rel_type, self.term(parent)))
595        return parents
596       
597    def child_edges(self, term):
598        """ Return a list of (rel_type, source_term) tuples
599        """
600        self._cache_validate()
601        term = self.term(term)
602        return self._related_to.get(term, [])
603       
604       
605    def super_terms(self, term):
606        """ Return a set of all super terms of `term` up to the most general one.
607        """
608        terms = self.parent_terms(term)
609        visited = set()
610        queue = set(terms)
611        while queue:
612            term = queue.pop()
613            visited.add(term)
614            queue.update(self.parent_terms(term) - visited)
615        return visited
616   
617    def sub_terms(self, term):
618        """ Return a set of all sub terms for `term`.
619        """
620        terms = self.child_terms(term)
621        visited = set()
622        queue = set(terms)
623        while queue:
624            term = queue.pop()
625            visited.add(term)
626            queue.update(self.child_terms(term) - visited)
627        return visited
628   
629    def child_terms(self, term):
630        """ Return a set of all child terms for this `term`.
631        """
632        self._cache_validate()
633        term = self.term(term)
634        children = []
635        for rel_type, term in self.child_edges(term):
636            children.append(term)
637        return set(children)
638       
639    def parent_terms(self, term):
640        """ Return a set of all parent terms for this `term`
641        """
642        term = self.term(term)
643        parents = []
644        for rel_type, id in self.parent_edges(term): #term.related_objects():
645            parents.append(self.term(id))
646        return set(parents)
647   
648    def relations(self):
649        """ Return a list of all relations in the ontology.
650        """
651        relations = []
652        for obj in self.objects:
653            for type_id, id in  obj.related:
654                target_term = self.term(id)
655            relations.append((obj, type_id, target_term))
656        return relations
657   
658    def __len__(self):
659        return len(self.objects)
660   
661    def __iter__(self):
662        return iter(self.objects)
663   
664    def __contains__(self, obj):
665        if isinstance(obj, basestring):
666            return obj in self.id2term
667        else:
668            return obj in self.objects
669   
670    def __getitem__(self, key):
671        return self.id2term[key]
672   
673    def has_key(self, key):
674        return self.id2tem.has_key(key)
675   
[1531]676    def traverse_bf(self, term):
677        """ BF traverse of the ontology down from term.
678        """
679        queue = list(self.child_terms(term))
680        while queue:
681            term = queue.pop(0)
682            queue.extend(self.child_terms(term))
683            yield term
684                   
685    def traverse_df(self, term, depth=1e30):
686        """ DF traverse of the ontology down from term.
687        """
688        if depth >= 1:
689            for child in self.child_terms(term):
690                yield child
691                for t in self.traverse_df(child, depth-1):
692                    yield t
693       
694   
[1355]695    def to_network(self, terms=None):
[1349]696        """ Return an Orange.network.Network instance constructed from
697        this ontology.
698       
[1326]699        """
[1349]700        edge_types = self.edge_types()
[1326]701        terms = self.terms()
[1632]702        from Orange.orng import orngNetwork
703        import orange
[1326]704       
[1349]705        network = orngNetwork.Network(len(terms), True, len(edge_types))
[1326]706        network.objects = dict([(term.id, i) for i, term in enumerate(terms)])
707       
708        edges = defaultdict(set)
709        for term in self.terms():
[1349]710            related = self.related_terms(term)
[1326]711            for relType, relTerm in related:
712                edges[(term.id, relTerm)].add(relType)
713               
714        edgeitems = edges.items()
715        for (src, dst), eTypes in edgeitems:
[1349]716            network[src, dst] = [1 if e in eTypes else 0 for e in edge_types]
[1326]717           
718        domain = orange.Domain([orange.StringVariable("id"),
719                                orange.StringVariable("name"),
720                                orange.StringVariable("def"),
721                                ], False)
722       
723        items = orange.ExampleTable(domain)
724        for term in terms:
725            ex = orange.Example(domain, [term.id, term.name, term.values.get("def", [""])[0]])
726            items.append(ex)
727       
728        relationships = set([", ".join(sorted(eTypes)) for (_, _), eTypes in edgeitems])
729        domain = orange.Domain([orange.FloatVariable("u"),
730                                orange.FloatVariable("v"),
[1349]731                                orange.EnumVariable("relationship", values=list(edge_types))
[1326]732                                ], False)
733       
734        id2index = dict([(term.id, i + 1) for i, term in enumerate(terms)])
735        links = orange.ExampleTable(domain)
736        for (src, dst), eTypes in edgeitems:
737            ex = orange.Example(domain, [id2index[src], id2index[dst], eTypes.pop()])
738            links.append(ex)
739           
740        network.items = items
741        network.links = links
742        network.optimization = None
743        return network
[1349]744   
[1355]745    def to_networkx(self, terms=None):
[1349]746        """ Return a NetworkX graph of this ontology
747        """
[1355]748        import networkx
749        graph = networkx.Graph()
[1326]750       
[1355]751        edge_types = self.edge_types()
752       
753        edge_colors = {"is_a": "red"}
754       
755        if terms is None:
756            terms = self.terms()
757        else:
758            terms = [self.term(term) for term in terms]
759            super_terms = [self.super_terms(term) for term in terms]
760            terms = reduce(set.union, super_terms, set(terms))
761           
762        for term in terms:
763            graph.add_node(term.id, name=term.name)
764           
765        for term in terms:
766            for rel_type, rel_term in self.related_terms(term):
767                rel_term = self.term(rel_term)
768                if rel_term in terms:
769                    graph.add_edge(term.id, rel_term.id, label=rel_type, color=edge_colors.get(rel_type, "blue"))
770                   
771        return graph
772   
773    def to_graphviz(self, terms=None):
774        """ Return an pygraphviz.AGraph representation of the ontology in.
775        If `terms` is not `None` it must be a list of terms in the ontology.
776        The graph will in this case contain only the super graph of those
777        terms. 
778       
[1326]779        """
[1355]780        import pygraphviz as pgv
781        graph = pgv.AGraph(directed=True, name="ontology")
[1326]782       
[1355]783        edge_types = self.edge_types()
[1349]784       
[1355]785        edge_colors = {"is_a": "red"}
[1349]786       
[1355]787        if terms is None:
788            terms = self.terms()
[1349]789        else:
[1355]790            terms = [self.term(term) for term in terms]
791            super_terms = [self.super_terms(term) for term in terms]
792            terms = reduce(set.union, super_terms, set(terms))
793           
794        for term in terms:
795            graph.add_node(term.id, name=term.name)
796           
797        for term in terms:
798            for rel_type, rel_term in self.related_terms(term):
799                rel_term = self.term(rel_term)
800                if rel_term in terms:
801                    graph.add_edge(term.id, rel_term.id, label=rel_type, color=edge_colors.get(rel_type, "blue"))
802                   
803        return graph
[1349]804   
805   
806def load(file):
807    """ Load an ontology from a .obo file
808    """
809    return OBOOntology(file)
810   
811   
812def foundry_ontologies():
[1326]813    """ List ontologies available from the OBOFoundry website
814    (`http://www.obofoundry.org/`_)
815    Example::
[1349]816        >>> foundry_ontologies()
[1326]817        [('Biological process', 'http://obo.cvs.sourceforge.net/*checkout*/obo/obo/ontology/genomic-proteomic/gene_ontology_edit.obo'), ...
818   
819    """
820    import urllib2, re
821    stream = urllib2.urlopen("http://www.obofoundry.org/")
822    text = stream.read()
823    pattern = r'<td class=".+?">\s*<a href=".+?">(.+?)</a>\s*</td>\s*<td class=".+?">.*?</td>\s*<td class=".+?">.*?</td>\s*?<td class=".+?">\s*<a href="(.+?obo)">.+?</a>'
824    return re.findall(pattern, text)
825   
826   
827if __name__ == "__main__":
828    import doctest
829    stanza = '''[Term]
830id: FOO:001
831name: bar
[1349]832'''
833    from StringIO import StringIO
834    seinfeld = StringIO("""
835[Typedef]
836id: parent
837
838[Typedef]
839id: child
840inverse_of: parent ! not actually used yet
841
842[Term]
843id: 001
844name: George
845
846[Term]
847id: 002
848name: Estelle
849relationship: parent 001 ! George
850
851[Term]
852id: 003
853name: Frank
854relationship: parent 001 ! George
855
856""") # TODO: fill the ontology with all characters
857    term = OBOObject.parse_stanza(stanza)
858   
859    seinfeld = OBOOntology(seinfeld)
860    print seinfeld.child_edges("001")
861   
[1326]862    doctest.testmod(extraglobs={"stanza": stanza, "term": term}, optionflags=doctest.ELLIPSIS)
[1632]863       
Note: See TracBrowser for help on using the repository browser.