source: orange-bioinformatics/_bioinformatics/obiOntology.py @ 1742:fa3a9e4af7e6

Revision 1742:fa3a9e4af7e6, 30.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Code style and docstring fixes.

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