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/id_formats.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Detection and synthesis helpers for the HOG id "scheme" used by an OrthoXML file.
|
|
2
|
+
|
|
3
|
+
Three schemes are recognised:
|
|
4
|
+
|
|
5
|
+
- LOFT: after a duplication, paralogous branches get a ``.<num><letter>`` suffix
|
|
6
|
+
appended (e.g. ``1074943`` -> ``1074943.2a`` -> ``1074943.2a.7b``). Non-duplication
|
|
7
|
+
levels keep the same id as they move up/down the tree, so two different taxonomic
|
|
8
|
+
levels can end up sharing an id.
|
|
9
|
+
- LOFT_TAXID: LOFT plus a trailing ``_<taxid>`` tied to the specific taxonomic level,
|
|
10
|
+
which restores per-level uniqueness (e.g. ``HOG:0402997.2b.1a_47424``).
|
|
11
|
+
- GENERIC: no inferable structure; ids are opaque and used as-is.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import string
|
|
16
|
+
|
|
17
|
+
LOFT = "LOFT"
|
|
18
|
+
LOFT_TAXID = "LOFT_TAXID"
|
|
19
|
+
GENERIC = "GENERIC"
|
|
20
|
+
SCHEMES = {LOFT, LOFT_TAXID, GENERIC}
|
|
21
|
+
|
|
22
|
+
# <fam><subhog>[_<taxid>], e.g. "HOG:0402997.2b.1a_47424" or "HOG:0402997" or "3".
|
|
23
|
+
# Kept identical to the grammar Ham.get_hog_by_id used to inline, for backward compatibility.
|
|
24
|
+
HOG_ID_RE = re.compile(r"^(?P<fam>(HOG:[A-Z]?)?(\d+))(?P<subhog>[0-9a-z.]*)(_(?P<taxid>-?\d+))?$")
|
|
25
|
+
|
|
26
|
+
# Strict LOFT dot-chain shape: one or more ".<digits><letters>" groups.
|
|
27
|
+
_LOFT_CHAIN_RE = re.compile(r"^(\.\d+[a-zA-Z]+)+$")
|
|
28
|
+
|
|
29
|
+
_CHAIN_SHAPE_THRESHOLD = 0.8
|
|
30
|
+
_TAXID_SUFFIX_THRESHOLD = 0.5
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def detect_id_scheme(sample_ids):
|
|
34
|
+
"""Classify a batch of raw HOG id strings into one of LOFT / LOFT_TAXID / GENERIC.
|
|
35
|
+
|
|
36
|
+
Ids without a subhog part (e.g. a bare top-level id like "HOG:0402997") are
|
|
37
|
+
ignored: they say nothing about the nested-level convention used by a file.
|
|
38
|
+
"""
|
|
39
|
+
matches = [HOG_ID_RE.match(s) for s in sample_ids]
|
|
40
|
+
matches = [m for m in matches if m]
|
|
41
|
+
|
|
42
|
+
with_subhog = [m for m in matches if m.group('subhog')]
|
|
43
|
+
if not with_subhog:
|
|
44
|
+
return GENERIC
|
|
45
|
+
|
|
46
|
+
chain_shaped = [m for m in with_subhog if _LOFT_CHAIN_RE.match(m.group('subhog'))]
|
|
47
|
+
if len(chain_shaped) / len(with_subhog) < _CHAIN_SHAPE_THRESHOLD:
|
|
48
|
+
return GENERIC
|
|
49
|
+
|
|
50
|
+
with_taxid = [m for m in chain_shaped if m.group('taxid') is not None]
|
|
51
|
+
if len(with_taxid) / len(chain_shaped) > _TAXID_SUFFIX_THRESHOLD:
|
|
52
|
+
return LOFT_TAXID
|
|
53
|
+
return LOFT
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def taxid_for_taxon(taxon):
|
|
57
|
+
"""Best-effort numeric/string id for an ete4 taxonomy node.
|
|
58
|
+
|
|
59
|
+
Newick-derived trees carry no id props at all; phyloxml-derived trees store it
|
|
60
|
+
under 'taxon_id'; orthoxml-embedded taxonomies store it under 'id'. The `_taxid`
|
|
61
|
+
suffix must stay numeric to remain parseable by `HOG_ID_RE`/`Ham.get_hog_by_id`, so
|
|
62
|
+
this returns None (rather than e.g. the node's name) when no numeric id is found.
|
|
63
|
+
"""
|
|
64
|
+
for key in ('id', 'taxon_id'):
|
|
65
|
+
value = taxon.props.get(key)
|
|
66
|
+
if value is None:
|
|
67
|
+
continue
|
|
68
|
+
try:
|
|
69
|
+
return int(value)
|
|
70
|
+
except (TypeError, ValueError):
|
|
71
|
+
continue
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def make_missing_hog_id(scheme, anchor_id, taxon):
|
|
76
|
+
"""Build an id for a HOG synthesized for a taxonomic level absent from the XML.
|
|
77
|
+
|
|
78
|
+
Reuses the LOFT dot-chain of `anchor_id` (a neighboring real HOG's id) and, under
|
|
79
|
+
LOFT_TAXID, swaps in the taxid of `taxon` so each synthesized level gets a
|
|
80
|
+
distinct, resolvable id. Falls back to returning `anchor_id` unchanged (today's
|
|
81
|
+
behavior) under LOFT/GENERIC, if `anchor_id` doesn't parse, or if no numeric taxid
|
|
82
|
+
can be found for `taxon` (e.g. a newick tree with no embedded taxon ids) -- a
|
|
83
|
+
non-unique id is preferable to a synthesized one that can't be looked back up.
|
|
84
|
+
"""
|
|
85
|
+
if scheme != LOFT_TAXID or anchor_id is None:
|
|
86
|
+
return anchor_id
|
|
87
|
+
|
|
88
|
+
m = HOG_ID_RE.match(anchor_id)
|
|
89
|
+
if not m:
|
|
90
|
+
return anchor_id
|
|
91
|
+
|
|
92
|
+
taxid = taxid_for_taxon(taxon)
|
|
93
|
+
if taxid is None:
|
|
94
|
+
return anchor_id
|
|
95
|
+
|
|
96
|
+
base = m.group('fam') + m.group('subhog')
|
|
97
|
+
return f"{base}_{taxid}"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# The first ".<idx><letters>" segment immediately following a known parent prefix.
|
|
101
|
+
_BRANCH_SEGMENT_RE = re.compile(r"^\.(?P<idx>\d+)(?P<letters>[a-zA-Z]+)")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def encode_paralog_branch(nr):
|
|
105
|
+
"""0-indexed bijective base-26 branch letter: 0->'a', 25->'z', 26->'aa', 51->'az', 52->'ba', ...
|
|
106
|
+
|
|
107
|
+
Ported from the LOFT id-generation script's `_encodeParalogClusterId`.
|
|
108
|
+
"""
|
|
109
|
+
letters = []
|
|
110
|
+
while nr // 26 > 0:
|
|
111
|
+
letters.append(string.ascii_lowercase[nr % 26])
|
|
112
|
+
nr = nr // 26 - 1
|
|
113
|
+
letters.append(string.ascii_lowercase[nr % 26])
|
|
114
|
+
return ''.join(letters[::-1])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def decode_paralog_branch(letters):
|
|
118
|
+
"""Inverse of `encode_paralog_branch`."""
|
|
119
|
+
nr = 0
|
|
120
|
+
for ch in letters:
|
|
121
|
+
nr = nr * 26 + (ord(ch.lower()) - ord('a') + 1)
|
|
122
|
+
return nr - 1
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_branch_segment(hog_id, parent_prefix):
|
|
126
|
+
"""Parse the `(idx, letters)` of the branch segment a real sibling's `hog_id` adds
|
|
127
|
+
immediately after `parent_prefix` (the enclosing hog's own `fam + subhog`).
|
|
128
|
+
|
|
129
|
+
Returns None if `hog_id` is None or doesn't extend `parent_prefix` with a proper
|
|
130
|
+
".<digits><letters>" segment (e.g. a bare id, or one that doesn't share the prefix).
|
|
131
|
+
"""
|
|
132
|
+
if hog_id is None or not hog_id.startswith(parent_prefix):
|
|
133
|
+
return None
|
|
134
|
+
m = _BRANCH_SEGMENT_RE.match(hog_id[len(parent_prefix):])
|
|
135
|
+
if not m:
|
|
136
|
+
return None
|
|
137
|
+
return int(m.group('idx')), m.group('letters')
|
pyham/iham.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
from string import Template
|
|
2
|
+
import logging
|
|
3
|
+
import json
|
|
4
|
+
import datetime
|
|
5
|
+
import lxml.etree as etree
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class IHAM(object):
|
|
10
|
+
|
|
11
|
+
def __init__(self, newick_str, hog):
|
|
12
|
+
self.hog = hog
|
|
13
|
+
self.newick_str = newick_str
|
|
14
|
+
self.html_template = self._get_html_template()
|
|
15
|
+
self.famdata = json.dumps(self._get_famdata())
|
|
16
|
+
self.orthoxml = OrthoXML_manager(self.hog)
|
|
17
|
+
try:
|
|
18
|
+
numeric_hogid = int(self.hog.hog_id)
|
|
19
|
+
hogid = "HOG:{:07d}".format(numeric_hogid)
|
|
20
|
+
except ValueError:
|
|
21
|
+
hogid = self.hog.hog_id
|
|
22
|
+
self.HTML = self.html_template.safe_substitute({'name': hog.hog_id,
|
|
23
|
+
'hog_id': hogid,
|
|
24
|
+
"tree": self.newick_str,
|
|
25
|
+
"orthoxml": self.orthoxml.get_orthoxml_str(),
|
|
26
|
+
"fam_data": self.famdata
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
def _get_html_template(self):
|
|
30
|
+
x =Template('''<!DOCTYPE html>
|
|
31
|
+
<html lang="en">
|
|
32
|
+
<head>
|
|
33
|
+
<meta charset="UTF-8">
|
|
34
|
+
<title>$hog_id</title>
|
|
35
|
+
<script src="https://d3js.org/d3.v3.js"></script>
|
|
36
|
+
|
|
37
|
+
<!-- Bootstrap CDN -->
|
|
38
|
+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
|
|
39
|
+
integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
|
|
40
|
+
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
|
|
41
|
+
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
|
|
42
|
+
crossorigin="anonymous"></script>
|
|
43
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"
|
|
44
|
+
integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"
|
|
45
|
+
crossorigin="anonymous"></script>
|
|
46
|
+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"
|
|
47
|
+
integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm"
|
|
48
|
+
crossorigin="anonymous"></script>
|
|
49
|
+
|
|
50
|
+
<!-- Roboto font -->
|
|
51
|
+
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
|
52
|
+
|
|
53
|
+
<script src="https://cdn.jsdelivr.net/gh/DessimozLab/iHam@master/build/iHam.js"></script>
|
|
54
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/DessimozLab/iHam@master/build/iHam.css" type="text/css"/>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
body {
|
|
58
|
+
font-family: 'Roboto', sans-serif;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#updating {
|
|
62
|
+
position: absolute;
|
|
63
|
+
left: 20px;
|
|
64
|
+
display: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.alert_remove {
|
|
68
|
+
margin-bottom: 0px;
|
|
69
|
+
padding: 4px;
|
|
70
|
+
display: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.alert-link {
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#header {
|
|
78
|
+
margin-left: 20px;
|
|
79
|
+
margin-bottom: 20px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#header > h3 {
|
|
83
|
+
margin-top: 20px;
|
|
84
|
+
margin-bottom: 20px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#menu-bar > div {
|
|
88
|
+
display: inline-block;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.dropdown-toggle {
|
|
92
|
+
padding-top: 7px;
|
|
93
|
+
padding-bottom: 7px;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
96
|
+
</head>
|
|
97
|
+
<body>
|
|
98
|
+
|
|
99
|
+
<div id="header">
|
|
100
|
+
<h3>Hierarchical group $hog_id open at level of <span id="current-node"></span></h3>
|
|
101
|
+
|
|
102
|
+
<div id="menu-bar">
|
|
103
|
+
|
|
104
|
+
<div id="gene-tooltips-dropdown" class="dropdown">
|
|
105
|
+
<button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-toggle="dropdown"
|
|
106
|
+
aria-haspopup="true" aria-expanded="false">
|
|
107
|
+
Show gene tooltips on
|
|
108
|
+
</button>
|
|
109
|
+
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
|
110
|
+
<a class="dropdown-item active" href="#">Click</a>
|
|
111
|
+
<a class="dropdown-item" href="#">Mouseover</a>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div id="percentage-coverage-selector">
|
|
116
|
+
<button class="btn btn-sm btn-outline-dark" type="button"
|
|
117
|
+
aria-haspopup="true" aria-expanded="false">Remove columns under <input id="set_min_coverage"
|
|
118
|
+
type="number" step="10"
|
|
119
|
+
value="0"
|
|
120
|
+
min="0" max="100">% of
|
|
121
|
+
species coverage
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div id="updating">
|
|
128
|
+
Updating...
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="alert alert-info text-center alert_remove"
|
|
132
|
+
role="alert"
|
|
133
|
+
>
|
|
134
|
+
Lowly supported hogs have been removed as per settings.
|
|
135
|
+
<a class="alert-link" id="reset_column_filter">Click here to reset.</a>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
<div style="width: 1500px; min-width: 500px;" id="iham"></div>
|
|
140
|
+
|
|
141
|
+
<script>
|
|
142
|
+
|
|
143
|
+
(function (div) {
|
|
144
|
+
|
|
145
|
+
const data = {
|
|
146
|
+
"tree": '$tree',
|
|
147
|
+
"orthoxml": `$orthoxml`,
|
|
148
|
+
"fam_data": $fam_data
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
var theme = iHam()
|
|
153
|
+
.on('node_selected', function (node) {
|
|
154
|
+
d3.select('#current-node')
|
|
155
|
+
.text(node.node_name());
|
|
156
|
+
})
|
|
157
|
+
.orthoxml(data.orthoxml)
|
|
158
|
+
.show_oma_link(false)
|
|
159
|
+
.newick(data.tree)
|
|
160
|
+
.fam_data(data.fam_data)
|
|
161
|
+
.tree_width(330)
|
|
162
|
+
.board_width(530)
|
|
163
|
+
.query_gene({id: 3965})
|
|
164
|
+
.on("updating", function () {
|
|
165
|
+
d3.select("#updating")
|
|
166
|
+
.style("display", 'block');
|
|
167
|
+
})
|
|
168
|
+
.on("updated", function () {
|
|
169
|
+
d3.select("#updating")
|
|
170
|
+
.style("display", "none");
|
|
171
|
+
})
|
|
172
|
+
.on("hogs_removed", function (what) {
|
|
173
|
+
if (what.length) {
|
|
174
|
+
d3.select(".alert_remove")
|
|
175
|
+
.style("display", "block")
|
|
176
|
+
} else {
|
|
177
|
+
d3.select(".alert_remove")
|
|
178
|
+
.style("display", "none");
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
theme(div);
|
|
183
|
+
|
|
184
|
+
// Update the color schemas
|
|
185
|
+
|
|
186
|
+
// Update event for gene tooltips
|
|
187
|
+
d3.select("#gene-tooltips-dropdown")
|
|
188
|
+
.selectAll("a")
|
|
189
|
+
.on("click", function () {
|
|
190
|
+
// Manage state of menu itself
|
|
191
|
+
d3.select(this.parentNode).selectAll("a").classed("active", false);
|
|
192
|
+
d3.select(this)
|
|
193
|
+
.classed("active", true);
|
|
194
|
+
|
|
195
|
+
if (d3.select(this).text() === "Click") {
|
|
196
|
+
theme.gene_tooltips_on("click");
|
|
197
|
+
}
|
|
198
|
+
if (d3.select(this).text() === "Mouseover") {
|
|
199
|
+
theme.gene_tooltips_on("mouseover");
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Set minimum species coverage
|
|
204
|
+
d3.select("#percentage-coverage-selector").select("input")
|
|
205
|
+
.on("input", function () {
|
|
206
|
+
theme.coverage_threshold(d3.select(this).property("value"));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Reset the coverage
|
|
210
|
+
d3.select("#reset_column_filter")
|
|
211
|
+
.on("click", function () {
|
|
212
|
+
d3.select("#percentage-coverage-selector").select("input").property("value", 0);
|
|
213
|
+
theme.coverage_threshold(0);
|
|
214
|
+
})
|
|
215
|
+
})(document.getElementById('iham'));
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
</script>
|
|
219
|
+
</body>
|
|
220
|
+
</html>''')
|
|
221
|
+
return x
|
|
222
|
+
|
|
223
|
+
def _get_famdata(self):
|
|
224
|
+
|
|
225
|
+
fd = []
|
|
226
|
+
|
|
227
|
+
for gene in self.hog.get_all_descendant_genes():
|
|
228
|
+
data = {}
|
|
229
|
+
data["taxon"] = {"species": gene.genome.name}
|
|
230
|
+
data["xrefid"] = gene.prot_id
|
|
231
|
+
data["sequence_length"] = 0
|
|
232
|
+
data["gc_content"] = 0
|
|
233
|
+
data["protid"] = gene.prot_id
|
|
234
|
+
data["id"] = gene.unique_id
|
|
235
|
+
|
|
236
|
+
fd.append(data)
|
|
237
|
+
|
|
238
|
+
return fd
|
|
239
|
+
|
|
240
|
+
class OrthoXML_manager(object):
|
|
241
|
+
|
|
242
|
+
def __init__(self, hog):
|
|
243
|
+
|
|
244
|
+
self.hog = hog
|
|
245
|
+
|
|
246
|
+
origin = "OMA"
|
|
247
|
+
vers = datetime.date.today().strftime("%b %Y")
|
|
248
|
+
origin += " standalone (bottom-up)"
|
|
249
|
+
|
|
250
|
+
self.xml = self._create_xml(origin, vers)
|
|
251
|
+
|
|
252
|
+
self._add_species_data()
|
|
253
|
+
|
|
254
|
+
self._add_groups()
|
|
255
|
+
|
|
256
|
+
def _create_xml(self, origin, originVersion):
|
|
257
|
+
|
|
258
|
+
xml_core = etree.Element("orthoXML")
|
|
259
|
+
xml_core.set("origin", origin)
|
|
260
|
+
xml_core.set("originVersion", originVersion)
|
|
261
|
+
xml_core.set("version", "0.3")
|
|
262
|
+
xml_core.set("xmlns", "http://orthoXML.org/2011/")
|
|
263
|
+
return xml_core
|
|
264
|
+
|
|
265
|
+
def _add_species_data(self):
|
|
266
|
+
|
|
267
|
+
sp_data = self.hog.get_all_descendant_genes_clustered_by_species()
|
|
268
|
+
|
|
269
|
+
for species, childs in sp_data.items():
|
|
270
|
+
|
|
271
|
+
species_xml = etree.Element("species")
|
|
272
|
+
species_xml.set("name", species.name)
|
|
273
|
+
species_xml.set("NCBITaxId", str(-1))
|
|
274
|
+
self.xml.insert(0, species_xml)
|
|
275
|
+
|
|
276
|
+
# Add <database> into <species>
|
|
277
|
+
database_xml = etree.SubElement(species_xml, "database")
|
|
278
|
+
database_xml.set("name", "None")
|
|
279
|
+
database_xml.set("version", "None")
|
|
280
|
+
|
|
281
|
+
# Add <genes> TAG into <database>
|
|
282
|
+
genes_xml = etree.SubElement(database_xml, "genes")
|
|
283
|
+
|
|
284
|
+
# Fill <genes> with <gene>
|
|
285
|
+
for gene_obj in childs:
|
|
286
|
+
gene_xml = etree.SubElement(genes_xml, "gene")
|
|
287
|
+
gene_xml.set("id", str(gene_obj.unique_id))
|
|
288
|
+
gene_xml.set("protId", str(gene_obj.prot_id))
|
|
289
|
+
|
|
290
|
+
def _add_groups(self):
|
|
291
|
+
|
|
292
|
+
from . import abstractgene as absGene
|
|
293
|
+
|
|
294
|
+
def _process_child(child, current_xml):
|
|
295
|
+
|
|
296
|
+
if isinstance(child, absGene.Gene):
|
|
297
|
+
generef_xml = etree.SubElement(current_xml, "geneRef")
|
|
298
|
+
generef_xml.set('id', str(child.unique_id))
|
|
299
|
+
else:
|
|
300
|
+
_visit(child, current_xml)
|
|
301
|
+
|
|
302
|
+
def _visit(hog, parent):
|
|
303
|
+
|
|
304
|
+
if len(hog.children) == 1:
|
|
305
|
+
current_hog_xml = parent
|
|
306
|
+
|
|
307
|
+
elif len(hog.duplications) >= 1:
|
|
308
|
+
|
|
309
|
+
dup_child = []
|
|
310
|
+
|
|
311
|
+
for duplicationNode in hog.duplications:
|
|
312
|
+
dup_child += duplicationNode.children
|
|
313
|
+
|
|
314
|
+
remaining_hog = list(set(hog.children) - set(dup_child))
|
|
315
|
+
|
|
316
|
+
if len(remaining_hog) == 0 and len(hog.duplications) == 1 :
|
|
317
|
+
|
|
318
|
+
current_hog_xml = parent
|
|
319
|
+
|
|
320
|
+
else:
|
|
321
|
+
|
|
322
|
+
current_hog_xml = etree.SubElement(parent, "orthologGroup")
|
|
323
|
+
taxon = etree.SubElement(current_hog_xml, "property")
|
|
324
|
+
taxon.set("name", 'TaxRange')
|
|
325
|
+
taxon.set("value", hog.genome.name)
|
|
326
|
+
|
|
327
|
+
current_hog_xml.set('id', str(hog.hog_id))
|
|
328
|
+
|
|
329
|
+
else:
|
|
330
|
+
current_hog_xml = etree.SubElement(parent, "orthologGroup")
|
|
331
|
+
taxon = etree.SubElement(current_hog_xml, "property")
|
|
332
|
+
taxon.set("name", 'TaxRange')
|
|
333
|
+
taxon.set("value", hog.genome.name)
|
|
334
|
+
|
|
335
|
+
current_hog_xml.set('id', str(hog.hog_id))
|
|
336
|
+
|
|
337
|
+
processed_child = []
|
|
338
|
+
if len(hog.duplications) > 0:
|
|
339
|
+
|
|
340
|
+
for duplicationNode in hog.duplications:
|
|
341
|
+
|
|
342
|
+
paralogGroup = etree.SubElement(current_hog_xml, "paralogGroup")
|
|
343
|
+
|
|
344
|
+
for child in duplicationNode.children:
|
|
345
|
+
_process_child(child, paralogGroup)
|
|
346
|
+
processed_child.append(child)
|
|
347
|
+
|
|
348
|
+
remaining_hog = list(set(hog.children) - set(processed_child))
|
|
349
|
+
for child in remaining_hog:
|
|
350
|
+
_process_child(child, current_hog_xml)
|
|
351
|
+
|
|
352
|
+
self.groupsxml = etree.SubElement(self.xml, "groups")
|
|
353
|
+
|
|
354
|
+
_visit(self.hog, self.groupsxml)
|
|
355
|
+
|
|
356
|
+
def get_orthoxml_str(self):
|
|
357
|
+
return etree.tostring(self.xml, encoding='utf8', method='xml', pretty_print=False).decode('utf-8')
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|