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

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

Moved '_bioinformatics' into orangecontrib namespace.

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