biocypher 0.5.17__py3-none-any.whl → 0.5.20__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.
Potentially problematic release.
This version of biocypher might be problematic. Click here for more details.
- biocypher/__init__.py +10 -11
- biocypher/_config/__init__.py +25 -27
- biocypher/_config/biocypher_config.yaml +1 -2
- biocypher/_connect.py +59 -79
- biocypher/_core.py +146 -78
- biocypher/_create.py +55 -52
- biocypher/_deduplicate.py +81 -36
- biocypher/_logger.py +12 -13
- biocypher/_mapping.py +69 -83
- biocypher/_metadata.py +12 -17
- biocypher/_misc.py +17 -28
- biocypher/_ontology.py +85 -101
- biocypher/_pandas.py +46 -11
- biocypher/_translate.py +93 -113
- biocypher/_write.py +457 -404
- {biocypher-0.5.17.dist-info → biocypher-0.5.20.dist-info}/METADATA +16 -6
- biocypher-0.5.20.dist-info/RECORD +23 -0
- biocypher-0.5.17.dist-info/RECORD +0 -23
- {biocypher-0.5.17.dist-info → biocypher-0.5.20.dist-info}/LICENSE +0 -0
- {biocypher-0.5.17.dist-info → biocypher-0.5.20.dist-info}/WHEEL +0 -0
biocypher/_misc.py
CHANGED
|
@@ -13,7 +13,7 @@ Handy functions for use in various places.
|
|
|
13
13
|
"""
|
|
14
14
|
from ._logger import logger
|
|
15
15
|
|
|
16
|
-
logger.debug(f
|
|
16
|
+
logger.debug(f"Loading module {__name__}.")
|
|
17
17
|
|
|
18
18
|
from typing import (
|
|
19
19
|
Any,
|
|
@@ -31,7 +31,7 @@ from treelib import Tree
|
|
|
31
31
|
import networkx as nx
|
|
32
32
|
import stringcase
|
|
33
33
|
|
|
34
|
-
__all__ = [
|
|
34
|
+
__all__ = ["LIST_LIKE", "SIMPLE_TYPES", "ensure_iterable", "to_list"]
|
|
35
35
|
|
|
36
36
|
SIMPLE_TYPES = (
|
|
37
37
|
bytes,
|
|
@@ -60,11 +60,9 @@ def to_list(value: Any) -> list:
|
|
|
60
60
|
"""
|
|
61
61
|
|
|
62
62
|
if isinstance(value, LIST_LIKE):
|
|
63
|
-
|
|
64
63
|
value = list(value)
|
|
65
64
|
|
|
66
65
|
else:
|
|
67
|
-
|
|
68
66
|
value = [value]
|
|
69
67
|
|
|
70
68
|
return value
|
|
@@ -75,7 +73,7 @@ def ensure_iterable(value: Any) -> Iterable:
|
|
|
75
73
|
Returns iterables, except strings, wraps simple types into tuple.
|
|
76
74
|
"""
|
|
77
75
|
|
|
78
|
-
return value if isinstance(value, LIST_LIKE) else (value,
|
|
76
|
+
return value if isinstance(value, LIST_LIKE) else (value,)
|
|
79
77
|
|
|
80
78
|
|
|
81
79
|
def create_tree_visualisation(inheritance_tree: Union[dict, nx.Graph]) -> str:
|
|
@@ -84,7 +82,6 @@ def create_tree_visualisation(inheritance_tree: Union[dict, nx.Graph]) -> str:
|
|
|
84
82
|
"""
|
|
85
83
|
|
|
86
84
|
if isinstance(inheritance_tree, nx.Graph):
|
|
87
|
-
|
|
88
85
|
inheritance_tree = nx.to_dict_of_lists(inheritance_tree)
|
|
89
86
|
# unlist values
|
|
90
87
|
inheritance_tree = {k: v[0] for k, v in inheritance_tree.items() if v}
|
|
@@ -95,56 +92,48 @@ def create_tree_visualisation(inheritance_tree: Union[dict, nx.Graph]) -> str:
|
|
|
95
92
|
root = list(parents - classes)
|
|
96
93
|
|
|
97
94
|
if len(root) > 1:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
root = 'entity' # default: good standard? TODO
|
|
95
|
+
if "entity" in root:
|
|
96
|
+
root = "entity" # default: good standard? TODO
|
|
102
97
|
|
|
103
98
|
else:
|
|
104
|
-
|
|
105
99
|
raise ValueError(
|
|
106
|
-
|
|
107
|
-
f
|
|
100
|
+
"Inheritance tree cannot have more than one root node. "
|
|
101
|
+
f"Found {len(root)}: {root}."
|
|
108
102
|
)
|
|
109
103
|
|
|
110
104
|
else:
|
|
111
|
-
|
|
112
105
|
root = root[0]
|
|
113
106
|
|
|
114
107
|
if not root:
|
|
115
108
|
# find key whose value is None
|
|
116
|
-
root = list(inheritance_tree.keys())[
|
|
117
|
-
|
|
109
|
+
root = list(inheritance_tree.keys())[
|
|
110
|
+
list(inheritance_tree.values()).index(None)
|
|
111
|
+
]
|
|
118
112
|
|
|
119
113
|
tree = Tree()
|
|
120
114
|
|
|
121
115
|
tree.create_node(root, root)
|
|
122
116
|
|
|
123
117
|
while classes:
|
|
124
|
-
|
|
125
118
|
for child in classes:
|
|
126
|
-
|
|
127
119
|
parent = inheritance_tree[child]
|
|
128
120
|
|
|
129
121
|
if parent in tree.nodes.keys() or parent == root:
|
|
130
|
-
|
|
131
122
|
tree.create_node(child, child, parent=parent)
|
|
132
123
|
|
|
133
124
|
for node in tree.nodes.keys():
|
|
134
|
-
|
|
135
125
|
if node in classes:
|
|
136
|
-
|
|
137
126
|
classes.remove(node)
|
|
138
127
|
|
|
139
128
|
return tree
|
|
140
129
|
|
|
141
130
|
|
|
142
131
|
# string conversion, adapted from Biolink Model Toolkit
|
|
143
|
-
lowercase_pattern = re.compile(r
|
|
144
|
-
underscore_pattern = re.compile(r
|
|
132
|
+
lowercase_pattern = re.compile(r"[a-zA-Z]*[a-z][a-zA-Z]*")
|
|
133
|
+
underscore_pattern = re.compile(r"(?<!^)(?=[A-Z][a-z])")
|
|
145
134
|
|
|
146
135
|
|
|
147
|
-
def from_pascal(s: str, sep: str =
|
|
136
|
+
def from_pascal(s: str, sep: str = " ") -> str:
|
|
148
137
|
underscored = underscore_pattern.sub(sep, s)
|
|
149
138
|
lowercased = lowercase_pattern.sub(
|
|
150
139
|
lambda match: match.group(0).lower(),
|
|
@@ -163,7 +152,7 @@ def pascalcase_to_sentencecase(s: str) -> str:
|
|
|
163
152
|
Returns:
|
|
164
153
|
string in sentence case form
|
|
165
154
|
"""
|
|
166
|
-
return from_pascal(s, sep=
|
|
155
|
+
return from_pascal(s, sep=" ")
|
|
167
156
|
|
|
168
157
|
|
|
169
158
|
def snakecase_to_sentencecase(s: str) -> str:
|
|
@@ -202,7 +191,7 @@ def sentencecase_to_pascalcase(s: str) -> str:
|
|
|
202
191
|
Returns:
|
|
203
192
|
string in PascalCase form
|
|
204
193
|
"""
|
|
205
|
-
return re.sub(r
|
|
194
|
+
return re.sub(r"(?:^| )([a-zA-Z])", lambda match: match.group(1).upper(), s)
|
|
206
195
|
|
|
207
196
|
|
|
208
197
|
def to_lower_sentence_case(s: str) -> str:
|
|
@@ -216,9 +205,9 @@ def to_lower_sentence_case(s: str) -> str:
|
|
|
216
205
|
Returns:
|
|
217
206
|
string in lower sentence case form
|
|
218
207
|
"""
|
|
219
|
-
if
|
|
208
|
+
if "_" in s:
|
|
220
209
|
return snakecase_to_sentencecase(s)
|
|
221
|
-
elif
|
|
210
|
+
elif " " in s:
|
|
222
211
|
return s.lower()
|
|
223
212
|
elif s[0].isupper():
|
|
224
213
|
return pascalcase_to_sentencecase(s)
|
biocypher/_ontology.py
CHANGED
|
@@ -17,7 +17,7 @@ import os
|
|
|
17
17
|
|
|
18
18
|
from ._logger import logger
|
|
19
19
|
|
|
20
|
-
logger.debug(f
|
|
20
|
+
logger.debug(f"Loading module {__name__}.")
|
|
21
21
|
|
|
22
22
|
from typing import Optional
|
|
23
23
|
from datetime import datetime
|
|
@@ -40,6 +40,7 @@ class OntologyAdapter:
|
|
|
40
40
|
labels are formatted in lower sentence case. In some cases, this means that
|
|
41
41
|
we replace underscores with spaces.
|
|
42
42
|
"""
|
|
43
|
+
|
|
43
44
|
def __init__(
|
|
44
45
|
self,
|
|
45
46
|
ontology_file: str,
|
|
@@ -63,7 +64,7 @@ class OntologyAdapter:
|
|
|
63
64
|
node in the head ontology that should be used to join to the
|
|
64
65
|
root node of the tail ontology. Defaults to None.
|
|
65
66
|
|
|
66
|
-
merge_nodes (bool): If True, head and tail join nodes will be
|
|
67
|
+
merge_nodes (bool): If True, head and tail join nodes will be
|
|
67
68
|
merged, using the label of the head join node. If False, the
|
|
68
69
|
tail join node will be attached as a child of the head join
|
|
69
70
|
node.
|
|
@@ -76,7 +77,7 @@ class OntologyAdapter:
|
|
|
76
77
|
be removed. Defaults to True.
|
|
77
78
|
"""
|
|
78
79
|
|
|
79
|
-
logger.info(f
|
|
80
|
+
logger.info(f"Instantiating OntologyAdapter class for {ontology_file}.")
|
|
80
81
|
|
|
81
82
|
self._ontology_file = ontology_file
|
|
82
83
|
self._root_label = root_label
|
|
@@ -93,7 +94,6 @@ class OntologyAdapter:
|
|
|
93
94
|
)
|
|
94
95
|
|
|
95
96
|
def _rdf_to_nx(self, g, root_label, switch_id_and_label=True):
|
|
96
|
-
|
|
97
97
|
# Loop through all labels in the ontology
|
|
98
98
|
for s, _, o in g.triples((None, rdflib.RDFS.label, None)):
|
|
99
99
|
# If the label is the root label, set the root node to the subject of the label
|
|
@@ -102,7 +102,7 @@ class OntologyAdapter:
|
|
|
102
102
|
break
|
|
103
103
|
else:
|
|
104
104
|
raise ValueError(
|
|
105
|
-
f
|
|
105
|
+
f"Could not find root node with label {root_label}"
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
# Create a directed graph to represent the ontology as a tree
|
|
@@ -110,7 +110,6 @@ class OntologyAdapter:
|
|
|
110
110
|
|
|
111
111
|
# Define a recursive function to add subclasses to the graph
|
|
112
112
|
def add_subclasses(node):
|
|
113
|
-
|
|
114
113
|
# Only add nodes that have a label
|
|
115
114
|
if (node, rdflib.RDFS.label, None) not in g:
|
|
116
115
|
return
|
|
@@ -119,25 +118,23 @@ class OntologyAdapter:
|
|
|
119
118
|
|
|
120
119
|
if nx_id not in G:
|
|
121
120
|
G.add_node(nx_id)
|
|
122
|
-
G.nodes[nx_id][
|
|
121
|
+
G.nodes[nx_id]["label"] = nx_label
|
|
123
122
|
|
|
124
123
|
# Recursively add all subclasses of the node to the graph
|
|
125
124
|
for s, _, o in g.triples((None, rdflib.RDFS.subClassOf, node)):
|
|
126
|
-
|
|
127
125
|
# Only add nodes that have a label
|
|
128
126
|
if (s, rdflib.RDFS.label, None) not in g:
|
|
129
127
|
continue
|
|
130
128
|
|
|
131
129
|
s_id, s_label = _get_nx_id_and_label(s)
|
|
132
130
|
G.add_node(s_id)
|
|
133
|
-
G.nodes[s_id][
|
|
131
|
+
G.nodes[s_id]["label"] = s_label
|
|
134
132
|
|
|
135
133
|
G.add_edge(s_id, nx_id)
|
|
136
134
|
add_subclasses(s)
|
|
137
135
|
add_parents(s)
|
|
138
136
|
|
|
139
137
|
def add_parents(node):
|
|
140
|
-
|
|
141
138
|
# Only add nodes that have a label
|
|
142
139
|
if (node, rdflib.RDFS.label, None) not in g:
|
|
143
140
|
return
|
|
@@ -146,7 +143,6 @@ class OntologyAdapter:
|
|
|
146
143
|
|
|
147
144
|
# Recursively add all parents of the node to the graph
|
|
148
145
|
for s, _, o in g.triples((node, rdflib.RDFS.subClassOf, None)):
|
|
149
|
-
|
|
150
146
|
# Only add nodes that have a label
|
|
151
147
|
if (o, rdflib.RDFS.label, None) not in g:
|
|
152
148
|
continue
|
|
@@ -158,15 +154,16 @@ class OntologyAdapter:
|
|
|
158
154
|
continue
|
|
159
155
|
|
|
160
156
|
G.add_node(o_id)
|
|
161
|
-
G.nodes[o_id][
|
|
157
|
+
G.nodes[o_id]["label"] = o_label
|
|
162
158
|
|
|
163
159
|
G.add_edge(nx_id, o_id)
|
|
164
160
|
add_parents(o)
|
|
165
161
|
|
|
166
162
|
def _get_nx_id_and_label(node):
|
|
167
163
|
node_id_str = self._remove_prefix(str(node))
|
|
168
|
-
node_label_str = str(g.value(node,
|
|
169
|
-
|
|
164
|
+
node_label_str = str(g.value(node, rdflib.RDFS.label)).replace(
|
|
165
|
+
"_", " "
|
|
166
|
+
)
|
|
170
167
|
node_label_str = _misc.to_lower_sentence_case(node_label_str)
|
|
171
168
|
|
|
172
169
|
nx_id = node_label_str if switch_id_and_label else node_id_str
|
|
@@ -185,7 +182,7 @@ class OntologyAdapter:
|
|
|
185
182
|
everything before the last separator.
|
|
186
183
|
"""
|
|
187
184
|
if self._remove_prefixes:
|
|
188
|
-
return uri.rsplit(
|
|
185
|
+
return uri.rsplit("#", 1)[-1].rsplit("/", 1)[-1]
|
|
189
186
|
else:
|
|
190
187
|
return uri
|
|
191
188
|
|
|
@@ -202,17 +199,17 @@ class OntologyAdapter:
|
|
|
202
199
|
"""
|
|
203
200
|
Get the format of the ontology file.
|
|
204
201
|
"""
|
|
205
|
-
if ontology_file.endswith(
|
|
206
|
-
return
|
|
207
|
-
elif ontology_file.endswith(
|
|
208
|
-
raise NotImplementedError(
|
|
209
|
-
elif ontology_file.endswith(
|
|
210
|
-
return
|
|
211
|
-
elif ontology_file.endswith(
|
|
212
|
-
return
|
|
202
|
+
if ontology_file.endswith(".owl"):
|
|
203
|
+
return "application/rdf+xml"
|
|
204
|
+
elif ontology_file.endswith(".obo"):
|
|
205
|
+
raise NotImplementedError("OBO format not yet supported")
|
|
206
|
+
elif ontology_file.endswith(".rdf"):
|
|
207
|
+
return "application/rdf+xml"
|
|
208
|
+
elif ontology_file.endswith(".ttl"):
|
|
209
|
+
return "ttl"
|
|
213
210
|
else:
|
|
214
211
|
raise ValueError(
|
|
215
|
-
f
|
|
212
|
+
f"Could not determine format of ontology file {ontology_file}"
|
|
216
213
|
)
|
|
217
214
|
|
|
218
215
|
def get_nx_graph(self):
|
|
@@ -254,10 +251,11 @@ class Ontology:
|
|
|
254
251
|
while an arbitrary number of other resources can become "tail" ontologies at
|
|
255
252
|
arbitrary fusion points inside the "head" ontology.
|
|
256
253
|
"""
|
|
254
|
+
|
|
257
255
|
def __init__(
|
|
258
256
|
self,
|
|
259
257
|
head_ontology: dict,
|
|
260
|
-
ontology_mapping:
|
|
258
|
+
ontology_mapping: "OntologyMapping",
|
|
261
259
|
tail_ontologies: Optional[dict] = None,
|
|
262
260
|
):
|
|
263
261
|
"""
|
|
@@ -271,7 +269,7 @@ class Ontology:
|
|
|
271
269
|
"""
|
|
272
270
|
|
|
273
271
|
self._head_ontology_meta = head_ontology
|
|
274
|
-
self.
|
|
272
|
+
self.mapping = ontology_mapping
|
|
275
273
|
self._tail_ontology_meta = tail_ontologies
|
|
276
274
|
|
|
277
275
|
self._tail_ontologies = None
|
|
@@ -311,21 +309,21 @@ class Ontology:
|
|
|
311
309
|
instance variable (head) or a dictionary (tail).
|
|
312
310
|
"""
|
|
313
311
|
|
|
314
|
-
logger.info(
|
|
312
|
+
logger.info("Loading ontologies...")
|
|
315
313
|
|
|
316
314
|
self._head_ontology = OntologyAdapter(
|
|
317
|
-
self._head_ontology_meta[
|
|
318
|
-
self._head_ontology_meta[
|
|
315
|
+
self._head_ontology_meta["url"],
|
|
316
|
+
self._head_ontology_meta["root_node"],
|
|
319
317
|
)
|
|
320
318
|
|
|
321
319
|
if self._tail_ontology_meta:
|
|
322
320
|
self._tail_ontologies = {}
|
|
323
321
|
for key, value in self._tail_ontology_meta.items():
|
|
324
322
|
self._tail_ontologies[key] = OntologyAdapter(
|
|
325
|
-
ontology_file
|
|
326
|
-
root_label
|
|
327
|
-
head_join_node
|
|
328
|
-
merge_nodes
|
|
323
|
+
ontology_file=value["url"],
|
|
324
|
+
root_label=value["tail_join_node"],
|
|
325
|
+
head_join_node=value["head_join_node"],
|
|
326
|
+
merge_nodes=value.get("merge_nodes", True),
|
|
329
327
|
)
|
|
330
328
|
|
|
331
329
|
def _assert_join_node(self, adapter: OntologyAdapter) -> None:
|
|
@@ -342,10 +340,9 @@ class Ontology:
|
|
|
342
340
|
head_join_node = adapter.get_head_join_node()
|
|
343
341
|
|
|
344
342
|
if head_join_node not in self._head_ontology.get_nx_graph().nodes:
|
|
345
|
-
|
|
346
343
|
raise ValueError(
|
|
347
|
-
f
|
|
348
|
-
f
|
|
344
|
+
f"Head join node {head_join_node} not found in "
|
|
345
|
+
f"head ontology."
|
|
349
346
|
)
|
|
350
347
|
|
|
351
348
|
def _join_ontologies(self, adapter: OntologyAdapter) -> None:
|
|
@@ -383,11 +380,9 @@ class Ontology:
|
|
|
383
380
|
# as parent of tail join node
|
|
384
381
|
tail_ontology_subtree.add_node(
|
|
385
382
|
head_join_node,
|
|
386
|
-
**self._head_ontology.get_nx_graph().nodes[head_join_node]
|
|
387
|
-
)
|
|
388
|
-
tail_ontology_subtree.add_edge(
|
|
389
|
-
tail_join_node, head_join_node
|
|
383
|
+
**self._head_ontology.get_nx_graph().nodes[head_join_node],
|
|
390
384
|
)
|
|
385
|
+
tail_ontology_subtree.add_edge(tail_join_node, head_join_node)
|
|
391
386
|
|
|
392
387
|
# else rename tail join node to match head join node if necessary
|
|
393
388
|
elif not tail_join_node == head_join_node:
|
|
@@ -408,47 +403,44 @@ class Ontology:
|
|
|
408
403
|
if not self._nx_graph:
|
|
409
404
|
self._nx_graph = self._head_ontology.get_nx_graph().copy()
|
|
410
405
|
|
|
411
|
-
for key, value in self.extended_schema.items():
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if self._nx_graph.has_node(value.get('synonym_for')):
|
|
416
|
-
|
|
406
|
+
for key, value in self.mapping.extended_schema.items():
|
|
407
|
+
if not value.get("is_a"):
|
|
408
|
+
if self._nx_graph.has_node(value.get("synonym_for")):
|
|
417
409
|
continue
|
|
418
|
-
|
|
410
|
+
|
|
419
411
|
if not self._nx_graph.has_node(key):
|
|
420
|
-
|
|
421
412
|
raise ValueError(
|
|
422
|
-
f
|
|
423
|
-
|
|
424
|
-
|
|
413
|
+
f"Node {key} not found in ontology, but also has no "
|
|
414
|
+
"inheritance definition. Please check your schema for "
|
|
415
|
+
"spelling errors or a missing `is_a` definition."
|
|
425
416
|
)
|
|
426
|
-
|
|
417
|
+
|
|
427
418
|
continue
|
|
428
419
|
|
|
429
|
-
parents = _misc.to_list(value.get(
|
|
420
|
+
parents = _misc.to_list(value.get("is_a"))
|
|
430
421
|
child = key
|
|
431
422
|
|
|
432
423
|
while parents:
|
|
433
424
|
parent = parents.pop(0)
|
|
434
425
|
|
|
435
426
|
if parent not in self._nx_graph.nodes:
|
|
436
|
-
|
|
437
427
|
self._nx_graph.add_node(parent)
|
|
438
428
|
self._nx_graph.nodes[parent][
|
|
439
|
-
|
|
429
|
+
"label"
|
|
430
|
+
] = _misc.sentencecase_to_pascalcase(parent)
|
|
440
431
|
|
|
441
432
|
# mark parent as user extension
|
|
442
|
-
self._nx_graph.nodes[parent][
|
|
433
|
+
self._nx_graph.nodes[parent]["user_extension"] = True
|
|
443
434
|
self._extended_nodes.add(parent)
|
|
444
435
|
|
|
445
436
|
if child not in self._nx_graph.nodes:
|
|
446
437
|
self._nx_graph.add_node(child)
|
|
447
438
|
self._nx_graph.nodes[child][
|
|
448
|
-
|
|
439
|
+
"label"
|
|
440
|
+
] = _misc.sentencecase_to_pascalcase(child)
|
|
449
441
|
|
|
450
442
|
# mark child as user extension
|
|
451
|
-
self._nx_graph.nodes[child][
|
|
443
|
+
self._nx_graph.nodes[child]["user_extension"] = True
|
|
452
444
|
self._extended_nodes.add(child)
|
|
453
445
|
|
|
454
446
|
self._nx_graph.add_edge(child, parent)
|
|
@@ -463,29 +455,28 @@ class Ontology:
|
|
|
463
455
|
if not self._nx_graph:
|
|
464
456
|
self._nx_graph = self._head_ontology.get_nx_graph().copy()
|
|
465
457
|
|
|
466
|
-
if
|
|
458
|
+
if "entity" not in self._nx_graph.nodes:
|
|
467
459
|
return
|
|
468
460
|
|
|
469
461
|
# biolink classes that are disjoint from entity
|
|
470
462
|
disjoint_classes = [
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
463
|
+
"frequency qualifier mixin",
|
|
464
|
+
"chemical entity to entity association mixin",
|
|
465
|
+
"ontology class",
|
|
466
|
+
"relationship quantifier",
|
|
467
|
+
"physical essence or occurrent",
|
|
468
|
+
"gene or gene product",
|
|
469
|
+
"subject of investigation",
|
|
478
470
|
]
|
|
479
471
|
|
|
480
472
|
for node in disjoint_classes:
|
|
481
|
-
|
|
482
473
|
if not self._nx_graph.nodes.get(node):
|
|
483
|
-
|
|
484
474
|
self._nx_graph.add_node(node)
|
|
485
475
|
self._nx_graph.nodes[node][
|
|
486
|
-
|
|
476
|
+
"label"
|
|
477
|
+
] = _misc.sentencecase_to_pascalcase(node)
|
|
487
478
|
|
|
488
|
-
self._nx_graph.add_edge(node,
|
|
479
|
+
self._nx_graph.add_edge(node, "entity")
|
|
489
480
|
|
|
490
481
|
def _add_properties(self) -> None:
|
|
491
482
|
"""
|
|
@@ -494,22 +485,19 @@ class Ontology:
|
|
|
494
485
|
setting the synonym as the primary node label.
|
|
495
486
|
"""
|
|
496
487
|
|
|
497
|
-
for key, value in self.extended_schema.items():
|
|
498
|
-
|
|
488
|
+
for key, value in self.mapping.extended_schema.items():
|
|
499
489
|
if key in self._nx_graph.nodes:
|
|
500
|
-
|
|
501
490
|
self._nx_graph.nodes[key].update(value)
|
|
502
491
|
|
|
503
|
-
if value.get(
|
|
504
|
-
|
|
492
|
+
if value.get("synonym_for"):
|
|
505
493
|
# change node label to synonym
|
|
506
|
-
if value[
|
|
494
|
+
if value["synonym_for"] not in self._nx_graph.nodes:
|
|
507
495
|
raise ValueError(
|
|
508
496
|
f'Node {value["synonym_for"]} not found in ontology.'
|
|
509
497
|
)
|
|
510
498
|
|
|
511
499
|
self._nx_graph = nx.relabel_nodes(
|
|
512
|
-
self._nx_graph, {value[
|
|
500
|
+
self._nx_graph, {value["synonym_for"]: key}
|
|
513
501
|
)
|
|
514
502
|
|
|
515
503
|
def get_ancestors(self, node_label: str) -> list:
|
|
@@ -541,42 +529,41 @@ class Ontology:
|
|
|
541
529
|
"""
|
|
542
530
|
|
|
543
531
|
if not self._nx_graph:
|
|
544
|
-
raise ValueError(
|
|
532
|
+
raise ValueError("Ontology not loaded.")
|
|
545
533
|
|
|
546
534
|
if not self._tail_ontologies:
|
|
547
|
-
msg = f
|
|
535
|
+
msg = f"Showing ontology structure based on {self._head_ontology._ontology_file}"
|
|
548
536
|
|
|
549
537
|
else:
|
|
550
|
-
msg = f
|
|
538
|
+
msg = f"Showing ontology structure based on {len(self._tail_ontology_meta)+1} ontologies: "
|
|
551
539
|
|
|
552
540
|
print(msg)
|
|
553
541
|
|
|
554
542
|
if not full:
|
|
555
|
-
|
|
556
543
|
# set of leaves and their intermediate parents up to the root
|
|
557
|
-
filter_nodes = set(self.extended_schema.keys())
|
|
544
|
+
filter_nodes = set(self.mapping.extended_schema.keys())
|
|
558
545
|
|
|
559
|
-
for node in self.extended_schema.keys():
|
|
546
|
+
for node in self.mapping.extended_schema.keys():
|
|
560
547
|
filter_nodes.update(self.get_ancestors(node).nodes)
|
|
561
548
|
|
|
562
549
|
# filter graph
|
|
563
550
|
G = self._nx_graph.subgraph(filter_nodes)
|
|
564
551
|
|
|
565
552
|
else:
|
|
566
|
-
|
|
567
553
|
G = self._nx_graph
|
|
568
554
|
|
|
569
555
|
if not to_disk:
|
|
570
|
-
|
|
571
556
|
# create tree
|
|
572
557
|
tree = _misc.create_tree_visualisation(G)
|
|
573
558
|
|
|
574
559
|
# add synonym information
|
|
575
|
-
for node in self.extended_schema:
|
|
576
|
-
if self.extended_schema[node]
|
|
560
|
+
for node in self.mapping.extended_schema:
|
|
561
|
+
if not isinstance(self.mapping.extended_schema[node], dict):
|
|
562
|
+
continue
|
|
563
|
+
if self.mapping.extended_schema[node].get("synonym_for"):
|
|
577
564
|
tree.nodes[node].tag = (
|
|
578
|
-
f
|
|
579
|
-
f"{self.extended_schema[node].get('synonym_for')}"
|
|
565
|
+
f"{node} = "
|
|
566
|
+
f"{self.mapping.extended_schema[node].get('synonym_for')}"
|
|
580
567
|
)
|
|
581
568
|
|
|
582
569
|
tree.show()
|
|
@@ -584,26 +571,24 @@ class Ontology:
|
|
|
584
571
|
return tree
|
|
585
572
|
|
|
586
573
|
else:
|
|
587
|
-
|
|
588
574
|
# convert lists/dicts to strings for vis only
|
|
589
575
|
for node in G.nodes:
|
|
590
|
-
|
|
591
576
|
# rename node and use former id as label
|
|
592
|
-
label = G.nodes[node].get(
|
|
577
|
+
label = G.nodes[node].get("label")
|
|
593
578
|
|
|
594
579
|
if not label:
|
|
595
580
|
label = node
|
|
596
581
|
|
|
597
582
|
G = nx.relabel_nodes(G, {node: label})
|
|
598
|
-
G.nodes[label][
|
|
583
|
+
G.nodes[label]["label"] = node
|
|
599
584
|
|
|
600
585
|
for attrib in G.nodes[label]:
|
|
601
586
|
if type(G.nodes[label][attrib]) in [list, dict]:
|
|
602
587
|
G.nodes[label][attrib] = str(G.nodes[label][attrib])
|
|
603
588
|
|
|
604
|
-
path = os.path.join(to_disk,
|
|
589
|
+
path = os.path.join(to_disk, "ontology_structure.graphml")
|
|
605
590
|
|
|
606
|
-
logger.info(f
|
|
591
|
+
logger.info(f"Writing ontology structure to {path}.")
|
|
607
592
|
|
|
608
593
|
nx.write_graphml(G, path)
|
|
609
594
|
|
|
@@ -616,10 +601,10 @@ class Ontology:
|
|
|
616
601
|
"""
|
|
617
602
|
|
|
618
603
|
d = {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
604
|
+
"node_id": self._get_current_id(),
|
|
605
|
+
"node_label": "BioCypher",
|
|
606
|
+
"properties": {
|
|
607
|
+
"schema": "self.ontology_mapping.extended_schema",
|
|
623
608
|
},
|
|
624
609
|
}
|
|
625
610
|
|
|
@@ -635,5 +620,4 @@ class Ontology:
|
|
|
635
620
|
"""
|
|
636
621
|
|
|
637
622
|
now = datetime.now()
|
|
638
|
-
return now.strftime(
|
|
639
|
-
|
|
623
|
+
return now.strftime("v%Y%m%d-%H%M%S")
|