annotate gffparser_bcbio.py @ 8:d4f9b7beb52f

cleaning the repository - GUI make it hard
author vipints <vipin@cbio.mskcc.org>
date Thu, 23 Apr 2015 17:51:14 -0400
parents 6e589f267c14
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
5
6e589f267c14 Uploaded
devteam
parents:
diff changeset
1 """Parse GFF files into features attached to Biopython SeqRecord objects.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
2
6e589f267c14 Uploaded
devteam
parents:
diff changeset
3 This deals with GFF3 formatted files, a tab delimited format for storing
6e589f267c14 Uploaded
devteam
parents:
diff changeset
4 sequence features and annotations:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
5
6e589f267c14 Uploaded
devteam
parents:
diff changeset
6 http://www.sequenceontology.org/gff3.shtml
6e589f267c14 Uploaded
devteam
parents:
diff changeset
7
6e589f267c14 Uploaded
devteam
parents:
diff changeset
8 It will also deal with older GFF versions (GTF/GFF2):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
9
6e589f267c14 Uploaded
devteam
parents:
diff changeset
10 http://www.sanger.ac.uk/Software/formats/GFF/GFF_Spec.shtml
6e589f267c14 Uploaded
devteam
parents:
diff changeset
11 http://mblab.wustl.edu/GTF22.html
6e589f267c14 Uploaded
devteam
parents:
diff changeset
12
6e589f267c14 Uploaded
devteam
parents:
diff changeset
13 The implementation utilizes map/reduce parsing of GFF using Disco. Disco
6e589f267c14 Uploaded
devteam
parents:
diff changeset
14 (http://discoproject.org) is a Map-Reduce framework for Python utilizing
6e589f267c14 Uploaded
devteam
parents:
diff changeset
15 Erlang for parallelization. The code works on a single processor without
6e589f267c14 Uploaded
devteam
parents:
diff changeset
16 Disco using the same architecture.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
17 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
18 import os
6e589f267c14 Uploaded
devteam
parents:
diff changeset
19 import copy
6e589f267c14 Uploaded
devteam
parents:
diff changeset
20 import re
6e589f267c14 Uploaded
devteam
parents:
diff changeset
21 import collections
6e589f267c14 Uploaded
devteam
parents:
diff changeset
22 import urllib
6e589f267c14 Uploaded
devteam
parents:
diff changeset
23 import itertools
6e589f267c14 Uploaded
devteam
parents:
diff changeset
24
6e589f267c14 Uploaded
devteam
parents:
diff changeset
25 # Make defaultdict compatible with versions of python older than 2.4
6e589f267c14 Uploaded
devteam
parents:
diff changeset
26 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
27 collections.defaultdict
6e589f267c14 Uploaded
devteam
parents:
diff changeset
28 except AttributeError:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
29 import _utils
6e589f267c14 Uploaded
devteam
parents:
diff changeset
30 collections.defaultdict = _utils.defaultdict
6e589f267c14 Uploaded
devteam
parents:
diff changeset
31
6e589f267c14 Uploaded
devteam
parents:
diff changeset
32 from Bio.Seq import Seq, UnknownSeq
6e589f267c14 Uploaded
devteam
parents:
diff changeset
33 from Bio.SeqRecord import SeqRecord
6e589f267c14 Uploaded
devteam
parents:
diff changeset
34 from Bio.SeqFeature import SeqFeature, FeatureLocation
6e589f267c14 Uploaded
devteam
parents:
diff changeset
35 from Bio import SeqIO
6e589f267c14 Uploaded
devteam
parents:
diff changeset
36
6e589f267c14 Uploaded
devteam
parents:
diff changeset
37 def _gff_line_map(line, params):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
38 """Map part of Map-Reduce; parses a line of GFF into a dictionary.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
39
6e589f267c14 Uploaded
devteam
parents:
diff changeset
40 Given an input line from a GFF file, this:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
41 - decides if the file passes our filtering limits
6e589f267c14 Uploaded
devteam
parents:
diff changeset
42 - if so:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
43 - breaks it into component elements
6e589f267c14 Uploaded
devteam
parents:
diff changeset
44 - determines the type of attribute (flat, parent, child or annotation)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
45 - generates a dictionary of GFF info which can be serialized as JSON
6e589f267c14 Uploaded
devteam
parents:
diff changeset
46 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
47 gff3_kw_pat = re.compile("\w+=")
6e589f267c14 Uploaded
devteam
parents:
diff changeset
48 def _split_keyvals(keyval_str):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
49 """Split key-value pairs in a GFF2, GTF and GFF3 compatible way.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
50
6e589f267c14 Uploaded
devteam
parents:
diff changeset
51 GFF3 has key value pairs like:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
52 count=9;gene=amx-2;sequence=SAGE:aacggagccg
6e589f267c14 Uploaded
devteam
parents:
diff changeset
53 GFF2 and GTF have:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
54 Sequence "Y74C9A" ; Note "Clone Y74C9A; Genbank AC024206"
6e589f267c14 Uploaded
devteam
parents:
diff changeset
55 name "fgenesh1_pg.C_chr_1000003"; transcriptId 869
6e589f267c14 Uploaded
devteam
parents:
diff changeset
56 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
57 quals = collections.defaultdict(list)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
58 if keyval_str is None:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
59 return quals
6e589f267c14 Uploaded
devteam
parents:
diff changeset
60 # ensembl GTF has a stray semi-colon at the end
6e589f267c14 Uploaded
devteam
parents:
diff changeset
61 if keyval_str[-1] == ';':
6e589f267c14 Uploaded
devteam
parents:
diff changeset
62 keyval_str = keyval_str[:-1]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
63 # GFF2/GTF has a semi-colon with at least one space after it.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
64 # It can have spaces on both sides; wormbase does this.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
65 # GFF3 works with no spaces.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
66 # Split at the first one we can recognize as working
6e589f267c14 Uploaded
devteam
parents:
diff changeset
67 parts = keyval_str.split(" ; ")
6e589f267c14 Uploaded
devteam
parents:
diff changeset
68 if len(parts) == 1:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
69 parts = keyval_str.split("; ")
6e589f267c14 Uploaded
devteam
parents:
diff changeset
70 if len(parts) == 1:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
71 parts = keyval_str.split(";")
6e589f267c14 Uploaded
devteam
parents:
diff changeset
72 # check if we have GFF3 style key-vals (with =)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
73 is_gff2 = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
74 if gff3_kw_pat.match(parts[0]):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
75 is_gff2 = False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
76 key_vals = [p.split('=') for p in parts]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
77 # otherwise, we are separated by a space with a key as the first item
6e589f267c14 Uploaded
devteam
parents:
diff changeset
78 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
79 pieces = []
6e589f267c14 Uploaded
devteam
parents:
diff changeset
80 for p in parts:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
81 # fix misplaced semi-colons in keys in some GFF2 files
6e589f267c14 Uploaded
devteam
parents:
diff changeset
82 if p and p[0] == ';':
6e589f267c14 Uploaded
devteam
parents:
diff changeset
83 p = p[1:]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
84 pieces.append(p.strip().split(" "))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
85 key_vals = [(p[0], " ".join(p[1:])) for p in pieces]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
86 for item in key_vals:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
87 # standard in-spec items are key=value
6e589f267c14 Uploaded
devteam
parents:
diff changeset
88 if len(item) == 2:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
89 key, val = item
6e589f267c14 Uploaded
devteam
parents:
diff changeset
90 # out-of-spec files can have just key values. We set an empty value
6e589f267c14 Uploaded
devteam
parents:
diff changeset
91 # which will be changed to true later to standardize.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
92 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
93 assert len(item) == 1, item
6e589f267c14 Uploaded
devteam
parents:
diff changeset
94 key = item[0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
95 val = ''
6e589f267c14 Uploaded
devteam
parents:
diff changeset
96 # remove quotes in GFF2 files
6e589f267c14 Uploaded
devteam
parents:
diff changeset
97 if (len(val) > 0 and val[0] == '"' and val[-1] == '"'):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
98 val = val[1:-1]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
99 if val:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
100 quals[key].extend([v for v in val.split(',') if v])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
101 # if we don't have a value, make this a key=True/False style
6e589f267c14 Uploaded
devteam
parents:
diff changeset
102 # attribute
6e589f267c14 Uploaded
devteam
parents:
diff changeset
103 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
104 quals[key].append('true')
6e589f267c14 Uploaded
devteam
parents:
diff changeset
105 for key, vals in quals.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
106 quals[key] = [urllib.unquote(v) for v in vals]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
107 return quals, is_gff2
6e589f267c14 Uploaded
devteam
parents:
diff changeset
108
6e589f267c14 Uploaded
devteam
parents:
diff changeset
109 def _nest_gff2_features(gff_parts):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
110 """Provide nesting of GFF2 transcript parts with transcript IDs.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
111
6e589f267c14 Uploaded
devteam
parents:
diff changeset
112 exons and coding sequences are mapped to a parent with a transcript_id
6e589f267c14 Uploaded
devteam
parents:
diff changeset
113 in GFF2. This is implemented differently at different genome centers
6e589f267c14 Uploaded
devteam
parents:
diff changeset
114 and this function attempts to resolve that and map things to the GFF3
6e589f267c14 Uploaded
devteam
parents:
diff changeset
115 way of doing them.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
116 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
117 # map protein or transcript ids to a parent
6e589f267c14 Uploaded
devteam
parents:
diff changeset
118 for transcript_id in ["transcript_id", "transcriptId", "proteinId"]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
119 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
120 gff_parts["quals"]["Parent"] = \
6e589f267c14 Uploaded
devteam
parents:
diff changeset
121 gff_parts["quals"][transcript_id]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
122 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
123 except KeyError:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
124 pass
6e589f267c14 Uploaded
devteam
parents:
diff changeset
125 # case for WormBase GFF -- everything labelled as Transcript or CDS
6e589f267c14 Uploaded
devteam
parents:
diff changeset
126 for flat_name in ["Transcript", "CDS"]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
127 if gff_parts["quals"].has_key(flat_name):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
128 # parent types
6e589f267c14 Uploaded
devteam
parents:
diff changeset
129 if gff_parts["type"] in [flat_name]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
130 if not gff_parts["id"]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
131 gff_parts["id"] = gff_parts["quals"][flat_name][0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
132 gff_parts["quals"]["ID"] = [gff_parts["id"]]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
133 # children types
6e589f267c14 Uploaded
devteam
parents:
diff changeset
134 elif gff_parts["type"] in ["intron", "exon", "three_prime_UTR",
6e589f267c14 Uploaded
devteam
parents:
diff changeset
135 "coding_exon", "five_prime_UTR", "CDS", "stop_codon",
6e589f267c14 Uploaded
devteam
parents:
diff changeset
136 "start_codon"]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
137 gff_parts["quals"]["Parent"] = gff_parts["quals"][flat_name]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
138 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
139
6e589f267c14 Uploaded
devteam
parents:
diff changeset
140 return gff_parts
6e589f267c14 Uploaded
devteam
parents:
diff changeset
141
6e589f267c14 Uploaded
devteam
parents:
diff changeset
142 strand_map = {'+' : 1, '-' : -1, '?' : None, None: None}
6e589f267c14 Uploaded
devteam
parents:
diff changeset
143 line = line.strip()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
144 if line[:2] == "##":
6e589f267c14 Uploaded
devteam
parents:
diff changeset
145 return [('directive', line[2:])]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
146 elif line and line[0] != "#":
6e589f267c14 Uploaded
devteam
parents:
diff changeset
147 parts = line.split('\t')
6e589f267c14 Uploaded
devteam
parents:
diff changeset
148 should_do = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
149 if params.limit_info:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
150 for limit_name, limit_values in params.limit_info.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
151 cur_id = tuple([parts[i] for i in
6e589f267c14 Uploaded
devteam
parents:
diff changeset
152 params.filter_info[limit_name]])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
153 if cur_id not in limit_values:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
154 should_do = False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
155 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
156 if should_do:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
157 assert len(parts) >= 8, line
6e589f267c14 Uploaded
devteam
parents:
diff changeset
158 # not python2.4 compatible but easier to understand
6e589f267c14 Uploaded
devteam
parents:
diff changeset
159 #gff_parts = [(None if p == '.' else p) for p in parts]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
160 gff_parts = []
6e589f267c14 Uploaded
devteam
parents:
diff changeset
161 for p in parts:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
162 if p == ".":
6e589f267c14 Uploaded
devteam
parents:
diff changeset
163 gff_parts.append(None)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
164 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
165 gff_parts.append(p)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
166 gff_info = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
167 # collect all of the base qualifiers for this item
6e589f267c14 Uploaded
devteam
parents:
diff changeset
168 if len(parts) > 8:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
169 quals, is_gff2 = _split_keyvals(gff_parts[8])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
170 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
171 quals, is_gff2 = dict(), False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
172 gff_info["is_gff2"] = is_gff2
6e589f267c14 Uploaded
devteam
parents:
diff changeset
173 if gff_parts[1]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
174 quals["source"].append(gff_parts[1])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
175 if gff_parts[5]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
176 quals["score"].append(gff_parts[5])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
177 if gff_parts[7]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
178 quals["phase"].append(gff_parts[7])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
179 gff_info['quals'] = dict(quals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
180 gff_info['rec_id'] = gff_parts[0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
181 # if we are describing a location, then we are a feature
6e589f267c14 Uploaded
devteam
parents:
diff changeset
182 if gff_parts[3] and gff_parts[4]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
183 gff_info['location'] = [int(gff_parts[3]) - 1,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
184 int(gff_parts[4])]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
185 gff_info['type'] = gff_parts[2]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
186 gff_info['id'] = quals.get('ID', [''])[0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
187 gff_info['strand'] = strand_map.get(gff_parts[6], None)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
188 if is_gff2:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
189 gff_info = _nest_gff2_features(gff_info)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
190 # features that have parents need to link so we can pick up
6e589f267c14 Uploaded
devteam
parents:
diff changeset
191 # the relationship
6e589f267c14 Uploaded
devteam
parents:
diff changeset
192 if gff_info['quals'].has_key('Parent'):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
193 # check for self referential parent/child relationships
6e589f267c14 Uploaded
devteam
parents:
diff changeset
194 # remove the ID, which is not useful
6e589f267c14 Uploaded
devteam
parents:
diff changeset
195 for p in gff_info['quals']['Parent']:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
196 if p == gff_info['id']:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
197 gff_info['id'] = ''
6e589f267c14 Uploaded
devteam
parents:
diff changeset
198 del gff_info['quals']['ID']
6e589f267c14 Uploaded
devteam
parents:
diff changeset
199 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
200 final_key = 'child'
6e589f267c14 Uploaded
devteam
parents:
diff changeset
201 elif gff_info['id']:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
202 final_key = 'parent'
6e589f267c14 Uploaded
devteam
parents:
diff changeset
203 # Handle flat features
6e589f267c14 Uploaded
devteam
parents:
diff changeset
204 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
205 final_key = 'feature'
6e589f267c14 Uploaded
devteam
parents:
diff changeset
206 # otherwise, associate these annotations with the full record
6e589f267c14 Uploaded
devteam
parents:
diff changeset
207 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
208 final_key = 'annotation'
6e589f267c14 Uploaded
devteam
parents:
diff changeset
209 if params.jsonify:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
210 return [(final_key, simplejson.dumps(gff_info))]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
211 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
212 return [(final_key, gff_info)]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
213 return []
6e589f267c14 Uploaded
devteam
parents:
diff changeset
214
6e589f267c14 Uploaded
devteam
parents:
diff changeset
215 def _gff_line_reduce(map_results, out, params):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
216 """Reduce part of Map-Reduce; combines results of parsed features.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
217 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
218 final_items = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
219 for gff_type, final_val in map_results:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
220 if params.jsonify and gff_type not in ['directive']:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
221 final_val = simplejson.loads(final_val)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
222 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
223 final_items[gff_type].append(final_val)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
224 except KeyError:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
225 final_items[gff_type] = [final_val]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
226 for key, vals in final_items.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
227 if params.jsonify:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
228 vals = simplejson.dumps(vals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
229 out.add(key, vals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
230
6e589f267c14 Uploaded
devteam
parents:
diff changeset
231 class _MultiIDRemapper:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
232 """Provide an ID remapping for cases where a parent has a non-unique ID.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
233
6e589f267c14 Uploaded
devteam
parents:
diff changeset
234 Real life GFF3 cases have non-unique ID attributes, which we fix here
6e589f267c14 Uploaded
devteam
parents:
diff changeset
235 by using the unique sequence region to assign children to the right
6e589f267c14 Uploaded
devteam
parents:
diff changeset
236 parent.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
237 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
238 def __init__(self, base_id, all_parents):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
239 self._base_id = base_id
6e589f267c14 Uploaded
devteam
parents:
diff changeset
240 self._parents = all_parents
6e589f267c14 Uploaded
devteam
parents:
diff changeset
241
6e589f267c14 Uploaded
devteam
parents:
diff changeset
242 def remap_id(self, feature_dict):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
243 rstart, rend = feature_dict['location']
6e589f267c14 Uploaded
devteam
parents:
diff changeset
244 for index, parent in enumerate(self._parents):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
245 pstart, pend = parent['location']
6e589f267c14 Uploaded
devteam
parents:
diff changeset
246 if rstart >= pstart and rend <= pend:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
247 if index > 0:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
248 return ("%s_%s" % (self._base_id, index + 1))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
249 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
250 return self._base_id
6e589f267c14 Uploaded
devteam
parents:
diff changeset
251 raise ValueError("Did not find remapped ID location: %s, %s, %s" % (
6e589f267c14 Uploaded
devteam
parents:
diff changeset
252 self._base_id, [p['location'] for p in self._parents],
6e589f267c14 Uploaded
devteam
parents:
diff changeset
253 feature_dict['location']))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
254
6e589f267c14 Uploaded
devteam
parents:
diff changeset
255 class _AbstractMapReduceGFF:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
256 """Base class providing general GFF parsing for local and remote classes.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
257
6e589f267c14 Uploaded
devteam
parents:
diff changeset
258 This class should be subclassed to provide a concrete class to parse
6e589f267c14 Uploaded
devteam
parents:
diff changeset
259 GFF under specific conditions. These classes need to implement
6e589f267c14 Uploaded
devteam
parents:
diff changeset
260 the _gff_process function, which returns a dictionary of SeqRecord
6e589f267c14 Uploaded
devteam
parents:
diff changeset
261 information.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
262 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
263 def __init__(self, create_missing=True):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
264 """Initialize GFF parser
6e589f267c14 Uploaded
devteam
parents:
diff changeset
265
6e589f267c14 Uploaded
devteam
parents:
diff changeset
266 create_missing - If True, create blank records for GFF ids not in
6e589f267c14 Uploaded
devteam
parents:
diff changeset
267 the base_dict. If False, an error will be raised.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
268 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
269 self._create_missing = create_missing
6e589f267c14 Uploaded
devteam
parents:
diff changeset
270 self._map_fn = _gff_line_map
6e589f267c14 Uploaded
devteam
parents:
diff changeset
271 self._reduce_fn = _gff_line_reduce
6e589f267c14 Uploaded
devteam
parents:
diff changeset
272 self._examiner = GFFExaminer()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
273
6e589f267c14 Uploaded
devteam
parents:
diff changeset
274 def _gff_process(self, gff_files, limit_info, target_lines=None):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
275 raise NotImplementedError("Derived class must define")
6e589f267c14 Uploaded
devteam
parents:
diff changeset
276
6e589f267c14 Uploaded
devteam
parents:
diff changeset
277 def parse(self, gff_files, base_dict=None, limit_info=None):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
278 """Parse a GFF file, returning an iterator of SeqRecords.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
279
6e589f267c14 Uploaded
devteam
parents:
diff changeset
280 limit_info - A dictionary specifying the regions of the GFF file
6e589f267c14 Uploaded
devteam
parents:
diff changeset
281 which should be extracted. This allows only relevant portions of a file
6e589f267c14 Uploaded
devteam
parents:
diff changeset
282 to be parsed.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
283
6e589f267c14 Uploaded
devteam
parents:
diff changeset
284 base_dict - A base dictionary of SeqRecord objects which may be
6e589f267c14 Uploaded
devteam
parents:
diff changeset
285 pre-populated with sequences and other features. The new features from
6e589f267c14 Uploaded
devteam
parents:
diff changeset
286 the GFF file will be added to this dictionary.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
287 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
288 for rec in self.parse_in_parts(gff_files, base_dict, limit_info):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
289 yield rec
6e589f267c14 Uploaded
devteam
parents:
diff changeset
290
6e589f267c14 Uploaded
devteam
parents:
diff changeset
291 def parse_in_parts(self, gff_files, base_dict=None, limit_info=None,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
292 target_lines=None):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
293 """Parse a region of a GFF file specified, returning info as generated.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
294
6e589f267c14 Uploaded
devteam
parents:
diff changeset
295 target_lines -- The number of lines in the file which should be used
6e589f267c14 Uploaded
devteam
parents:
diff changeset
296 for each partial parse. This should be determined based on available
6e589f267c14 Uploaded
devteam
parents:
diff changeset
297 memory.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
298 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
299 for results in self.parse_simple(gff_files, limit_info, target_lines):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
300 if base_dict is None:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
301 cur_dict = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
302 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
303 cur_dict = copy.deepcopy(base_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
304 cur_dict = self._results_to_features(cur_dict, results)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
305 all_ids = cur_dict.keys()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
306 all_ids.sort()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
307 for cur_id in all_ids:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
308 yield cur_dict[cur_id]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
309
6e589f267c14 Uploaded
devteam
parents:
diff changeset
310 def parse_simple(self, gff_files, limit_info=None, target_lines=1):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
311 """Simple parse which does not build or nest features.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
312
6e589f267c14 Uploaded
devteam
parents:
diff changeset
313 This returns a simple dictionary representation of each line in the
6e589f267c14 Uploaded
devteam
parents:
diff changeset
314 GFF file.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
315 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
316 # gracefully handle a single file passed
6e589f267c14 Uploaded
devteam
parents:
diff changeset
317 if not isinstance(gff_files, (list, tuple)):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
318 gff_files = [gff_files]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
319 limit_info = self._normalize_limit_info(limit_info)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
320 for results in self._gff_process(gff_files, limit_info, target_lines):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
321 yield results
6e589f267c14 Uploaded
devteam
parents:
diff changeset
322
6e589f267c14 Uploaded
devteam
parents:
diff changeset
323 def _normalize_limit_info(self, limit_info):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
324 """Turn all limit information into tuples for identical comparisons.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
325 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
326 final_limit_info = {}
6e589f267c14 Uploaded
devteam
parents:
diff changeset
327 if limit_info:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
328 for key, values in limit_info.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
329 final_limit_info[key] = []
6e589f267c14 Uploaded
devteam
parents:
diff changeset
330 for v in values:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
331 if isinstance(v, str):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
332 final_limit_info[key].append((v,))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
333 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
334 final_limit_info[key].append(tuple(v))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
335 return final_limit_info
6e589f267c14 Uploaded
devteam
parents:
diff changeset
336
6e589f267c14 Uploaded
devteam
parents:
diff changeset
337 def _results_to_features(self, base, results):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
338 """Add parsed dictionaries of results to Biopython SeqFeatures.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
339 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
340 base = self._add_annotations(base, results.get('annotation', []))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
341 for feature in results.get('feature', []):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
342 (_, base) = self._add_toplevel_feature(base, feature)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
343 base = self._add_parent_child_features(base, results.get('parent', []),
6e589f267c14 Uploaded
devteam
parents:
diff changeset
344 results.get('child', []))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
345 base = self._add_seqs(base, results.get('fasta', []))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
346 base = self._add_directives(base, results.get('directive', []))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
347 return base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
348
6e589f267c14 Uploaded
devteam
parents:
diff changeset
349 def _add_directives(self, base, directives):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
350 """Handle any directives or meta-data in the GFF file.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
351
6e589f267c14 Uploaded
devteam
parents:
diff changeset
352 Relevant items are added as annotation meta-data to each record.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
353 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
354 dir_keyvals = collections.defaultdict(list)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
355 for directive in directives:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
356 parts = directive.split()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
357 if len(parts) > 1:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
358 key = parts[0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
359 if len(parts) == 2:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
360 val = parts[1]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
361 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
362 val = tuple(parts[1:])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
363 dir_keyvals[key].append(val)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
364 for key, vals in dir_keyvals.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
365 for rec in base.values():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
366 self._add_ann_to_rec(rec, key, vals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
367 return base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
368
6e589f267c14 Uploaded
devteam
parents:
diff changeset
369 def _add_seqs(self, base, recs):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
370 """Add sequence information contained in the GFF3 to records.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
371 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
372 for rec in recs:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
373 if base.has_key(rec.id):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
374 base[rec.id].seq = rec.seq
6e589f267c14 Uploaded
devteam
parents:
diff changeset
375 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
376 base[rec.id] = rec
6e589f267c14 Uploaded
devteam
parents:
diff changeset
377 return base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
378
6e589f267c14 Uploaded
devteam
parents:
diff changeset
379 def _add_parent_child_features(self, base, parents, children):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
380 """Add nested features with parent child relationships.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
381 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
382 multi_remap = self._identify_dup_ids(parents)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
383 # add children features
6e589f267c14 Uploaded
devteam
parents:
diff changeset
384 children_prep = collections.defaultdict(list)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
385 for child_dict in children:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
386 child_feature = self._get_feature(child_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
387 for pindex, pid in enumerate(child_feature.qualifiers['Parent']):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
388 if multi_remap.has_key(pid):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
389 pid = multi_remap[pid].remap_id(child_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
390 child_feature.qualifiers['Parent'][pindex] = pid
6e589f267c14 Uploaded
devteam
parents:
diff changeset
391 children_prep[pid].append((child_dict['rec_id'],
6e589f267c14 Uploaded
devteam
parents:
diff changeset
392 child_feature))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
393 children = dict(children_prep)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
394 # add children to parents that exist
6e589f267c14 Uploaded
devteam
parents:
diff changeset
395 for cur_parent_dict in parents:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
396 cur_id = cur_parent_dict['id']
6e589f267c14 Uploaded
devteam
parents:
diff changeset
397 if multi_remap.has_key(cur_id):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
398 cur_parent_dict['id'] = multi_remap[cur_id].remap_id(
6e589f267c14 Uploaded
devteam
parents:
diff changeset
399 cur_parent_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
400 cur_parent, base = self._add_toplevel_feature(base, cur_parent_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
401 cur_parent, children = self._add_children_to_parent(cur_parent,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
402 children)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
403 # create parents for children without them (GFF2 or split/bad files)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
404 while len(children) > 0:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
405 parent_id, cur_children = itertools.islice(children.items(),
6e589f267c14 Uploaded
devteam
parents:
diff changeset
406 1).next()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
407 # one child, do not nest it
6e589f267c14 Uploaded
devteam
parents:
diff changeset
408 if len(cur_children) == 1:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
409 rec_id, child = cur_children[0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
410 loc = (child.location.nofuzzy_start, child.location.nofuzzy_end)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
411 rec, base = self._get_rec(base,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
412 dict(rec_id=rec_id, location=loc))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
413 rec.features.append(child)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
414 del children[parent_id]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
415 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
416 cur_parent, base = self._add_missing_parent(base, parent_id,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
417 cur_children)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
418 cur_parent, children = self._add_children_to_parent(cur_parent,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
419 children)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
420 return base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
421
6e589f267c14 Uploaded
devteam
parents:
diff changeset
422 def _identify_dup_ids(self, parents):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
423 """Identify duplicated ID attributes in potential nested parents.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
424
6e589f267c14 Uploaded
devteam
parents:
diff changeset
425 According to the GFF3 spec ID attributes are supposed to be unique
6e589f267c14 Uploaded
devteam
parents:
diff changeset
426 for a file, but this is not always true in practice. This looks
6e589f267c14 Uploaded
devteam
parents:
diff changeset
427 for duplicates, and provides unique IDs sorted by locations.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
428 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
429 multi_ids = collections.defaultdict(list)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
430 for parent in parents:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
431 multi_ids[parent['id']].append(parent)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
432 multi_ids = [(mid, parents) for (mid, parents) in multi_ids.items()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
433 if len(parents) > 1]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
434 multi_remap = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
435 for mid, parents in multi_ids:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
436 multi_remap[mid] = _MultiIDRemapper(mid, parents)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
437 return multi_remap
6e589f267c14 Uploaded
devteam
parents:
diff changeset
438
6e589f267c14 Uploaded
devteam
parents:
diff changeset
439 def _add_children_to_parent(self, cur_parent, children):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
440 """Recursively add children to parent features.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
441 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
442 if children.has_key(cur_parent.id):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
443 cur_children = children[cur_parent.id]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
444 for rec_id, cur_child in cur_children:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
445 cur_child, children = self._add_children_to_parent(cur_child,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
446 children)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
447 cur_parent.location_operator = "join"
6e589f267c14 Uploaded
devteam
parents:
diff changeset
448 cur_parent.sub_features.append(cur_child)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
449 del children[cur_parent.id]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
450 return cur_parent, children
6e589f267c14 Uploaded
devteam
parents:
diff changeset
451
6e589f267c14 Uploaded
devteam
parents:
diff changeset
452 def _add_annotations(self, base, anns):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
453 """Add annotation data from the GFF file to records.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
454 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
455 # add these as a list of annotations, checking not to overwrite
6e589f267c14 Uploaded
devteam
parents:
diff changeset
456 # current values
6e589f267c14 Uploaded
devteam
parents:
diff changeset
457 for ann in anns:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
458 rec, base = self._get_rec(base, ann)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
459 for key, vals in ann['quals'].items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
460 self._add_ann_to_rec(rec, key, vals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
461 return base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
462
6e589f267c14 Uploaded
devteam
parents:
diff changeset
463 def _add_ann_to_rec(self, rec, key, vals):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
464 """Add a key/value annotation to the given SeqRecord.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
465 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
466 if rec.annotations.has_key(key):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
467 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
468 rec.annotations[key].extend(vals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
469 except AttributeError:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
470 rec.annotations[key] = [rec.annotations[key]] + vals
6e589f267c14 Uploaded
devteam
parents:
diff changeset
471 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
472 rec.annotations[key] = vals
6e589f267c14 Uploaded
devteam
parents:
diff changeset
473
6e589f267c14 Uploaded
devteam
parents:
diff changeset
474 def _get_rec(self, base, info_dict):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
475 """Retrieve a record to add features to.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
476 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
477 max_loc = info_dict.get('location', (0, 1))[1]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
478 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
479 cur_rec = base[info_dict['rec_id']]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
480 # update generated unknown sequences with the expected maximum length
6e589f267c14 Uploaded
devteam
parents:
diff changeset
481 if isinstance(cur_rec.seq, UnknownSeq):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
482 cur_rec.seq._length = max([max_loc, cur_rec.seq._length])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
483 return cur_rec, base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
484 except KeyError:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
485 if self._create_missing:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
486 new_rec = SeqRecord(UnknownSeq(max_loc), info_dict['rec_id'])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
487 base[info_dict['rec_id']] = new_rec
6e589f267c14 Uploaded
devteam
parents:
diff changeset
488 return new_rec, base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
489 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
490 raise
6e589f267c14 Uploaded
devteam
parents:
diff changeset
491
6e589f267c14 Uploaded
devteam
parents:
diff changeset
492 def _add_missing_parent(self, base, parent_id, cur_children):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
493 """Add a new feature that is missing from the GFF file.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
494 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
495 base_rec_id = list(set(c[0] for c in cur_children))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
496 assert len(base_rec_id) == 1
6e589f267c14 Uploaded
devteam
parents:
diff changeset
497 feature_dict = dict(id=parent_id, strand=None,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
498 type="inferred_parent", quals=dict(ID=[parent_id]),
6e589f267c14 Uploaded
devteam
parents:
diff changeset
499 rec_id=base_rec_id[0])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
500 coords = [(c.location.nofuzzy_start, c.location.nofuzzy_end)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
501 for r, c in cur_children]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
502 feature_dict["location"] = (min([c[0] for c in coords]),
6e589f267c14 Uploaded
devteam
parents:
diff changeset
503 max([c[1] for c in coords]))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
504 return self._add_toplevel_feature(base, feature_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
505
6e589f267c14 Uploaded
devteam
parents:
diff changeset
506 def _add_toplevel_feature(self, base, feature_dict):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
507 """Add a toplevel non-nested feature to the appropriate record.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
508 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
509 new_feature = self._get_feature(feature_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
510 rec, base = self._get_rec(base, feature_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
511 rec.features.append(new_feature)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
512 return new_feature, base
6e589f267c14 Uploaded
devteam
parents:
diff changeset
513
6e589f267c14 Uploaded
devteam
parents:
diff changeset
514 def _get_feature(self, feature_dict):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
515 """Retrieve a Biopython feature from our dictionary representation.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
516 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
517 location = FeatureLocation(*feature_dict['location'])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
518 new_feature = SeqFeature(location, feature_dict['type'],
6e589f267c14 Uploaded
devteam
parents:
diff changeset
519 id=feature_dict['id'], strand=feature_dict['strand'])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
520 new_feature.qualifiers = feature_dict['quals']
6e589f267c14 Uploaded
devteam
parents:
diff changeset
521 return new_feature
6e589f267c14 Uploaded
devteam
parents:
diff changeset
522
6e589f267c14 Uploaded
devteam
parents:
diff changeset
523 def _parse_fasta(self, in_handle):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
524 """Parse FASTA sequence information contained in the GFF3 file.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
525 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
526 return list(SeqIO.parse(in_handle, "fasta"))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
527
6e589f267c14 Uploaded
devteam
parents:
diff changeset
528 class _GFFParserLocalOut:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
529 """Provide a collector for local GFF MapReduce file parsing.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
530 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
531 def __init__(self, smart_breaks=False):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
532 self._items = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
533 self._smart_breaks = smart_breaks
6e589f267c14 Uploaded
devteam
parents:
diff changeset
534 self._missing_keys = collections.defaultdict(int)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
535 self._last_parent = None
6e589f267c14 Uploaded
devteam
parents:
diff changeset
536 self.can_break = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
537 self.num_lines = 0
6e589f267c14 Uploaded
devteam
parents:
diff changeset
538
6e589f267c14 Uploaded
devteam
parents:
diff changeset
539 def add(self, key, vals):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
540 if self._smart_breaks:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
541 # if we are not GFF2 we expect parents and break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
542 # based on not having missing ones
6e589f267c14 Uploaded
devteam
parents:
diff changeset
543 if key == 'directive':
6e589f267c14 Uploaded
devteam
parents:
diff changeset
544 if vals[0] == '#':
6e589f267c14 Uploaded
devteam
parents:
diff changeset
545 self.can_break = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
546 self._last_parent = None
6e589f267c14 Uploaded
devteam
parents:
diff changeset
547 elif not vals[0].get("is_gff2", False):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
548 self._update_missing_parents(key, vals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
549 self.can_break = (len(self._missing_keys) == 0)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
550 # break when we are done with stretches of child features
6e589f267c14 Uploaded
devteam
parents:
diff changeset
551 elif key != 'child':
6e589f267c14 Uploaded
devteam
parents:
diff changeset
552 self.can_break = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
553 self._last_parent = None
6e589f267c14 Uploaded
devteam
parents:
diff changeset
554 # break when we have lots of child features in a row
6e589f267c14 Uploaded
devteam
parents:
diff changeset
555 # and change between parents
6e589f267c14 Uploaded
devteam
parents:
diff changeset
556 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
557 cur_parent = vals[0]["quals"]["Parent"][0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
558 if (self._last_parent):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
559 self.can_break = (cur_parent != self._last_parent)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
560 self._last_parent = cur_parent
6e589f267c14 Uploaded
devteam
parents:
diff changeset
561 self.num_lines += 1
6e589f267c14 Uploaded
devteam
parents:
diff changeset
562 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
563 self._items[key].extend(vals)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
564 except KeyError:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
565 self._items[key] = vals
6e589f267c14 Uploaded
devteam
parents:
diff changeset
566
6e589f267c14 Uploaded
devteam
parents:
diff changeset
567 def _update_missing_parents(self, key, vals):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
568 # smart way of deciding if we can break this.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
569 # if this is too much, can go back to not breaking in the
6e589f267c14 Uploaded
devteam
parents:
diff changeset
570 # middle of children
6e589f267c14 Uploaded
devteam
parents:
diff changeset
571 if key in ["child"]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
572 for val in vals:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
573 for p_id in val["quals"]["Parent"]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
574 self._missing_keys[p_id] += 1
6e589f267c14 Uploaded
devteam
parents:
diff changeset
575 for val in vals:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
576 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
577 del self._missing_keys[val["quals"]["ID"][0]]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
578 except KeyError:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
579 pass
6e589f267c14 Uploaded
devteam
parents:
diff changeset
580
6e589f267c14 Uploaded
devteam
parents:
diff changeset
581 def has_items(self):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
582 return len(self._items) > 0
6e589f267c14 Uploaded
devteam
parents:
diff changeset
583
6e589f267c14 Uploaded
devteam
parents:
diff changeset
584 def get_results(self):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
585 self._last_parent = None
6e589f267c14 Uploaded
devteam
parents:
diff changeset
586 return self._items
6e589f267c14 Uploaded
devteam
parents:
diff changeset
587
6e589f267c14 Uploaded
devteam
parents:
diff changeset
588 class GFFParser(_AbstractMapReduceGFF):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
589 """Local GFF parser providing standardized parsing of GFF3 and GFF2 files.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
590 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
591 def __init__(self, line_adjust_fn=None, create_missing=True):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
592 _AbstractMapReduceGFF.__init__(self, create_missing=create_missing)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
593 self._line_adjust_fn = line_adjust_fn
6e589f267c14 Uploaded
devteam
parents:
diff changeset
594
6e589f267c14 Uploaded
devteam
parents:
diff changeset
595 def _gff_process(self, gff_files, limit_info, target_lines):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
596 """Process GFF addition without any parallelization.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
597
6e589f267c14 Uploaded
devteam
parents:
diff changeset
598 In addition to limit filtering, this accepts a target_lines attribute
6e589f267c14 Uploaded
devteam
parents:
diff changeset
599 which provides a number of lines to parse before returning results.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
600 This allows partial parsing of a file to prevent memory issues.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
601 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
602 line_gen = self._file_line_generator(gff_files)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
603 for out in self._lines_to_out_info(line_gen, limit_info, target_lines):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
604 yield out
6e589f267c14 Uploaded
devteam
parents:
diff changeset
605
6e589f267c14 Uploaded
devteam
parents:
diff changeset
606 def _file_line_generator(self, gff_files):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
607 """Generate single lines from a set of GFF files.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
608 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
609 for gff_file in gff_files:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
610 if hasattr(gff_file, "read"):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
611 need_close = False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
612 in_handle = gff_file
6e589f267c14 Uploaded
devteam
parents:
diff changeset
613 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
614 need_close = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
615 in_handle = open(gff_file)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
616 found_seqs = False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
617 while 1:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
618 line = in_handle.readline()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
619 if not line:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
620 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
621 yield line
6e589f267c14 Uploaded
devteam
parents:
diff changeset
622 if need_close:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
623 in_handle.close()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
624
6e589f267c14 Uploaded
devteam
parents:
diff changeset
625 def _lines_to_out_info(self, line_iter, limit_info=None,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
626 target_lines=None):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
627 """Generate SeqRecord and SeqFeatures from GFF file lines.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
628 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
629 params = self._examiner._get_local_params(limit_info)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
630 out_info = _GFFParserLocalOut((target_lines is not None and
6e589f267c14 Uploaded
devteam
parents:
diff changeset
631 target_lines > 1))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
632 found_seqs = False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
633 for line in line_iter:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
634 results = self._map_fn(line, params)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
635 if self._line_adjust_fn and results:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
636 if results[0][0] not in ['directive']:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
637 results = [(results[0][0],
6e589f267c14 Uploaded
devteam
parents:
diff changeset
638 self._line_adjust_fn(results[0][1]))]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
639 self._reduce_fn(results, out_info, params)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
640 if (target_lines and out_info.num_lines >= target_lines and
6e589f267c14 Uploaded
devteam
parents:
diff changeset
641 out_info.can_break):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
642 yield out_info.get_results()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
643 out_info = _GFFParserLocalOut((target_lines is not None and
6e589f267c14 Uploaded
devteam
parents:
diff changeset
644 target_lines > 1))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
645 if (results and results[0][0] == 'directive' and
6e589f267c14 Uploaded
devteam
parents:
diff changeset
646 results[0][1] == 'FASTA'):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
647 found_seqs = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
648 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
649
6e589f267c14 Uploaded
devteam
parents:
diff changeset
650 class FakeHandle:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
651 def __init__(self, line_iter):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
652 self._iter = line_iter
6e589f267c14 Uploaded
devteam
parents:
diff changeset
653 def read(self):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
654 return "".join(l for l in self._iter)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
655 def readline(self):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
656 try:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
657 return self._iter.next()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
658 except StopIteration:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
659 return ""
6e589f267c14 Uploaded
devteam
parents:
diff changeset
660
6e589f267c14 Uploaded
devteam
parents:
diff changeset
661 if found_seqs:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
662 fasta_recs = self._parse_fasta(FakeHandle(line_iter))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
663 out_info.add('fasta', fasta_recs)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
664 if out_info.has_items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
665 yield out_info.get_results()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
666
6e589f267c14 Uploaded
devteam
parents:
diff changeset
667 class DiscoGFFParser(_AbstractMapReduceGFF):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
668 """GFF Parser with parallelization through Disco (http://discoproject.org.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
669 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
670 def __init__(self, disco_host, create_missing=True):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
671 """Initialize parser.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
672
6e589f267c14 Uploaded
devteam
parents:
diff changeset
673 disco_host - Web reference to a Disco host which will be used for
6e589f267c14 Uploaded
devteam
parents:
diff changeset
674 parallelizing the GFF reading job.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
675 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
676 _AbstractMapReduceGFF.__init__(self, create_missing=create_missing)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
677 self._disco_host = disco_host
6e589f267c14 Uploaded
devteam
parents:
diff changeset
678
6e589f267c14 Uploaded
devteam
parents:
diff changeset
679 def _gff_process(self, gff_files, limit_info, target_lines=None):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
680 """Process GFF addition, using Disco to parallelize the process.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
681 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
682 assert target_lines is None, "Cannot split parallelized jobs"
6e589f267c14 Uploaded
devteam
parents:
diff changeset
683 # make these imports local; only need them when using disco
6e589f267c14 Uploaded
devteam
parents:
diff changeset
684 import simplejson
6e589f267c14 Uploaded
devteam
parents:
diff changeset
685 import disco
6e589f267c14 Uploaded
devteam
parents:
diff changeset
686 # absolute path names unless they are special disco files
6e589f267c14 Uploaded
devteam
parents:
diff changeset
687 full_files = []
6e589f267c14 Uploaded
devteam
parents:
diff changeset
688 for f in gff_files:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
689 if f.split(":")[0] != "disco":
6e589f267c14 Uploaded
devteam
parents:
diff changeset
690 full_files.append(os.path.abspath(f))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
691 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
692 full_files.append(f)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
693 results = disco.job(self._disco_host, name="gff_reader",
6e589f267c14 Uploaded
devteam
parents:
diff changeset
694 input=full_files,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
695 params=disco.Params(limit_info=limit_info, jsonify=True,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
696 filter_info=self._examiner._filter_info),
6e589f267c14 Uploaded
devteam
parents:
diff changeset
697 required_modules=["simplejson", "collections", "re"],
6e589f267c14 Uploaded
devteam
parents:
diff changeset
698 map=self._map_fn, reduce=self._reduce_fn)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
699 processed = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
700 for out_key, out_val in disco.result_iterator(results):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
701 processed[out_key] = simplejson.loads(out_val)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
702 yield processed
6e589f267c14 Uploaded
devteam
parents:
diff changeset
703
6e589f267c14 Uploaded
devteam
parents:
diff changeset
704 def parse(gff_files, base_dict=None, limit_info=None, target_lines=None):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
705 """High level interface to parse GFF files into SeqRecords and SeqFeatures.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
706 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
707 parser = GFFParser()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
708 for rec in parser.parse_in_parts(gff_files, base_dict, limit_info,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
709 target_lines):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
710 yield rec
6e589f267c14 Uploaded
devteam
parents:
diff changeset
711
6e589f267c14 Uploaded
devteam
parents:
diff changeset
712 def _file_or_handle(fn):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
713 """Decorator to handle either an input handle or a file.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
714 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
715 def _file_or_handle_inside(*args, **kwargs):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
716 in_file = args[1]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
717 if hasattr(in_file, "read"):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
718 need_close = False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
719 in_handle = in_file
6e589f267c14 Uploaded
devteam
parents:
diff changeset
720 else:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
721 need_close = True
6e589f267c14 Uploaded
devteam
parents:
diff changeset
722 in_handle = open(in_file)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
723 args = (args[0], in_handle) + args[2:]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
724 out = fn(*args, **kwargs)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
725 if need_close:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
726 in_handle.close()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
727 return out
6e589f267c14 Uploaded
devteam
parents:
diff changeset
728 return _file_or_handle_inside
6e589f267c14 Uploaded
devteam
parents:
diff changeset
729
6e589f267c14 Uploaded
devteam
parents:
diff changeset
730 class GFFExaminer:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
731 """Provide high level details about a GFF file to refine parsing.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
732
6e589f267c14 Uploaded
devteam
parents:
diff changeset
733 GFF is a spec and is provided by many different centers. Real life files
6e589f267c14 Uploaded
devteam
parents:
diff changeset
734 will present the same information in slightly different ways. Becoming
6e589f267c14 Uploaded
devteam
parents:
diff changeset
735 familiar with the file you are dealing with is the best way to extract the
6e589f267c14 Uploaded
devteam
parents:
diff changeset
736 information you need. This class provides high level summary details to
6e589f267c14 Uploaded
devteam
parents:
diff changeset
737 help in learning.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
738 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
739 def __init__(self):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
740 self._filter_info = dict(gff_id = [0], gff_source_type = [1, 2],
6e589f267c14 Uploaded
devteam
parents:
diff changeset
741 gff_source = [1], gff_type = [2])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
742
6e589f267c14 Uploaded
devteam
parents:
diff changeset
743 def _get_local_params(self, limit_info=None):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
744 class _LocalParams:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
745 def __init__(self):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
746 self.jsonify = False
6e589f267c14 Uploaded
devteam
parents:
diff changeset
747 params = _LocalParams()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
748 params.limit_info = limit_info
6e589f267c14 Uploaded
devteam
parents:
diff changeset
749 params.filter_info = self._filter_info
6e589f267c14 Uploaded
devteam
parents:
diff changeset
750 return params
6e589f267c14 Uploaded
devteam
parents:
diff changeset
751
6e589f267c14 Uploaded
devteam
parents:
diff changeset
752 @_file_or_handle
6e589f267c14 Uploaded
devteam
parents:
diff changeset
753 def available_limits(self, gff_handle):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
754 """Return dictionary information on possible limits for this file.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
755
6e589f267c14 Uploaded
devteam
parents:
diff changeset
756 This returns a nested dictionary with the following structure:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
757
6e589f267c14 Uploaded
devteam
parents:
diff changeset
758 keys -- names of items to filter by
6e589f267c14 Uploaded
devteam
parents:
diff changeset
759 values -- dictionary with:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
760 keys -- filter choice
6e589f267c14 Uploaded
devteam
parents:
diff changeset
761 value -- counts of that filter in this file
6e589f267c14 Uploaded
devteam
parents:
diff changeset
762
6e589f267c14 Uploaded
devteam
parents:
diff changeset
763 Not a parallelized map-reduce implementation.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
764 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
765 cur_limits = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
766 for filter_key in self._filter_info.keys():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
767 cur_limits[filter_key] = collections.defaultdict(int)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
768 for line in gff_handle:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
769 # when we hit FASTA sequences, we are done with annotations
6e589f267c14 Uploaded
devteam
parents:
diff changeset
770 if line.startswith("##FASTA"):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
771 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
772 # ignore empty and comment lines
6e589f267c14 Uploaded
devteam
parents:
diff changeset
773 if line.strip() and line.strip()[0] != "#":
6e589f267c14 Uploaded
devteam
parents:
diff changeset
774 parts = [p.strip() for p in line.split('\t')]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
775 assert len(parts) == 9, line
6e589f267c14 Uploaded
devteam
parents:
diff changeset
776 for filter_key, cur_indexes in self._filter_info.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
777 cur_id = tuple([parts[i] for i in cur_indexes])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
778 cur_limits[filter_key][cur_id] += 1
6e589f267c14 Uploaded
devteam
parents:
diff changeset
779 # get rid of the default dicts
6e589f267c14 Uploaded
devteam
parents:
diff changeset
780 final_dict = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
781 for key, value_dict in cur_limits.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
782 if len(key) == 1:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
783 key = key[0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
784 final_dict[key] = dict(value_dict)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
785 gff_handle.close()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
786 return final_dict
6e589f267c14 Uploaded
devteam
parents:
diff changeset
787
6e589f267c14 Uploaded
devteam
parents:
diff changeset
788 @_file_or_handle
6e589f267c14 Uploaded
devteam
parents:
diff changeset
789 def parent_child_map(self, gff_handle):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
790 """Provide a mapping of parent to child relationships in the file.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
791
6e589f267c14 Uploaded
devteam
parents:
diff changeset
792 Returns a dictionary of parent child relationships:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
793
6e589f267c14 Uploaded
devteam
parents:
diff changeset
794 keys -- tuple of (source, type) for each parent
6e589f267c14 Uploaded
devteam
parents:
diff changeset
795 values -- tuple of (source, type) as children of that parent
6e589f267c14 Uploaded
devteam
parents:
diff changeset
796
6e589f267c14 Uploaded
devteam
parents:
diff changeset
797 Not a parallelized map-reduce implementation.
6e589f267c14 Uploaded
devteam
parents:
diff changeset
798 """
6e589f267c14 Uploaded
devteam
parents:
diff changeset
799 # collect all of the parent and child types mapped to IDs
6e589f267c14 Uploaded
devteam
parents:
diff changeset
800 parent_sts = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
801 child_sts = collections.defaultdict(list)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
802 for line in gff_handle:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
803 # when we hit FASTA sequences, we are done with annotations
6e589f267c14 Uploaded
devteam
parents:
diff changeset
804 if line.startswith("##FASTA"):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
805 break
6e589f267c14 Uploaded
devteam
parents:
diff changeset
806 if line.strip():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
807 line_type, line_info = _gff_line_map(line,
6e589f267c14 Uploaded
devteam
parents:
diff changeset
808 self._get_local_params())[0]
6e589f267c14 Uploaded
devteam
parents:
diff changeset
809 if (line_type == 'parent' or (line_type == 'child' and
6e589f267c14 Uploaded
devteam
parents:
diff changeset
810 line_info['id'])):
6e589f267c14 Uploaded
devteam
parents:
diff changeset
811 parent_sts[line_info['id']] = (
6e589f267c14 Uploaded
devteam
parents:
diff changeset
812 line_info['quals']['source'][0], line_info['type'])
6e589f267c14 Uploaded
devteam
parents:
diff changeset
813 if line_type == 'child':
6e589f267c14 Uploaded
devteam
parents:
diff changeset
814 for parent_id in line_info['quals']['Parent']:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
815 child_sts[parent_id].append((
6e589f267c14 Uploaded
devteam
parents:
diff changeset
816 line_info['quals']['source'][0], line_info['type']))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
817 #print parent_sts, child_sts
6e589f267c14 Uploaded
devteam
parents:
diff changeset
818 # generate a dictionary of the unique final type relationships
6e589f267c14 Uploaded
devteam
parents:
diff changeset
819 pc_map = collections.defaultdict(list)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
820 for parent_id, parent_type in parent_sts.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
821 for child_type in child_sts[parent_id]:
6e589f267c14 Uploaded
devteam
parents:
diff changeset
822 pc_map[parent_type].append(child_type)
6e589f267c14 Uploaded
devteam
parents:
diff changeset
823 pc_final_map = dict()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
824 for ptype, ctypes in pc_map.items():
6e589f267c14 Uploaded
devteam
parents:
diff changeset
825 unique_ctypes = list(set(ctypes))
6e589f267c14 Uploaded
devteam
parents:
diff changeset
826 unique_ctypes.sort()
6e589f267c14 Uploaded
devteam
parents:
diff changeset
827 pc_final_map[ptype] = unique_ctypes
6e589f267c14 Uploaded
devteam
parents:
diff changeset
828 return pc_final_map