pyham 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyham/TreeProfile.py +408 -0
- pyham/__init__.py +20 -0
- pyham/_version.py +24 -0
- pyham/abstractgene.py +726 -0
- pyham/genome.py +148 -0
- pyham/ham.py +984 -0
- pyham/id_formats.py +137 -0
- pyham/iham.py +364 -0
- pyham/mapper.py +414 -0
- pyham/parsers.py +959 -0
- pyham/taxonomy.py +349 -0
- pyham/utils.py +31 -0
- pyham/validation.py +914 -0
- pyham-2.0.0.dist-info/METADATA +200 -0
- pyham-2.0.0.dist-info/RECORD +19 -0
- pyham-2.0.0.dist-info/WHEEL +5 -0
- pyham-2.0.0.dist-info/entry_points.txt +2 -0
- pyham-2.0.0.dist-info/licenses/LICENSE.txt +19 -0
- pyham-2.0.0.dist-info/top_level.txt +1 -0
pyham/ham.py
ADDED
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
import gzip
|
|
2
|
+
import re
|
|
3
|
+
from xml.etree.ElementTree import XMLParser
|
|
4
|
+
from . import taxonomy as tax
|
|
5
|
+
from .genome import Genome,AncestralGenome, ExtantGenome
|
|
6
|
+
from . import parsers
|
|
7
|
+
from . import mapper
|
|
8
|
+
from . import abstractgene
|
|
9
|
+
from . import id_formats
|
|
10
|
+
from .TreeProfile import TreeProfile
|
|
11
|
+
import logging
|
|
12
|
+
import copy
|
|
13
|
+
import io
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ParserFilter(object):
|
|
18
|
+
"""
|
|
19
|
+
Object containing a list of queries (hogs/genes ids) that will be used by the FilterOrthoXMLParser to collect
|
|
20
|
+
in the orthoxml file the required information for the OrthoXMLParser to only parse data related to this sub-dataset
|
|
21
|
+
of interest.
|
|
22
|
+
|
|
23
|
+
The ParserFilter first collect top level HOG ids, unique or external genes ids that are related to the hogs of
|
|
24
|
+
interest (FilterOrthoXMLParser queries) then run the FilterOrthoXMLParser to built the list of all genes and hogs
|
|
25
|
+
(OrthoXMLParser queries) required to be able to work on this subset.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
| HOGId_filter (:obj:`set`): :obj:`set` of HOG ids used by the FilterOrthoXMLParser.
|
|
29
|
+
| GeneExtId_filter (:obj:`set`): :obj:`set` of external genes ids used by the FilterOrthoXMLParser.
|
|
30
|
+
| GeneIntId_filter (:obj:`set`): :obj:`set` of unique genes ids used by the FilterOrthoXMLParser.
|
|
31
|
+
|
|
32
|
+
| geneUniqueId (:obj:`set`): :obj:`set` of all required unique gene ids build by the FilterOrthoXMLParser.
|
|
33
|
+
| hogsId (:obj:`set`): :obj:`set` of all required top level hog ids build by the FilterOrthoXMLParser.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
|
|
38
|
+
# Information used to select a subdataset of interest during the applyFilter call.
|
|
39
|
+
self.HOGId_filter = set()
|
|
40
|
+
self.GeneExtId_filter = set()
|
|
41
|
+
self.GeneIntId_filter = set()
|
|
42
|
+
|
|
43
|
+
# Information created during buildFilter call that is required by the main OrthoXMLParser for the HOGs
|
|
44
|
+
# construction during Ham instantiation.
|
|
45
|
+
self.geneUniqueId = None # [geneUniqueIds]
|
|
46
|
+
self.hogsId = None # [hogIds]
|
|
47
|
+
|
|
48
|
+
def add_hogs_via_hogId(self, list_id):
|
|
49
|
+
"""
|
|
50
|
+
:param list_id:
|
|
51
|
+
"""
|
|
52
|
+
self.HOGId_filter = self.HOGId_filter | set(map(lambda x: str(x), list_id))
|
|
53
|
+
|
|
54
|
+
def add_hogs_via_GeneExtId(self, list_id):
|
|
55
|
+
"""
|
|
56
|
+
:param list_id:
|
|
57
|
+
"""
|
|
58
|
+
self.GeneExtId_filter = self.GeneExtId_filter | set(map(lambda x: str(x), list_id))
|
|
59
|
+
|
|
60
|
+
def add_hogs_via_GeneIntId(self, list_id):
|
|
61
|
+
"""
|
|
62
|
+
:param list_id:
|
|
63
|
+
"""
|
|
64
|
+
self.GeneIntId_filter = self.GeneIntId_filter | set(map(lambda x: str(x), list_id))
|
|
65
|
+
|
|
66
|
+
def buildFilter(self, file_object, type_hog_file="orthoxml"):
|
|
67
|
+
""" This function will use the FilterOrthoXMLParser with the ``*_filter`` queries to build geneUniqueId and
|
|
68
|
+
hogsId.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
| hog_file (:obj:`str`): Path to the file that contained the HOGs information.
|
|
72
|
+
| type_hog_file (:obj:`str`): File type of the hog_file. Can be "orthoxml or "hdf5". Defaults to "orthoxml".
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
if type_hog_file == "orthoxml":
|
|
76
|
+
self.geneUniqueId, self.hogsId = self._filter_hogs_and_genes(file_object)
|
|
77
|
+
elif type_hog_file == "hdf5":
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
raise TypeError("Invalid type of hog file.")
|
|
82
|
+
|
|
83
|
+
def _filter_hogs_and_genes(self, file_object):
|
|
84
|
+
|
|
85
|
+
""" This function collect from an orthoxml file all data that is required to build Ham object based this filter
|
|
86
|
+
object.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
| file_object (:obj:`FileObject`): File Object of the orthoxml to parse.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
| :obj:`set` of gene unique ids, :obj:`set` of top level hog id.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
factory_filter = parsers.FilterOrthoXMLParser(self)
|
|
97
|
+
parser_filter = XMLParser(target=factory_filter)
|
|
98
|
+
|
|
99
|
+
for line in file_object:
|
|
100
|
+
parser_filter.feed(line)
|
|
101
|
+
|
|
102
|
+
return set(factory_filter.geneUniqueId), set(factory_filter.hogsId)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Ham(object):
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
| tree_file (:obj:`str`): Path to the file that contained the taxonomy information.
|
|
110
|
+
| hog_file (:obj:`str`): Path to the file that contained the HOGs information.
|
|
111
|
+
| hog_file_type (:obj:`str`): File type of the hog_file. Can be "orthoxml or "hdf5". Defaults to "orthoxml".
|
|
112
|
+
| top_level_hogs (:obj:`dict`): Dictionary that map hog unique id with its list of related :obj:`pyham.abstractgene.HOG`.
|
|
113
|
+
| extant_gene_map (:obj:`dict`): Dictionary that map gene unique id with its list of related :obj:`pyham.abstractgene.Gene`.
|
|
114
|
+
| external_id_mapper (:obj:`dict`): Dictionary that map a gene external id with its list of related :obj:`pyham.abstractgene.HOG` or :obj:`pyham.abstractGene.gene`.
|
|
115
|
+
| HOGMaps (:obj:`dict`): Dictionary that map a :obj:`frozenset` of a pair of genomes to its :obj:`pyham.mapper.HOGsMap`.
|
|
116
|
+
| filter_obj (:obj:`pyham.pyham.ParserFilter`): :obj:`ParserFilter` used during the instanciation of Ham. Defaults to None.
|
|
117
|
+
| taxonomy: (:obj:`pyham.mapper.Taxonomy`): :obj:`pyham.pyham.Taxonomy` build and used by :obj:`pyham.pyham.Ham` instance.
|
|
118
|
+
| orthoXML_as_string (:obj:`Bool`) If set to true, hog_file is a string of an orthoxml file. Defaults to False.
|
|
119
|
+
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, tree_file=None, hog_file=None, type_hog_file="orthoxml", filter_object=None, use_internal_name=False,\
|
|
123
|
+
orthoXML_as_string=False, tree_format='newick_string', phyloxml_internal_name_tag='taxonomy_scientific_name', \
|
|
124
|
+
phyloxml_leaf_name_tag='taxonomy_scientific_name', use_data_from=None, query_database=None,
|
|
125
|
+
species_resolve_mode=None, with_parser_progress=False, fail_fast=False, id_schema='auto'):
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
| tree_file (:obj:`str`): Path to the file that contained the taxonomy information.
|
|
130
|
+
| hog_file (:obj:`str`): Path to the file that contained the HOGs information.
|
|
131
|
+
| type_hog_file (:obj:`str`, optional): File type of the hog_file. Can be "orthoxml or "hdf5". Defaults to "orthoxml".
|
|
132
|
+
| filter_object (:obj:`pyham.pyham.ParserFilter`, optional): :obj:`pyham.pyham.ParserFilter` used during the instantiation of pyham.pyham.Ham. Defaults to None.
|
|
133
|
+
| use_internal_name (:obj:`Boolean`, optional): Set to decide to use or not the internal naming of the given taxonomy. This should be set to False when support values are provided in the newick. Defaults to False.
|
|
134
|
+
| tree_format (:obj:`str`): type of inputted tree file. Defaults to newick_string. Can be 'newick', 'phyloxml, 'newick_string'.
|
|
135
|
+
| phyloxml_leaf_name_tag (:obj:`str`) tag to use in the phyloxml to name the extant species (leaves). Defaults tag is taxonomy.scientific_name to populate species names.
|
|
136
|
+
available options: 'clade_name', 'taxonomy_scientific_name', 'taxonomy_code'. Beware than missing species names will stop pyham working.
|
|
137
|
+
| phyloxml_internal_name_tag (:obj:`str`) tag to use in the phyloxml to name the ancestral species (internal nodes). Defaults will use taxonomy.scientific_name to populate species names.
|
|
138
|
+
available options: 'clade_name', 'taxonomy_scientific_name', 'taxonomy_code'. Beware than missing species names will stop pyham working.
|
|
139
|
+
| use_data_from (:obj:`str`) if specified, use data from a remote databaseto populate pyHam. Defaults to None. Options: 'oma'.
|
|
140
|
+
| query_database (:obj:`str`) if use_data_from is specified, use this as a query to fetch the orthoxml and \
|
|
141
|
+
tree information for the related query hog (gene family). For 'oma', this correspond to the oma gene id (e.g. 'HUMAN12' or 'CHIMP1435').
|
|
142
|
+
| with_parser_progress (:bool:, optional) whether to show progress bar when parsing the XML file.
|
|
143
|
+
In a terminal, log messages emitted during parsing can garble the bar; to interleave them cleanly,
|
|
144
|
+
wrap the call in ``tqdm.contrib.logging.logging_redirect_tqdm()``.
|
|
145
|
+
| fail_fast (:obj:`Boolean`, optional) if a HOG's claimed taxonomic level (TaxRange/taxid) conflicts
|
|
146
|
+
with the given species tree, raise :obj:`pyham.abstractgene.TaxonomicConflictError` immediately at
|
|
147
|
+
the first conflict found. If False (default), keep parsing and collect every conflict found across
|
|
148
|
+
the whole file, raising a single combined error (with all conflicts available via its `conflicts`
|
|
149
|
+
attribute) once parsing completes.
|
|
150
|
+
| id_schema (:obj:`str`, optional) the HOG id scheme used by the orthoxml file, one of 'LOFT',
|
|
151
|
+
'LOFT_TAXID', 'GENERIC' (see :obj:`pyham.id_formats`), used to synthesize ids for HOG levels
|
|
152
|
+
the species tree implies but the file doesn't explicitly encode. Defaults to 'auto', which
|
|
153
|
+
samples the first few families of the file to detect the scheme automatically. The resolved
|
|
154
|
+
value is available afterwards as `self.id_schema`.
|
|
155
|
+
"""
|
|
156
|
+
self.with_parser_progress = with_parser_progress
|
|
157
|
+
self.fail_fast = fail_fast
|
|
158
|
+
|
|
159
|
+
if id_schema != 'auto' and id_schema not in id_formats.SCHEMES:
|
|
160
|
+
raise TypeError("{} is not a valid option for id_schema. Available options: 'auto', {}."
|
|
161
|
+
.format(id_schema, ', '.join(sorted(id_formats.SCHEMES))))
|
|
162
|
+
self._requested_id_schema = id_schema
|
|
163
|
+
self.id_schema = id_schema if id_schema != 'auto' else id_formats.GENERIC
|
|
164
|
+
|
|
165
|
+
if use_data_from is not None:
|
|
166
|
+
if query_database is None:
|
|
167
|
+
raise TypeError("query_database argument can't be empty.")
|
|
168
|
+
|
|
169
|
+
if use_data_from == 'oma':
|
|
170
|
+
|
|
171
|
+
import requests
|
|
172
|
+
protein_url = "https://omabrowser.org/api/protein/{}/".format(query_database)
|
|
173
|
+
res = requests.get(protein_url)
|
|
174
|
+
if not res.ok:
|
|
175
|
+
res.raise_for_status()
|
|
176
|
+
gene = res.json()
|
|
177
|
+
if not gene['is_main_isoform']:
|
|
178
|
+
res = requests.get(gene['isoforms'])
|
|
179
|
+
res.raise_for_status()
|
|
180
|
+
isoforms = res.json()
|
|
181
|
+
for gene in isoforms:
|
|
182
|
+
if gene['is_main_isoform']:
|
|
183
|
+
break
|
|
184
|
+
else:
|
|
185
|
+
raise ValueError("No main isoform found for query gene: {}".format(query_database))
|
|
186
|
+
res = requests.get(gene['entry_url'])
|
|
187
|
+
res.raise_for_status()
|
|
188
|
+
gene = res.json()
|
|
189
|
+
top_level = gene['hog_levels'][-1]
|
|
190
|
+
|
|
191
|
+
rep_tax = requests.get("https://omabrowser.org/api/taxonomy/{}/".format(top_level),
|
|
192
|
+
params={"type": "phyloxml"},
|
|
193
|
+
headers={"Accept": "application/xml"})
|
|
194
|
+
if not rep_tax.ok:
|
|
195
|
+
rep_tax.raise_for_status()
|
|
196
|
+
|
|
197
|
+
self.tree_file = 'taxonomy_from_oma_open_at_{}.phyloxml'.format(top_level)
|
|
198
|
+
with open(self.tree_file, 'w') as f:
|
|
199
|
+
f.write(rep_tax.text)
|
|
200
|
+
|
|
201
|
+
# Get the phyloxml data
|
|
202
|
+
oma_url = 'https://omabrowser.org/oma/hog/{}/orthoxml/'.format(gene['oma_hog_id'])
|
|
203
|
+
self.hog_file = requests.get(oma_url).text
|
|
204
|
+
self.hog_file_type = type_hog_file
|
|
205
|
+
|
|
206
|
+
self.orthoXML_as_string = True
|
|
207
|
+
use_internal_name = True
|
|
208
|
+
tree_format = 'phyloxml'
|
|
209
|
+
|
|
210
|
+
elif use_data_from == 'ensembl':
|
|
211
|
+
raise NotImplementedError("This function is not yet implemented.")
|
|
212
|
+
|
|
213
|
+
elif use_data_from not in ['oma', 'ensembl']:
|
|
214
|
+
raise TypeError("{} is not a valid option for use_data_from. Available options: 'oma', 'ensembl'.".format(use_data_from))
|
|
215
|
+
|
|
216
|
+
elif tree_file==None and hog_file==None :
|
|
217
|
+
raise TypeError("Arguments tree_file:{} or hog_file:{} should not be empty.".format(tree_file, hog_file ))
|
|
218
|
+
else:
|
|
219
|
+
if tree_file == None and hog_file == None:
|
|
220
|
+
raise TypeError("Arguments tree_file:{} or hog_file:{} should not be empty.".format(tree_file, hog_file))
|
|
221
|
+
|
|
222
|
+
# HOGs file
|
|
223
|
+
self.hog_file = hog_file
|
|
224
|
+
self.tree_file = tree_file
|
|
225
|
+
self.hog_file_type = type_hog_file
|
|
226
|
+
self.orthoXML_as_string = orthoXML_as_string
|
|
227
|
+
|
|
228
|
+
if self.orthoXML_as_string == True:
|
|
229
|
+
self.hog_file = self.hog_file.encode()
|
|
230
|
+
self.hog_file = self.hog_file.decode('utf-8')
|
|
231
|
+
|
|
232
|
+
# Filtering
|
|
233
|
+
if isinstance(filter_object, ParserFilter) or filter_object is None:
|
|
234
|
+
self.filter_obj = filter_object
|
|
235
|
+
else:
|
|
236
|
+
raise TypeError("filter_obj should be '{}', got {}"
|
|
237
|
+
.format(ParserFilter.__name__,
|
|
238
|
+
type(filter_object).__name__))
|
|
239
|
+
|
|
240
|
+
# Taxonomy
|
|
241
|
+
|
|
242
|
+
accepted_tag_phyloxml = ['clade_name', 'taxonomy_scientific_name', 'taxonomy_code']
|
|
243
|
+
|
|
244
|
+
if phyloxml_leaf_name_tag not in accepted_tag_phyloxml or phyloxml_internal_name_tag not in accepted_tag_phyloxml:
|
|
245
|
+
raise TypeError("{} is an invalid type phyloxml tag name")
|
|
246
|
+
self.species_resolve_mode = species_resolve_mode
|
|
247
|
+
if self.tree_file is not None:
|
|
248
|
+
self.taxonomy = tax.Taxonomy(self.tree_file, tree_format=tree_format, use_internal_name=use_internal_name, phyloxml_leaf_name_tag=phyloxml_leaf_name_tag, phyloxml_internal_name_tag=phyloxml_internal_name_tag)
|
|
249
|
+
else:
|
|
250
|
+
self.taxonomy = None
|
|
251
|
+
logger.info('Build taxonomy: completed.')
|
|
252
|
+
|
|
253
|
+
# Misc. information
|
|
254
|
+
self.top_level_hogs = None
|
|
255
|
+
self.extant_gene_map = None
|
|
256
|
+
self.external_id_mapper = None
|
|
257
|
+
self.HOGMaps = {}
|
|
258
|
+
|
|
259
|
+
# Parsing of data
|
|
260
|
+
if self.hog_file_type == "orthoxml":
|
|
261
|
+
|
|
262
|
+
# If filter_object specified, pyham parse a first time to collect required information
|
|
263
|
+
if self.filter_obj is not None:
|
|
264
|
+
with self._open_hog_file() as orthoxml_file:
|
|
265
|
+
self.filter_obj.buildFilter(orthoxml_file, self.hog_file_type)
|
|
266
|
+
|
|
267
|
+
logger.info('Filtering Indexing of Orthoxml done: {} top level hogs and {} extant genes will be extract.'.format(
|
|
268
|
+
len(self.filter_obj.hogsId),
|
|
269
|
+
len(self.filter_obj.geneUniqueId)))
|
|
270
|
+
|
|
271
|
+
# If id_schema='auto' (default), sample the first few families to detect the HOG id scheme
|
|
272
|
+
if self._requested_id_schema == 'auto':
|
|
273
|
+
with self._open_hog_file() as orthoxml_file:
|
|
274
|
+
self.id_schema = parsers.sniff_id_schema(orthoxml_file)
|
|
275
|
+
logger.info('Auto-detected HOG id scheme: {}'.format(self.id_schema))
|
|
276
|
+
|
|
277
|
+
with self._open_hog_file() as orthoxml_file:
|
|
278
|
+
# This is the actual parser to build HOG/Gene and related Genomes.
|
|
279
|
+
self.top_level_hogs, self.extant_gene_map, self.external_id_mapper = self._build_hogs_and_genes(orthoxml_file, filter_object=self.filter_obj)
|
|
280
|
+
|
|
281
|
+
logger.info('Parse Orthoxml: {} top level hogs and {} extant genes extract.'.format(len(self.top_level_hogs),len(self.extant_gene_map)))
|
|
282
|
+
|
|
283
|
+
elif self.hog_file_type == "hdf5":
|
|
284
|
+
# Looping through all orthoXML within the hdf5
|
|
285
|
+
# for each run self.build_...
|
|
286
|
+
# update self.top_level_hogs and self.extant_gene_map for each
|
|
287
|
+
pass
|
|
288
|
+
|
|
289
|
+
else:
|
|
290
|
+
raise TypeError("Invalid type of hog file")
|
|
291
|
+
|
|
292
|
+
logger.info(
|
|
293
|
+
'Set up Ham analysis: ready to go with {} hogs founded within {} species.'.format(
|
|
294
|
+
len(self.top_level_hogs), len(self.taxonomy.leaves)))
|
|
295
|
+
|
|
296
|
+
# ... TOOLS ... #
|
|
297
|
+
|
|
298
|
+
def compare_genomes_vertically(self, genome1, genome2):
|
|
299
|
+
|
|
300
|
+
"""
|
|
301
|
+
Function to compute a :obj:`MapVertical` based on the 2 given genomes. The genome order doesn't matter.
|
|
302
|
+
|
|
303
|
+
Attributes:
|
|
304
|
+
| genome1 (:obj:`pyham.genome.Genome`): First :obj:`pyham.genome.Genome` to compare.
|
|
305
|
+
| genome2 (:obj:`pyham.genome.Genome`): Second :obj:`pyham.genome.Genome` to compare.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
| :obj:`pyham.mapper.MapVertical`.
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
| TypeError: if genome1 and genome2 are not :obj:`pyham.genome.Genome`.
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
if not isinstance(genome1, Genome):
|
|
315
|
+
raise TypeError("expect subclass obj of '{}', got {}"
|
|
316
|
+
.format(Genome.__name__,
|
|
317
|
+
type(genome1).__name__))
|
|
318
|
+
|
|
319
|
+
if not isinstance(genome2, Genome):
|
|
320
|
+
raise TypeError("expect subclass obj of '{}', got {}"
|
|
321
|
+
.format(Genome.__name__,
|
|
322
|
+
type(genome2).__name__))
|
|
323
|
+
|
|
324
|
+
vertical_map = mapper.MapVertical(self)
|
|
325
|
+
vertical_map.add_map(self._get_HOGMap({genome1, genome2}))
|
|
326
|
+
|
|
327
|
+
return vertical_map
|
|
328
|
+
|
|
329
|
+
def compare_genomes_lateral(self, genome1, genome2):
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
Function to compute a :obj:`pyham.mapper.MapLateral` based on the 2 given genomes. The genome order doesn't matter.
|
|
333
|
+
|
|
334
|
+
Attributes:
|
|
335
|
+
| genome1 (:obj:`pyham.genome.Genome`): First :obj:`pyham.genome.Genome` to compare.
|
|
336
|
+
| genome2 (:obj:`pyham.genome.Genome`): Second :obj:`pyham.genome.Genome` to compare.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
| :obj:`pyham.mapper.MapLateral`.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
| TypeError: if genome1 and genome2 are not :obj:`pyham.genome.Genome`.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
if not isinstance(genome1, Genome):
|
|
346
|
+
raise TypeError("expect subclass obj of '{}', got {}"
|
|
347
|
+
.format(Genome.__name__,
|
|
348
|
+
type(genome1).__name__))
|
|
349
|
+
|
|
350
|
+
if not isinstance(genome2, Genome):
|
|
351
|
+
raise TypeError("expect subclass obj of '{}', got {}"
|
|
352
|
+
.format(Genome.__name__,
|
|
353
|
+
type(genome2).__name__))
|
|
354
|
+
|
|
355
|
+
lateral_map = mapper.MapLateral(self)
|
|
356
|
+
anc, desc = self._get_ancestor_and_descendant(copy.copy({genome1, genome2}))
|
|
357
|
+
for g in desc:
|
|
358
|
+
hogmap = mapper.HOGsMap(self, g, anc)
|
|
359
|
+
lateral_map.add_map(hogmap)
|
|
360
|
+
|
|
361
|
+
return lateral_map
|
|
362
|
+
|
|
363
|
+
def create_iHam(self, hog, outfile=None): #todo Make this compatible with phyloxml input
|
|
364
|
+
|
|
365
|
+
"""
|
|
366
|
+
Function to compute a :obj:`pyham.IHAM`.
|
|
367
|
+
|
|
368
|
+
If an outfile is specified, export the :obj:`pyham.IHAM` as html file.
|
|
369
|
+
|
|
370
|
+
Attributes:
|
|
371
|
+
| hog (:obj:`pyham.abstractgene.HOG`): HOG use as template for the :obj:`pyham.IHAM`.
|
|
372
|
+
| outfile (:obj:`str`, optional): Path to the IHAM html file.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
| :obj:`pyham.IHAM`
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
newick_tree = self.taxonomy.get_newick_from_tree(hog.genome.taxon)
|
|
379
|
+
|
|
380
|
+
vis = hog.get_hog_vis(newick_tree)
|
|
381
|
+
|
|
382
|
+
if outfile is not None:
|
|
383
|
+
with open(outfile, 'w') as fh:
|
|
384
|
+
fh.write(vis.HTML)
|
|
385
|
+
|
|
386
|
+
return vis
|
|
387
|
+
|
|
388
|
+
def create_tree_profile(self, hog=None, outfile=None, as_html=True, export_with_histogram=True):
|
|
389
|
+
|
|
390
|
+
"""
|
|
391
|
+
Function to compute a :obj:`pyham.TreeProfile`.
|
|
392
|
+
|
|
393
|
+
If no hog are given the tree profile will be created for the whole Ham setup (all internal nodes with all HOGs).
|
|
394
|
+
Otherwise, the tree profile is build for the specific hog given.
|
|
395
|
+
|
|
396
|
+
If an outfile is specified, export the create_tree_profile as image into file.
|
|
397
|
+
|
|
398
|
+
Attributes:
|
|
399
|
+
| hog (:obj:`pyham.abstractgene.HOG`, optional): HOG use as template for the create_tree_profile.
|
|
400
|
+
| outfile (:obj:`str`, optional): Path to the create_tree_profile output image file. valid extensions are .SVG, .PDF, .PNG.
|
|
401
|
+
| export_with_histogram (:obj:`Bool`, optional): If True, export image with histogram at each internal node otherwise
|
|
402
|
+
display internal node information as text.
|
|
403
|
+
| as_html (:obj:`Bool`, optional): If true, the tree profile will be exported as html file containing an
|
|
404
|
+
interactive viewer to handle tree profile information.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
| :obj:`pyham.TreeProfile`
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
tp = TreeProfile(self, hog=hog)
|
|
411
|
+
|
|
412
|
+
if outfile:
|
|
413
|
+
|
|
414
|
+
if as_html:
|
|
415
|
+
tp.export_as_html(outfile)
|
|
416
|
+
|
|
417
|
+
else:
|
|
418
|
+
tp.export(outfile, display_internal_histogram=export_with_histogram)
|
|
419
|
+
|
|
420
|
+
return tp
|
|
421
|
+
|
|
422
|
+
def get_ascii_taxonomy(self):
|
|
423
|
+
return self.taxonomy.tree.get_ascii()
|
|
424
|
+
|
|
425
|
+
# ... QUERY METHODS ... #
|
|
426
|
+
|
|
427
|
+
# ___ Gene ___ #
|
|
428
|
+
|
|
429
|
+
def get_gene_by_id(self, gene_unique_id):
|
|
430
|
+
|
|
431
|
+
""" Get the :obj:`pyham.abstractgene.Gene` that match the query unique gene Id.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
| gene_unique_id (:obj:`str` or :obj:`int`): Unique gene Id.
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
:obj:`pyham.abstractgene.Gene`
|
|
438
|
+
|
|
439
|
+
Raises:
|
|
440
|
+
KeyError is not `pyham.abstractgene.Gene` match the id.
|
|
441
|
+
|
|
442
|
+
"""
|
|
443
|
+
gene_unique_id = str(gene_unique_id)
|
|
444
|
+
|
|
445
|
+
if gene_unique_id in self.extant_gene_map.keys():
|
|
446
|
+
return self.extant_gene_map[gene_unique_id]
|
|
447
|
+
|
|
448
|
+
raise KeyError('Id {} cannot match any Gene unique Id.'.format(gene_unique_id))
|
|
449
|
+
|
|
450
|
+
def get_genes_by_external_id(self, external_gene_id):
|
|
451
|
+
|
|
452
|
+
""" Get the list of :obj:`pyham.abstractgene.Gene` that match the query external gene Id.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
external_gene_id (:obj:`str` or :obj:`int`): External gene Id.
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
a list of :obj:`pyham.abstractgene.Gene`
|
|
459
|
+
|
|
460
|
+
Raises:
|
|
461
|
+
KeyError if no `pyham.abstractgene.Gene` match id.
|
|
462
|
+
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
external_gene_id = str(external_gene_id)
|
|
466
|
+
|
|
467
|
+
if external_gene_id in self.external_id_mapper.keys():
|
|
468
|
+
return [self.extant_gene_map[qgene_id] for qgene_id in self.external_id_mapper[external_gene_id]]
|
|
469
|
+
|
|
470
|
+
raise KeyError('Id {} cannot match any Gene external Id.'.format(external_gene_id))
|
|
471
|
+
|
|
472
|
+
def get_list_extant_genes(self):
|
|
473
|
+
|
|
474
|
+
""" Get the list of all :obj:`pyham.abstractgene.Gene`.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
a list of :obj:`pyham.abstractgene.Gene`.
|
|
478
|
+
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
return list(self.extant_gene_map.values())
|
|
482
|
+
|
|
483
|
+
def get_dict_extant_genes(self):
|
|
484
|
+
|
|
485
|
+
""" Get a dictionary that map all unique gene id with their related :obj:`pyham.abstractgene.Gene`.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
a dictionary mapping unique gene Id (:obj:`str`) with :obj:`pyham.abstractgene.Gene`.
|
|
489
|
+
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
return self.extant_gene_map
|
|
493
|
+
|
|
494
|
+
# ___ HOG ___ #
|
|
495
|
+
|
|
496
|
+
def get_hog_by_id(self, hog_id):
|
|
497
|
+
|
|
498
|
+
""" Get the top level :obj:`HOG` that match the hog id query.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
hog_id (:obj:`str` or :obj:`int`): Top level HOG id.
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
:obj:`pyham.abstractgene.HOG`
|
|
505
|
+
|
|
506
|
+
Raises:
|
|
507
|
+
KeyError if id match no `pyham.abstractgene.HOG`.
|
|
508
|
+
|
|
509
|
+
"""
|
|
510
|
+
|
|
511
|
+
hog_id = str(hog_id)
|
|
512
|
+
if hog_id in self.top_level_hogs:
|
|
513
|
+
return self.top_level_hogs[hog_id]
|
|
514
|
+
|
|
515
|
+
m = id_formats.HOG_ID_RE.match(hog_id)
|
|
516
|
+
if not m:
|
|
517
|
+
raise KeyError(f"No HOG with id \"{hog_id}\" can be found.")
|
|
518
|
+
|
|
519
|
+
if m:
|
|
520
|
+
fam = m.group('fam')
|
|
521
|
+
subhog = m.group('subhog')
|
|
522
|
+
taxid = m.group('taxid')
|
|
523
|
+
|
|
524
|
+
if fam in self.top_level_hogs:
|
|
525
|
+
roothog = self.top_level_hogs[fam]
|
|
526
|
+
elif fam[4:] in self.top_level_hogs:
|
|
527
|
+
fam = fam[4:]
|
|
528
|
+
roothog = self.top_level_hogs[fam]
|
|
529
|
+
else:
|
|
530
|
+
raise KeyError(f"No HOG with id \"{hog_id}\" can be found.")
|
|
531
|
+
|
|
532
|
+
if subhog or taxid is not None:
|
|
533
|
+
hog = roothog.find_by_id(hog_id=fam + subhog, taxid=int(taxid) if taxid is not None else None)
|
|
534
|
+
if hog is not None:
|
|
535
|
+
return hog
|
|
536
|
+
else:
|
|
537
|
+
raise KeyError(f"No HOG with id \"{hog_id}\" can be found.")
|
|
538
|
+
return roothog
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
if hog_id in self.top_level_hogs.keys():
|
|
544
|
+
return self.top_level_hogs[hog_id]
|
|
545
|
+
|
|
546
|
+
raise KeyError(' Id {} cannot match any HOG Id.'.format(hog_id))
|
|
547
|
+
|
|
548
|
+
def get_hog_by_gene(self, gene):
|
|
549
|
+
|
|
550
|
+
""" Get the top level :obj:`HOG` that contain the query :obj:`pyham.abstractgene.Gene`. If the :obj:`pyham.abstractgene.Gene` is a singleton it will
|
|
551
|
+
return itself.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
gene (:obj:`pyham.abstractgene.Gene`): :obj:`pyham.abstractgene.Gene` object.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
:obj:`pyham.abstractgene.HOG`
|
|
558
|
+
|
|
559
|
+
Raises:
|
|
560
|
+
KeyError is gene is not a :obj:`pyham.abstractgene.Gene`.
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
if isinstance(gene, abstractgene.Gene):
|
|
565
|
+
return gene.get_top_level_hog()
|
|
566
|
+
|
|
567
|
+
raise KeyError("expect a '{}' as query, got {}".format(abstractgene.Gene, type(gene).__name__))
|
|
568
|
+
|
|
569
|
+
def get_list_top_level_hogs(self):
|
|
570
|
+
|
|
571
|
+
""" Get the list of all the top level :obj:`pyham.abstractgene.HOG`.
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
a list of :obj:`pyham.abstractgene.HOG`.
|
|
575
|
+
|
|
576
|
+
"""
|
|
577
|
+
|
|
578
|
+
return list(self.top_level_hogs.values())
|
|
579
|
+
|
|
580
|
+
def get_dict_top_level_hogs(self):
|
|
581
|
+
|
|
582
|
+
""" Get a dictionary that map all top level hog id with their related :obj:`pyham.abstractgene.HOG`.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
a dictionary mapping hog Id (:obj:`str`) with :obj:`pyham.abstractgene.HOG`.
|
|
586
|
+
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
return self.top_level_hogs
|
|
590
|
+
|
|
591
|
+
# ___ ExtantGenome ___ #
|
|
592
|
+
|
|
593
|
+
def get_list_extant_genomes(self):
|
|
594
|
+
|
|
595
|
+
"""
|
|
596
|
+
Get the list of all :obj:`pyham.genome.ExtantGenome` created during the parsing.
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
a list of :obj:`pyham.genome.ExtantGenome`.
|
|
600
|
+
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
return [leaf.props['genome'] for leaf in self.taxonomy.leaves]
|
|
604
|
+
|
|
605
|
+
def get_extant_genome_by_name(self, name):
|
|
606
|
+
|
|
607
|
+
"""
|
|
608
|
+
Get the :obj:`pyham.genome.ExtantGenome` that match the query name.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
name (:obj:`str`): Name of the :obj:`pyham.genome.ExtantGenome`.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
:obj:`pyham.genome.ExtantGenome` or raise KeyError
|
|
615
|
+
|
|
616
|
+
"""
|
|
617
|
+
|
|
618
|
+
for taxon in self.taxonomy.leaves:
|
|
619
|
+
if taxon.name == name:
|
|
620
|
+
if "genome" in taxon.props:
|
|
621
|
+
return taxon.props['genome']
|
|
622
|
+
|
|
623
|
+
raise KeyError('No extant genomes match the query name: {}'.format(name))
|
|
624
|
+
|
|
625
|
+
# ___ AncestralGenome ___ #
|
|
626
|
+
|
|
627
|
+
def get_list_ancestral_genomes(self):
|
|
628
|
+
|
|
629
|
+
"""
|
|
630
|
+
Get the list of all :obj:`pyham.genome.AncestralGenome` created during the parsing.
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
a list of :obj:`pyham.genome.AncestralGenome`.
|
|
634
|
+
|
|
635
|
+
"""
|
|
636
|
+
return [internal_node.props['genome'] for internal_node in self.taxonomy.internal_nodes]
|
|
637
|
+
|
|
638
|
+
def get_ancestral_genome_by_taxon(self, taxon):
|
|
639
|
+
|
|
640
|
+
"""
|
|
641
|
+
Get the :obj:`pyham.genome.AncestralGenome` corresponding of the query taxon.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
taxon (:obj:`str`): treeNode object of the :obj:`pyham.taxonomy.Taxonomy`.tree object.
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
:obj:`pyham.genome.AncestralGenome` or raise KeyError
|
|
648
|
+
|
|
649
|
+
"""
|
|
650
|
+
|
|
651
|
+
if taxon in self.taxonomy.internal_nodes and "genome" in taxon.props:
|
|
652
|
+
return taxon.props['genome']
|
|
653
|
+
|
|
654
|
+
raise KeyError("Taxon {} doesn't have a genome attached to it.".format(taxon))
|
|
655
|
+
|
|
656
|
+
def get_ancestral_genome_by_name(self, name):
|
|
657
|
+
|
|
658
|
+
"""
|
|
659
|
+
Get the :obj:`pyham.genome.AncestralGenome` corresponding of the query name.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
name (:obj:`str`): Name of the :obj:`pyham.genome.AncestralGenome`.
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
:obj:`pyham.genome.AncestralGenome` or raise KeyError
|
|
666
|
+
|
|
667
|
+
"""
|
|
668
|
+
taxon = self.taxonomy.get_node_by_name(name)
|
|
669
|
+
if taxon.is_leaf:
|
|
670
|
+
raise KeyError(f"query name {name} is an extant genome, not an ancestral one.")
|
|
671
|
+
return self.taxonomy.get_genome_from_taxnode(taxon)
|
|
672
|
+
|
|
673
|
+
def get_ancestral_genome_by_mrca_of_genome_set(self, genome_set):
|
|
674
|
+
|
|
675
|
+
"""
|
|
676
|
+
Get the :obj:`pyham.genome.AncestralGenome` corresponding to the MRCA of query genomes.
|
|
677
|
+
|
|
678
|
+
Args:
|
|
679
|
+
genome_set (:obj:`set`): Set of :obj:`pyham.genome.AncestralGenome`.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
:obj:`pyham.genome.AncestralGenome` or raise KeyError
|
|
683
|
+
|
|
684
|
+
"""
|
|
685
|
+
|
|
686
|
+
if len(genome_set) < 2:
|
|
687
|
+
raise ValueError('Minimum 2 genomes are required, only {} provided.'.format(len(genome_set)))
|
|
688
|
+
|
|
689
|
+
for g in genome_set:
|
|
690
|
+
if not isinstance(g, Genome):
|
|
691
|
+
raise TypeError("expect subclass obj of '{}', got {}"
|
|
692
|
+
.format(Genome.__name__,
|
|
693
|
+
type(g).__name__))
|
|
694
|
+
|
|
695
|
+
genome_nodes = set([geno.taxon for geno in genome_set])
|
|
696
|
+
|
|
697
|
+
mrca_node = self.taxonomy.tree.common_ancestor(*genome_nodes)
|
|
698
|
+
|
|
699
|
+
return self.get_ancestral_genome_by_taxon(mrca_node)
|
|
700
|
+
|
|
701
|
+
# Taxon
|
|
702
|
+
|
|
703
|
+
def get_taxon_by_name(self, name):
|
|
704
|
+
|
|
705
|
+
"""
|
|
706
|
+
Get the :obj:`ete3.TreeNode` object of the :obj:`pyham.taxonomy.Taxonomy`.tree corresponding of the query name.
|
|
707
|
+
|
|
708
|
+
Args:
|
|
709
|
+
name (:obj:`str`): Name of the treeNode.
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
:obj:`ete3.TreeNode` or raise KeyError
|
|
713
|
+
|
|
714
|
+
"""
|
|
715
|
+
|
|
716
|
+
nodes_found = list(self.taxonomy.tree.search_nodes(name=name))
|
|
717
|
+
|
|
718
|
+
if not nodes_found:
|
|
719
|
+
raise KeyError('No node founded for the species name: {}'.format(name))
|
|
720
|
+
elif len(nodes_found) == 1:
|
|
721
|
+
return nodes_found[0]
|
|
722
|
+
else:
|
|
723
|
+
raise KeyError('{} nodes founded for the species name: {}'.format(len(nodes_found), name))
|
|
724
|
+
|
|
725
|
+
# ... PRIVATE METHODS ... #
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def _get_oldest_from_genome_pair(self, g1, g2):
|
|
729
|
+
|
|
730
|
+
"""
|
|
731
|
+
Get the oldest :obj:`Genome` for a pair of :obj:`Genome`.
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
g1 (:obj:`Genome`): First :obj:`Genome`.
|
|
735
|
+
g2 (:obj:`Genome`): Second :obj:`Genome`.
|
|
736
|
+
|
|
737
|
+
Returns:
|
|
738
|
+
:obj:`Genome`
|
|
739
|
+
|
|
740
|
+
"""
|
|
741
|
+
|
|
742
|
+
mrca = self.taxonomy.tree.common_ancestor(g1.taxon,g2.taxon)
|
|
743
|
+
|
|
744
|
+
if g1.taxon == mrca:
|
|
745
|
+
return g1, g2
|
|
746
|
+
elif g2.taxon == mrca:
|
|
747
|
+
return g2, g1
|
|
748
|
+
else:
|
|
749
|
+
raise TypeError("The genomes are not in the same lineage: {}".format({g1, g2}))
|
|
750
|
+
|
|
751
|
+
def _get_ancestor_and_descendant(self, genome_set):
|
|
752
|
+
|
|
753
|
+
"""
|
|
754
|
+
This method fetch from a set of :obj:`Genome`:
|
|
755
|
+
- the oldest :obj:`Genome` from the set (if oldest genome not in set we get their mrca ).
|
|
756
|
+
- the rest of the :obj:`Genome` present in the set.
|
|
757
|
+
|
|
758
|
+
Args:
|
|
759
|
+
genome_set (:obj:`set`): A set of :obj:`Genome`.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
:obj:`Genome`, a set of :obj:`Genome`.
|
|
763
|
+
|
|
764
|
+
"""
|
|
765
|
+
|
|
766
|
+
ancestor = self._get_ancestral_genome_by_mrca_of_genome_set(genome_set)
|
|
767
|
+
genome_set.discard(ancestor)
|
|
768
|
+
return ancestor, genome_set
|
|
769
|
+
|
|
770
|
+
def _get_HOGMap(self, genome_pair_set):
|
|
771
|
+
|
|
772
|
+
"""
|
|
773
|
+
Get the :obj:`HOGMap` between two genomes.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
genome_pair_set (:obj:`set`): A set of 2 :obj:`Genome`.
|
|
777
|
+
|
|
778
|
+
Returns:
|
|
779
|
+
:obj:`HOGMap`
|
|
780
|
+
|
|
781
|
+
"""
|
|
782
|
+
|
|
783
|
+
f = frozenset(genome_pair_set)
|
|
784
|
+
|
|
785
|
+
if f in self.HOGMaps.keys():
|
|
786
|
+
return self.HOGMaps[f]
|
|
787
|
+
else:
|
|
788
|
+
self.HOGMaps[f] = mapper.HOGsMap(self, list(genome_pair_set)[0], list(genome_pair_set)[1])
|
|
789
|
+
return self.HOGMaps[f]
|
|
790
|
+
|
|
791
|
+
def _open_hog_file(self):
|
|
792
|
+
"""Return a context manager yielding a fresh, from-the-start file object for `self.hog_file`
|
|
793
|
+
(a `StringIO` if the orthoxml was given as a string, else a plain/gzip-decompressed file)."""
|
|
794
|
+
if self.orthoXML_as_string:
|
|
795
|
+
return io.StringIO(self.hog_file)
|
|
796
|
+
open_ = gzip.open if str(self.hog_file).endswith('.gz') else open
|
|
797
|
+
return open_(self.hog_file, 'rt')
|
|
798
|
+
|
|
799
|
+
def _build_hogs_and_genes(self, file_object, filter_object):
|
|
800
|
+
|
|
801
|
+
""" This function build from an orthoxml file all data that is required to build this Ham object (using the Ham
|
|
802
|
+
filter object).
|
|
803
|
+
|
|
804
|
+
Args:
|
|
805
|
+
file_object (:obj:`FileObject`): File Object of the orthoxml to parse.
|
|
806
|
+
filter_object (:obj:`ParserFilter`): :obj:`ParserFilter` use by OrthoXMLParser.
|
|
807
|
+
|
|
808
|
+
Returns:
|
|
809
|
+
:obj:`set` of top level :obj:`HOG` , :obj:`dict` of unique id with their :obj:`Gene`, :obj:`dict` of
|
|
810
|
+
external id with their :obj:`Gene`.
|
|
811
|
+
|
|
812
|
+
"""
|
|
813
|
+
|
|
814
|
+
factory = parsers.OrthoXMLParser(self, filterObject=filter_object, with_progress=self.with_parser_progress,
|
|
815
|
+
fail_fast=self.fail_fast, id_schema=self.id_schema)
|
|
816
|
+
parser = XMLParser(target=factory)
|
|
817
|
+
|
|
818
|
+
try:
|
|
819
|
+
for line in file_object:
|
|
820
|
+
parser.feed(line)
|
|
821
|
+
except abstractgene.TaxonomicConflictError:
|
|
822
|
+
raise
|
|
823
|
+
except Exception as e:
|
|
824
|
+
if factory.current_family_id is not None:
|
|
825
|
+
note = ("pyHam: raised while building family {}. Run pyham.validate_input() with the same "
|
|
826
|
+
"inputs for a report covering every family.".format(factory.current_family_id))
|
|
827
|
+
if hasattr(e, "add_note"):
|
|
828
|
+
e.add_note(note)
|
|
829
|
+
else:
|
|
830
|
+
logger.error(note)
|
|
831
|
+
raise
|
|
832
|
+
finally:
|
|
833
|
+
factory.close_progress_bars()
|
|
834
|
+
|
|
835
|
+
if factory.taxonomic_conflicts:
|
|
836
|
+
# fail_fast=True would already have raised inside the parser itself; reaching here means
|
|
837
|
+
# fail_fast=False, so every conflict found across the whole file is reported together.
|
|
838
|
+
raise abstractgene.TaxonomicConflictError(
|
|
839
|
+
parsers.format_taxonomic_conflicts(factory.taxonomic_conflicts),
|
|
840
|
+
factory.taxonomic_conflicts,
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
if self.taxonomy is None:
|
|
844
|
+
self.taxonomy = factory.taxonomy
|
|
845
|
+
|
|
846
|
+
return factory.toplevel_hogs, factory.extant_gene_map, factory.external_id_mapper
|
|
847
|
+
|
|
848
|
+
def _get_ancestral_genome_by_name(self, name):
|
|
849
|
+
|
|
850
|
+
"""
|
|
851
|
+
Get the :obj:`pyham.genome.AncestralGenome` corresponding of the query name, if not founded in the taxonomy.tree.node.genome then created it.
|
|
852
|
+
|
|
853
|
+
Args:
|
|
854
|
+
name (:obj:`str`): Name of the :obj:`pyham.genome.AncestralGenome`.
|
|
855
|
+
|
|
856
|
+
Returns:
|
|
857
|
+
:obj:`pyham.genome.AncestralGenome` or raise KeyError
|
|
858
|
+
|
|
859
|
+
"""
|
|
860
|
+
|
|
861
|
+
nodes_found = list(self.taxonomy.tree.search_nodes(name=name))
|
|
862
|
+
|
|
863
|
+
if len(nodes_found) == 1:
|
|
864
|
+
|
|
865
|
+
node = nodes_found[0]
|
|
866
|
+
|
|
867
|
+
if "genome" in node.props:
|
|
868
|
+
return node.props['genome']
|
|
869
|
+
|
|
870
|
+
else:
|
|
871
|
+
ancestral_genome = AncestralGenome()
|
|
872
|
+
self.taxonomy.add_genome_to_node(node, ancestral_genome)
|
|
873
|
+
|
|
874
|
+
return ancestral_genome
|
|
875
|
+
else:
|
|
876
|
+
raise KeyError('{} node(s) founded for the ancestral genome named {}'.format(len(nodes_found), name))
|
|
877
|
+
|
|
878
|
+
def _get_extant_genome_by_name(self, **kwargs):
|
|
879
|
+
|
|
880
|
+
"""
|
|
881
|
+
Get the :obj:`ExtantGenome` by name, if not founded in the taxonomy.tree.node.genome then created it.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
**kwargs: dictionary of attribute and value required to create the :obj:`ExtantGenome`.
|
|
885
|
+
|
|
886
|
+
Returns:
|
|
887
|
+
:obj:`ExtantGenome`
|
|
888
|
+
|
|
889
|
+
"""
|
|
890
|
+
|
|
891
|
+
nodes_found = list(self.taxonomy.tree.search_nodes(name=kwargs['name']))
|
|
892
|
+
|
|
893
|
+
if len(nodes_found) == 1:
|
|
894
|
+
|
|
895
|
+
node = nodes_found[0]
|
|
896
|
+
if len(node.children) > 0 and self.species_resolve_mode == "OMA":
|
|
897
|
+
cand = []
|
|
898
|
+
for child in node.children:
|
|
899
|
+
if len(child.name) == 5 and re.match(r'[A-Z][A-Z0-9]{4}', child.name) is not None:
|
|
900
|
+
cand.append(child)
|
|
901
|
+
if len(cand) == 1:
|
|
902
|
+
node = cand[0]
|
|
903
|
+
kwargs['name'] = node.name
|
|
904
|
+
if len(node.children) > 0:
|
|
905
|
+
raise TypeError("species name '{}' maps to an ancestral name, not a leaf of the taxonomy"
|
|
906
|
+
.format(kwargs["name"]))
|
|
907
|
+
|
|
908
|
+
if "genome" in node.props:
|
|
909
|
+
return node.props['genome']
|
|
910
|
+
|
|
911
|
+
else:
|
|
912
|
+
extant_genome = ExtantGenome(**kwargs)
|
|
913
|
+
self.taxonomy.add_genome_to_node(node, extant_genome)
|
|
914
|
+
return extant_genome
|
|
915
|
+
else:
|
|
916
|
+
raise KeyError('{} node(s) found for the species name: {}'.format(len(nodes_found), kwargs['name']))
|
|
917
|
+
|
|
918
|
+
def _get_ancestral_genome_by_taxon(self, tax_node):
|
|
919
|
+
|
|
920
|
+
"""
|
|
921
|
+
Get the :obj:`AncestralGenome` corresponding of the query taxon if not founded in the taxonomy.tree
|
|
922
|
+
then created it.
|
|
923
|
+
|
|
924
|
+
Args:
|
|
925
|
+
tax_node : treeNode object of the :obj:`Taxonomy`.tree object.
|
|
926
|
+
|
|
927
|
+
Returns:
|
|
928
|
+
:obj:`AncestralGenome`
|
|
929
|
+
|
|
930
|
+
"""
|
|
931
|
+
|
|
932
|
+
if "genome" in tax_node.props:
|
|
933
|
+
return tax_node.props['genome']
|
|
934
|
+
|
|
935
|
+
else:
|
|
936
|
+
ancestral_genome = AncestralGenome()
|
|
937
|
+
self.taxonomy.add_genome_to_node(tax_node, ancestral_genome)
|
|
938
|
+
|
|
939
|
+
return ancestral_genome
|
|
940
|
+
|
|
941
|
+
def _get_ancestral_genome_by_mrca_of_hog_children_genomes(self, hog):
|
|
942
|
+
|
|
943
|
+
"""
|
|
944
|
+
Get MRCA :obj:`AncestralGenome` of the list of children :obj:`Genome` of the query :obj:`HOG`.
|
|
945
|
+
|
|
946
|
+
Args:
|
|
947
|
+
hog (:obj:`HOG`): query HOG.
|
|
948
|
+
|
|
949
|
+
Returns:
|
|
950
|
+
:obj:`AncestralGenome`
|
|
951
|
+
|
|
952
|
+
"""
|
|
953
|
+
|
|
954
|
+
children_genomes = set([child.genome for child in hog.children ])
|
|
955
|
+
|
|
956
|
+
return self._get_ancestral_genome_by_mrca_of_genome_set(children_genomes)
|
|
957
|
+
|
|
958
|
+
def _get_ancestral_genome_by_mrca_of_genome_set(self, genome_set):
|
|
959
|
+
|
|
960
|
+
"""
|
|
961
|
+
Get the :obj:`AncestralGenome` corresponding to the MRCA of query genomes.
|
|
962
|
+
|
|
963
|
+
Args:
|
|
964
|
+
genome_set (:obj:`set`): Set of :obj:`AncestralGenome`.
|
|
965
|
+
|
|
966
|
+
Returns:
|
|
967
|
+
:obj:`AncestralGenome` or raise KeyError
|
|
968
|
+
|
|
969
|
+
"""
|
|
970
|
+
|
|
971
|
+
if len(genome_set) < 2:
|
|
972
|
+
raise ValueError('Minimum 2 genomes are required, only {} provided.'.format(len(genome_set)))
|
|
973
|
+
|
|
974
|
+
for g in genome_set:
|
|
975
|
+
if not isinstance(g, Genome):
|
|
976
|
+
raise TypeError("expect subclass obj of '{}', got {}"
|
|
977
|
+
.format(Genome.__name__,
|
|
978
|
+
type(g).__name__))
|
|
979
|
+
|
|
980
|
+
genome_nodes = set([gen.taxon for gen in genome_set])
|
|
981
|
+
|
|
982
|
+
mrca_node = self.taxonomy.tree.common_ancestor(*genome_nodes)
|
|
983
|
+
|
|
984
|
+
return self._get_ancestral_genome_by_taxon(mrca_node)
|