vfbquery 0.2.6__py3-none-any.whl → 0.2.8__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.
- test/term_info_queries_test.py +50 -45
- vfbquery/vfb_queries.py +728 -163
- vfbquery-0.2.8.dist-info/METADATA +1168 -0
- vfbquery-0.2.8.dist-info/RECORD +10 -0
- {vfbquery-0.2.6.dist-info → vfbquery-0.2.8.dist-info}/WHEEL +1 -1
- vfbquery-0.2.6.dist-info/METADATA +0 -1512
- vfbquery-0.2.6.dist-info/RECORD +0 -10
- {vfbquery-0.2.6.dist-info → vfbquery-0.2.8.dist-info}/LICENSE +0 -0
- {vfbquery-0.2.6.dist-info → vfbquery-0.2.8.dist-info}/top_level.txt +0 -0
vfbquery/vfb_queries.py
CHANGED
|
@@ -5,6 +5,7 @@ from marshmallow import Schema, fields, post_load
|
|
|
5
5
|
from typing import List, Tuple
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from marshmallow import ValidationError
|
|
8
|
+
import json
|
|
8
9
|
|
|
9
10
|
# Connect to the VFB SOLR server
|
|
10
11
|
vfb_solr = pysolr.Solr('http://solr.virtualflybrain.org/solr/vfb_json/', always_commit=False, timeout=990)
|
|
@@ -13,22 +14,101 @@ vfb_solr = pysolr.Solr('http://solr.virtualflybrain.org/solr/vfb_json/', always_
|
|
|
13
14
|
vc = VfbConnect()
|
|
14
15
|
|
|
15
16
|
class Query:
|
|
16
|
-
def __init__(self, query, label, function, takes):
|
|
17
|
+
def __init__(self, query, label, function, takes, preview=0, preview_columns=[], preview_results=[], output_format="table", count=-1):
|
|
17
18
|
self.query = query
|
|
18
|
-
self.label = label
|
|
19
|
-
self.function = function
|
|
20
|
-
self.takes = takes
|
|
19
|
+
self.label = label
|
|
20
|
+
self.function = function
|
|
21
|
+
self.takes = takes
|
|
22
|
+
self.preview = preview
|
|
23
|
+
self.preview_columns = preview_columns
|
|
24
|
+
self.preview_results = preview_results
|
|
25
|
+
self.output_format = output_format
|
|
26
|
+
self.count = count
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
return f"Query: {self.query}, Label: {self.label}, Function: {self.function}, Takes: {self.takes}, Preview: {self.preview}, Preview Columns: {self.preview_columns}, Preview Results: {self.preview_results}, Count: {self.count}"
|
|
30
|
+
|
|
31
|
+
def to_dict(self):
|
|
32
|
+
return {
|
|
33
|
+
"query": self.query,
|
|
34
|
+
"label": self.label,
|
|
35
|
+
"function": self.function,
|
|
36
|
+
"takes": self.takes,
|
|
37
|
+
"preview": self.preview,
|
|
38
|
+
"preview_columns": self.preview_columns,
|
|
39
|
+
"preview_results": self.preview_results,
|
|
40
|
+
"output_format": self.output_format,
|
|
41
|
+
"count": self.count,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_dict(cls, data):
|
|
46
|
+
return cls(
|
|
47
|
+
query=data["query"],
|
|
48
|
+
label=data["label"],
|
|
49
|
+
function=data["function"],
|
|
50
|
+
takes=data["takes"],
|
|
51
|
+
preview=data["preview"],
|
|
52
|
+
preview_columns=data["preview_columns"],
|
|
53
|
+
preview_results=data["preview_results"],
|
|
54
|
+
output_format=data.get("output_format", 'table'),
|
|
55
|
+
count=data["count"],
|
|
56
|
+
)
|
|
21
57
|
|
|
22
58
|
class TakesSchema(Schema):
|
|
23
59
|
short_form = fields.Raw(required=True)
|
|
24
|
-
default = fields.
|
|
60
|
+
default = fields.Raw(required=False, allow_none=True)
|
|
25
61
|
|
|
26
62
|
class QuerySchema(Schema):
|
|
27
63
|
query = fields.String(required=True)
|
|
28
64
|
label = fields.String(required=True)
|
|
29
65
|
function = fields.String(required=True)
|
|
30
|
-
takes = fields.Nested(TakesSchema(),
|
|
66
|
+
takes = fields.Nested(TakesSchema(), required=False, missing={})
|
|
67
|
+
preview = fields.Integer(required=False, missing=0)
|
|
68
|
+
preview_columns = fields.List(fields.String(), required=False, missing=[])
|
|
69
|
+
preview_results = fields.List(fields.Dict(), required=False, missing=[])
|
|
70
|
+
output_format = fields.String(required=False, missing='table')
|
|
71
|
+
count = fields.Integer(required=False, missing=-1)
|
|
72
|
+
|
|
73
|
+
class License:
|
|
74
|
+
def __init__(self, iri, short_form, label, icon, source, source_iri):
|
|
75
|
+
self.iri = iri
|
|
76
|
+
self.short_form = short_form
|
|
77
|
+
self.label = label
|
|
78
|
+
self.icon = icon
|
|
79
|
+
self.source = source
|
|
80
|
+
self.source_iri = source_iri
|
|
31
81
|
|
|
82
|
+
class LicenseSchema(Schema):
|
|
83
|
+
iri = fields.String(required=True)
|
|
84
|
+
short_form = fields.String(required=True)
|
|
85
|
+
label = fields.String(required=True)
|
|
86
|
+
icon = fields.String(required=True)
|
|
87
|
+
source = fields.String(required=True)
|
|
88
|
+
source_iri = fields.String(required=True)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class LicenseField(fields.Nested):
|
|
92
|
+
def __init__(self, **kwargs):
|
|
93
|
+
super().__init__(LicenseSchema(), **kwargs)
|
|
94
|
+
|
|
95
|
+
def _serialize(self, value, attr, obj, **kwargs):
|
|
96
|
+
if value is None:
|
|
97
|
+
return value
|
|
98
|
+
if not isinstance(value, License):
|
|
99
|
+
raise ValidationError("Invalid input")
|
|
100
|
+
return {"iri": value.iri
|
|
101
|
+
, "short_form": value.short_form
|
|
102
|
+
, "label": value.label
|
|
103
|
+
,"icon": value.icon
|
|
104
|
+
, "source": value.source
|
|
105
|
+
, "source_iri": value.source_iri}
|
|
106
|
+
|
|
107
|
+
def _deserialize(self, value, attr, data, **kwargs):
|
|
108
|
+
if value is None:
|
|
109
|
+
return value
|
|
110
|
+
return LicenseSchema().load(value)
|
|
111
|
+
|
|
32
112
|
class Coordinates:
|
|
33
113
|
def __init__(self, X, Y, Z):
|
|
34
114
|
self.X = X
|
|
@@ -100,7 +180,7 @@ class ImageSchema(Schema):
|
|
|
100
180
|
class ImageField(fields.Nested):
|
|
101
181
|
def __init__(self, **kwargs):
|
|
102
182
|
super().__init__(ImageSchema(), **kwargs)
|
|
103
|
-
|
|
183
|
+
|
|
104
184
|
def _serialize(self, value, attr, obj, **kwargs):
|
|
105
185
|
if value is None:
|
|
106
186
|
return value
|
|
@@ -120,30 +200,22 @@ class ImageField(fields.Nested):
|
|
|
120
200
|
, "type_id": value.type_id
|
|
121
201
|
, "type_label": value.type_label
|
|
122
202
|
}
|
|
123
|
-
|
|
203
|
+
|
|
124
204
|
def _deserialize(self, value, attr, data, **kwargs):
|
|
125
205
|
if value is None:
|
|
126
206
|
return value
|
|
127
207
|
return ImageSchema().load(value)
|
|
128
208
|
|
|
129
|
-
class QueryField(fields.
|
|
130
|
-
def __init__(self, **kwargs):
|
|
131
|
-
super().__init__(QuerySchema, **kwargs)
|
|
209
|
+
class QueryField(fields.Field):
|
|
132
210
|
def _serialize(self, value, attr, obj, **kwargs):
|
|
133
211
|
if value is None:
|
|
134
|
-
return
|
|
135
|
-
return
|
|
136
|
-
, "label": value.label
|
|
137
|
-
, "function": value.function
|
|
138
|
-
, "takes": value.takes
|
|
139
|
-
, "default": value.default
|
|
140
|
-
}
|
|
212
|
+
return None
|
|
213
|
+
return value.to_dict()
|
|
141
214
|
|
|
142
215
|
def _deserialize(self, value, attr, data, **kwargs):
|
|
143
|
-
if value
|
|
144
|
-
|
|
145
|
-
return
|
|
146
|
-
|
|
216
|
+
if not isinstance(value, dict):
|
|
217
|
+
raise ValidationError("Invalid input type.")
|
|
218
|
+
return Query.from_dict(value)
|
|
147
219
|
|
|
148
220
|
class TermInfoOutputSchema(Schema):
|
|
149
221
|
Name = fields.String(required=True)
|
|
@@ -151,14 +223,28 @@ class TermInfoOutputSchema(Schema):
|
|
|
151
223
|
SuperTypes = fields.List(fields.String(), required=True)
|
|
152
224
|
Meta = fields.Dict(keys=fields.String(), values=fields.String(), required=True)
|
|
153
225
|
Tags = fields.List(fields.String(), required=True)
|
|
154
|
-
Queries = fields.
|
|
226
|
+
Queries = fields.List(QueryField(), required=False)
|
|
155
227
|
IsIndividual = fields.Bool(missing=False, required=False)
|
|
156
228
|
Images = fields.Dict(keys=fields.String(), values=fields.List(fields.Nested(ImageSchema()), missing={}), required=False, allow_none=True)
|
|
157
229
|
IsClass = fields.Bool(missing=False, required=False)
|
|
158
230
|
Examples = fields.Dict(keys=fields.String(), values=fields.List(fields.Nested(ImageSchema()), missing={}), required=False, allow_none=True)
|
|
159
231
|
IsTemplate = fields.Bool(missing=False, required=False)
|
|
160
232
|
Domains = fields.Dict(keys=fields.Integer(), values=fields.Nested(ImageSchema()), required=False, allow_none=True)
|
|
233
|
+
Licenses = fields.Dict(keys=fields.Integer(), values=fields.Nested(LicenseSchema()), required=False, allow_none=True)
|
|
234
|
+
|
|
235
|
+
@post_load
|
|
236
|
+
def make_term_info(self, data, **kwargs):
|
|
237
|
+
if "Queries" in data:
|
|
238
|
+
data["Queries"] = [query.to_dict() for query in data["Queries"]]
|
|
239
|
+
return data
|
|
161
240
|
|
|
241
|
+
def __str__(self):
|
|
242
|
+
term_info_data = self.make_term_info(self.data)
|
|
243
|
+
if "Queries" in term_info_data:
|
|
244
|
+
term_info_data["Queries"] = [query.to_dict() for query in term_info_data["Queries"]]
|
|
245
|
+
return str(self.dump(term_info_data))
|
|
246
|
+
|
|
247
|
+
|
|
162
248
|
def term_info_parse_object(results, short_form):
|
|
163
249
|
termInfo = {}
|
|
164
250
|
if results.hits > 0 and results.docs and len(results.docs) > 0:
|
|
@@ -196,6 +282,75 @@ def term_info_parse_object(results, short_form):
|
|
|
196
282
|
pass
|
|
197
283
|
except AttributeError:
|
|
198
284
|
print(f"vfbTerm.term.comment: {vfbTerm.term}")
|
|
285
|
+
|
|
286
|
+
if vfbTerm.parents and len(vfbTerm.parents) > 0:
|
|
287
|
+
parents = []
|
|
288
|
+
|
|
289
|
+
# Sort the parents alphabetically
|
|
290
|
+
sorted_parents = sorted(vfbTerm.parents, key=lambda parent: parent.label)
|
|
291
|
+
|
|
292
|
+
for parent in sorted_parents:
|
|
293
|
+
parents.append("[%s](%s)"%(parent.label, parent.short_form))
|
|
294
|
+
termInfo["Meta"]["Types"] = "; ".join(parents)
|
|
295
|
+
|
|
296
|
+
if vfbTerm.relationships and len(vfbTerm.relationships) > 0:
|
|
297
|
+
relationships = []
|
|
298
|
+
|
|
299
|
+
# Group relationships by relation type and remove duplicates
|
|
300
|
+
grouped_relationships = {}
|
|
301
|
+
for relationship in vfbTerm.relationships:
|
|
302
|
+
if relationship.relation.short_form:
|
|
303
|
+
relation_key = (relationship.relation.label, relationship.relation.short_form)
|
|
304
|
+
elif relationship.relation.iri:
|
|
305
|
+
relation_key = (relationship.relation.label, relationship.relation.iri.split('/')[-1])
|
|
306
|
+
elif relationship.relation.label:
|
|
307
|
+
relation_key = (relationship.relation.label, relationship.relation.label)
|
|
308
|
+
object_key = (relationship.object.label, relationship.object.short_form)
|
|
309
|
+
if relation_key not in grouped_relationships:
|
|
310
|
+
grouped_relationships[relation_key] = set()
|
|
311
|
+
grouped_relationships[relation_key].add(object_key)
|
|
312
|
+
|
|
313
|
+
# Sort the grouped_relationships by keys
|
|
314
|
+
sorted_grouped_relationships = dict(sorted(grouped_relationships.items()))
|
|
315
|
+
|
|
316
|
+
# Append the grouped relationships to termInfo
|
|
317
|
+
for relation_key, object_set in sorted_grouped_relationships.items():
|
|
318
|
+
# Sort the object_set by object_key
|
|
319
|
+
sorted_object_set = sorted(list(object_set))
|
|
320
|
+
relation_objects = []
|
|
321
|
+
for object_key in sorted_object_set:
|
|
322
|
+
relation_objects.append("[%s](%s)" % (object_key[0], object_key[1]))
|
|
323
|
+
relationships.append("[%s](%s): %s" % (relation_key[0], relation_key[1], ', '.join(relation_objects)))
|
|
324
|
+
termInfo["Meta"]["Relationships"] = "; ".join(relationships)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
if vfbTerm.xrefs and len(vfbTerm.xrefs) > 0:
|
|
328
|
+
xrefs = []
|
|
329
|
+
|
|
330
|
+
# Group xrefs by site
|
|
331
|
+
grouped_xrefs = {}
|
|
332
|
+
for xref in vfbTerm.xrefs:
|
|
333
|
+
site_key = (xref.site.label, xref.homepage, xref.icon)
|
|
334
|
+
link_key = (xref.accession, xref.link())
|
|
335
|
+
if site_key not in grouped_xrefs:
|
|
336
|
+
grouped_xrefs[site_key] = set()
|
|
337
|
+
grouped_xrefs[site_key].add(link_key)
|
|
338
|
+
|
|
339
|
+
# Sort the grouped_xrefs by site_key
|
|
340
|
+
sorted_grouped_xrefs = dict(sorted(grouped_xrefs.items()))
|
|
341
|
+
|
|
342
|
+
# Append the grouped xrefs to termInfo
|
|
343
|
+
for site_key, link_set in sorted_grouped_xrefs.items():
|
|
344
|
+
# Sort the link_set by link_key
|
|
345
|
+
sorted_link_set = sorted(list(link_set))
|
|
346
|
+
links = []
|
|
347
|
+
for link_key in sorted_link_set:
|
|
348
|
+
links.append("[%s](%s)" % (link_key[0], link_key[1]))
|
|
349
|
+
if site_key[2]:
|
|
350
|
+
xrefs.append(" [%s](%s): %s" % (site_key[0], site_key[2], site_key[0], site_key[1], ', '.join(links)))
|
|
351
|
+
else:
|
|
352
|
+
xrefs.append("[%s](%s): %s" % (site_key[0], site_key[1], ', '.join(links)))
|
|
353
|
+
termInfo["Meta"]["Cross References"] = "; ".join(xrefs)
|
|
199
354
|
|
|
200
355
|
# If the term has anatomy channel images, retrieve the images and associated information
|
|
201
356
|
if vfbTerm.anatomy_channel_image and len(vfbTerm.anatomy_channel_image) > 0:
|
|
@@ -217,9 +372,9 @@ def term_info_parse_object(results, short_form):
|
|
|
217
372
|
images[image.channel_image.image.template_anatomy.short_form].append(record)
|
|
218
373
|
termInfo["Examples"] = images
|
|
219
374
|
# add a query to `queries` list for listing all available images
|
|
220
|
-
q =
|
|
375
|
+
q = ListAllAvailableImages_to_schema(termInfo["Name"], {"short_form":vfbTerm.term.core.short_form})
|
|
221
376
|
queries.append(q)
|
|
222
|
-
|
|
377
|
+
|
|
223
378
|
# If the term has channel images but not anatomy channel images, create thumbnails from channel images.
|
|
224
379
|
if vfbTerm.channel_image and len(vfbTerm.channel_image) > 0:
|
|
225
380
|
images = {}
|
|
@@ -240,6 +395,19 @@ def term_info_parse_object(results, short_form):
|
|
|
240
395
|
images[image.image.template_anatomy.short_form].append(record)
|
|
241
396
|
# Add the thumbnails to the term info
|
|
242
397
|
termInfo["Images"] = images
|
|
398
|
+
|
|
399
|
+
if vfbTerm.dataset_license and len(vfbTerm.dataset_license) > 0:
|
|
400
|
+
licenses = {}
|
|
401
|
+
for idx, dataset_license in enumerate(vfbTerm.dataset_license):
|
|
402
|
+
record = {}
|
|
403
|
+
record['iri'] = dataset_license.license.core.iri
|
|
404
|
+
record['short_form'] = dataset_license.license.core.short_form
|
|
405
|
+
record['label'] = dataset_license.license.core.label
|
|
406
|
+
record['icon'] = dataset_license.license.icon
|
|
407
|
+
record['source_iri'] = dataset_license.dataset.core.iri
|
|
408
|
+
record['source'] = dataset_license.dataset.core.label
|
|
409
|
+
licenses[idx] = record
|
|
410
|
+
termInfo["Licenses"] = licenses
|
|
243
411
|
|
|
244
412
|
if vfbTerm.template_channel and vfbTerm.template_channel.channel.short_form:
|
|
245
413
|
termInfo["IsTemplate"] = True
|
|
@@ -271,195 +439,544 @@ def term_info_parse_object(results, short_form):
|
|
|
271
439
|
if 'orientation' in image_vars.keys():
|
|
272
440
|
record['orientation'] = image.orientation
|
|
273
441
|
images[vfbTerm.template_channel.channel.short_form].append(record)
|
|
274
|
-
|
|
442
|
+
|
|
275
443
|
# Add the thumbnails to the term info
|
|
276
444
|
termInfo["Images"] = images
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
445
|
+
|
|
446
|
+
if vfbTerm.template_domains and len(vfbTerm.template_domains) > 0:
|
|
447
|
+
images = {}
|
|
448
|
+
termInfo["IsTemplate"] = True
|
|
449
|
+
for image in vfbTerm.template_domains:
|
|
450
|
+
record = {}
|
|
451
|
+
record["id"] = image.anatomical_individual.short_form
|
|
452
|
+
label = image.anatomical_individual.label
|
|
453
|
+
if image.anatomical_individual.symbol != "" and len(image.anatomical_individual.symbol) > 0:
|
|
454
|
+
label = image.anatomical_individual.symbol
|
|
455
|
+
record["label"] = label
|
|
456
|
+
record["type_id"] = image.anatomical_type.short_form
|
|
457
|
+
label = image.anatomical_type.label
|
|
458
|
+
if image.anatomical_type.symbol != "" and len(image.anatomical_type.symbol) > 0:
|
|
459
|
+
label = image.anatomical_type.symbol
|
|
460
|
+
record["type_label"] = label
|
|
461
|
+
record["index"] = int(image.index[0])
|
|
462
|
+
record["thumbnail"] = image.folder.replace("http://", "https://") + "thumbnail.png"
|
|
463
|
+
record["thumbnail_transparent"] = image.folder.replace("http://", "https://") + "thumbnailT.png"
|
|
464
|
+
for key in vars(image).keys():
|
|
465
|
+
if "image_" in key and not ("thumbnail" in key or "folder" in key) and len(vars(image)[key]) > 1:
|
|
466
|
+
record[key.replace("image_", "")] = vars(image)[key].replace("http://", "https://")
|
|
467
|
+
record["center"] = image.get_center()
|
|
468
|
+
images[record["index"]] = record
|
|
469
|
+
|
|
470
|
+
# Sort the domains by their index and add them to the term info
|
|
471
|
+
sorted_images = {int(key): value for key, value in sorted(images.items(), key=lambda x: x[0])}
|
|
472
|
+
termInfo["Domains"] = sorted_images
|
|
473
|
+
|
|
474
|
+
if contains_all_tags(termInfo["SuperTypes"], ["Individual", "Neuron"]):
|
|
475
|
+
q = SimilarMorphologyTo_to_schema(termInfo["Name"], {"neuron": vfbTerm.term.core.short_form, "similarity_score": "NBLAST_score"})
|
|
476
|
+
queries.append(q)
|
|
477
|
+
if contains_all_tags(termInfo["SuperTypes"], ["Individual", "Neuron", "has_neuron_connectivity"]):
|
|
478
|
+
q = NeuronInputsTo_to_schema(termInfo["Name"], {"neuron_short_form": vfbTerm.term.core.short_form})
|
|
479
|
+
queries.append(q)
|
|
308
480
|
# Add the queries to the term info
|
|
309
481
|
termInfo["Queries"] = queries
|
|
310
|
-
|
|
311
|
-
print(termInfo)
|
|
312
|
-
|
|
482
|
+
|
|
483
|
+
# print("termInfo object after loading:", termInfo)
|
|
484
|
+
if "Queries" in termInfo:
|
|
485
|
+
termInfo["Queries"] = [query.to_dict() for query in termInfo["Queries"]]
|
|
486
|
+
# print("termInfo object before schema validation:", termInfo)
|
|
313
487
|
return TermInfoOutputSchema().load(termInfo)
|
|
314
488
|
|
|
315
|
-
def
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
489
|
+
def NeuronInputsTo_to_schema(name, take_default):
|
|
490
|
+
query = "NeuronInputsTo"
|
|
491
|
+
label = f"Find neurons with synapses into {name}"
|
|
492
|
+
function = "get_individual_neuron_inputs"
|
|
493
|
+
takes = {
|
|
494
|
+
"neuron_short_form": {"$and": ["Individual", "Neuron"]},
|
|
495
|
+
"default": take_default,
|
|
496
|
+
}
|
|
497
|
+
preview = -1
|
|
498
|
+
preview_columns = ["Neurotransmitter", "Weight"]
|
|
499
|
+
output_format = "ribbon"
|
|
500
|
+
|
|
501
|
+
return Query(query=query, label=label, function=function, takes=takes, preview=preview, preview_columns=preview_columns, output_format=output_format)
|
|
502
|
+
|
|
503
|
+
def SimilarMorphologyTo_to_schema(name, take_default):
|
|
504
|
+
query = "SimilarMorphologyTo"
|
|
505
|
+
label = f"Find similar neurons to {name}"
|
|
506
|
+
function = "get_similar_neurons"
|
|
507
|
+
takes = {
|
|
508
|
+
"short_form": {"$and": ["Individual", "Neuron"]},
|
|
509
|
+
"default": take_default,
|
|
510
|
+
}
|
|
511
|
+
preview = 5
|
|
512
|
+
preview_columns = ["id","score","name","tags","thumbnail"]
|
|
513
|
+
|
|
514
|
+
return Query(query=query, label=label, function=function, takes=takes, preview=preview, preview_columns=preview_columns)
|
|
515
|
+
|
|
516
|
+
def ListAllAvailableImages_to_schema(name, take_default):
|
|
517
|
+
query = "ListAllAvailableImages"
|
|
518
|
+
label = f"List all available images of {name}"
|
|
519
|
+
function = "get_instances"
|
|
520
|
+
takes = {
|
|
521
|
+
"short_form": {"$and": ["Class", "Anatomy"]},
|
|
522
|
+
"default": take_default,
|
|
523
|
+
}
|
|
524
|
+
preview = 0
|
|
525
|
+
preview_columns = ["id","label","tags","thumbnail"]
|
|
526
|
+
|
|
527
|
+
return Query(query=query, label=label, function=function, takes=takes, preview=preview, preview_columns=preview_columns)
|
|
528
|
+
|
|
529
|
+
def serialize_solr_output(results):
|
|
530
|
+
# Serialize the sanitized dictionary to JSON
|
|
531
|
+
json_string = json.dumps(results.docs[0], ensure_ascii=False)
|
|
532
|
+
json_string = json_string.replace('\\', '')
|
|
533
|
+
json_string = json_string.replace('"{', '{')
|
|
534
|
+
json_string = json_string.replace('}"', '}')
|
|
535
|
+
json_string = json_string.replace("\'", '-')
|
|
536
|
+
return json_string
|
|
537
|
+
|
|
538
|
+
def get_term_info(short_form: str, preview: bool = False):
|
|
340
539
|
"""
|
|
341
540
|
Retrieves the term info for the given term short form.
|
|
342
541
|
|
|
343
542
|
:param short_form: short form of the term
|
|
344
543
|
:return: term info
|
|
345
544
|
"""
|
|
545
|
+
parsed_object = None
|
|
346
546
|
try:
|
|
347
547
|
# Search for the term in the SOLR server
|
|
348
548
|
results = vfb_solr.search('id:' + short_form)
|
|
549
|
+
sanitized_results = serialize_solr_output(results)
|
|
550
|
+
print(sanitized_results)
|
|
349
551
|
# Check if any results were returned
|
|
350
552
|
parsed_object = term_info_parse_object(results, short_form)
|
|
553
|
+
term_info = fill_query_results(parsed_object)
|
|
554
|
+
if not term_info:
|
|
555
|
+
print("Failed to fill query preview results!")
|
|
556
|
+
return term_info
|
|
351
557
|
return parsed_object
|
|
352
558
|
except ValidationError as e:
|
|
353
|
-
|
|
354
|
-
|
|
559
|
+
# handle the validation error
|
|
560
|
+
print("Schema validation error when parsing response")
|
|
561
|
+
print("Error details:", e)
|
|
562
|
+
print("Original data:", results)
|
|
563
|
+
print("Parsed object:", parsed_object)
|
|
355
564
|
except IndexError:
|
|
356
565
|
print(f"No results found for ID '{short_form}'")
|
|
357
|
-
print("Error accessing SOLR server!")
|
|
566
|
+
print("Error accessing SOLR server!")
|
|
358
567
|
|
|
359
|
-
|
|
360
|
-
def get_instances(short_form: str):
|
|
568
|
+
def get_instances(short_form: str, return_dataframe=True, limit: int = -1):
|
|
361
569
|
"""
|
|
362
570
|
Retrieves available instances for the given class short form.
|
|
363
571
|
:param short_form: short form of the class
|
|
572
|
+
:param limit: maximum number of results to return (default -1, returns all results)
|
|
364
573
|
:return: results rows
|
|
365
574
|
"""
|
|
366
575
|
|
|
367
|
-
#
|
|
576
|
+
# Get the total count of rows
|
|
577
|
+
count_query = f"""
|
|
578
|
+
MATCH (i:Individual:has_image)-[:INSTANCEOF]->(p:Class {{ short_form: '{short_form}' }}),
|
|
579
|
+
(i)<-[:depicts]-(:Individual)-[r:in_register_with]->(:Template)
|
|
580
|
+
RETURN COUNT(r) AS total_count
|
|
581
|
+
"""
|
|
582
|
+
count_results = vc.nc.commit_list([count_query])
|
|
583
|
+
count_df = pd.DataFrame.from_records(dict_cursor(count_results))
|
|
584
|
+
total_count = count_df['total_count'][0] if not count_df.empty else 0
|
|
585
|
+
|
|
586
|
+
# Define the main Cypher query
|
|
368
587
|
query = f"""
|
|
369
|
-
MATCH (i:Individual)-[:INSTANCEOF]->(p:Class {{ short_form: '{short_form}' }}),
|
|
370
|
-
(i)<-[:depicts]-(:Individual)-[:in_register_with]->(:Template)-[:depicts]->(templ:Template),
|
|
588
|
+
MATCH (i:Individual:has_image)-[:INSTANCEOF]->(p:Class {{ short_form: '{short_form}' }}),
|
|
589
|
+
(i)<-[:depicts]-(:Individual)-[r:in_register_with]->(:Template)-[:depicts]->(templ:Template),
|
|
371
590
|
(i)-[:has_source]->(ds:DataSet)
|
|
372
591
|
OPTIONAL MATCH (i)-[rx:database_cross_reference]->(site:Site)
|
|
373
592
|
OPTIONAL MATCH (ds)-[:license|licence]->(lic:License)
|
|
374
|
-
RETURN
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
593
|
+
RETURN i.short_form as id,
|
|
594
|
+
apoc.text.format("[%s](%s)",[COALESCE(i.symbol[0],i.label),i.short_form]) AS label,
|
|
595
|
+
apoc.text.join(i.uniqueFacets, '|') AS tags,
|
|
596
|
+
apoc.text.format("[%s](%s)",[COALESCE(p.symbol[0],p.label),p.short_form]) AS parent,
|
|
597
|
+
REPLACE(apoc.text.format("[%s](%s)",[COALESCE(site.symbol[0],site.label),site.short_form]), '[null](null)', '') AS source,
|
|
598
|
+
REPLACE(apoc.text.format("[%s](%s)",[rx.accession[0],site.link_base[0] + rx.accession[0]]), '[null](null)', '') AS source_id,
|
|
599
|
+
apoc.text.format("[%s](%s)",[COALESCE(templ.symbol[0],templ.label),templ.short_form]) AS template,
|
|
600
|
+
apoc.text.format("[%s](%s)",[COALESCE(ds.symbol[0],ds.label),ds.short_form]) AS dataset,
|
|
601
|
+
REPLACE(apoc.text.format("[%s](%s)",[COALESCE(lic.symbol[0],lic.label),lic.short_form]), '[null](null)', '') AS license,
|
|
602
|
+
REPLACE(apoc.text.format("[](%s)",[COALESCE(i.symbol[0],i.label) + " aligned to " + COALESCE(templ.symbol[0],templ.label), REPLACE(COALESCE(r.thumbnail[0],""),"thumbnailT.png","thumbnail.png"), COALESCE(i.symbol[0],i.label) + " aligned to " + COALESCE(templ.symbol[0],templ.label), templ.short_form + "," + i.short_form]), "[](null)", "") as thumbnail
|
|
603
|
+
ORDER BY id Desc
|
|
382
604
|
"""
|
|
383
605
|
|
|
606
|
+
if limit != -1:
|
|
607
|
+
query += f" LIMIT {limit}"
|
|
608
|
+
|
|
384
609
|
# Run the query using VFB_connect
|
|
385
610
|
results = vc.nc.commit_list([query])
|
|
386
611
|
|
|
387
612
|
# Convert the results to a DataFrame
|
|
388
613
|
df = pd.DataFrame.from_records(dict_cursor(results))
|
|
389
614
|
|
|
615
|
+
if return_dataframe:
|
|
616
|
+
return df
|
|
617
|
+
|
|
390
618
|
# Format the results
|
|
391
619
|
formatted_results = {
|
|
392
620
|
"headers": {
|
|
621
|
+
"id": {"title": "Add", "type": "selection_id", "order": -1},
|
|
393
622
|
"label": {"title": "Name", "type": "markdown", "order": 0, "sort": {0: "Asc"}},
|
|
394
623
|
"parent": {"title": "Parent Type", "type": "markdown", "order": 1},
|
|
395
624
|
"template": {"title": "Template", "type": "markdown", "order": 4},
|
|
396
625
|
"tags": {"title": "Gross Types", "type": "tags", "order": 3},
|
|
397
626
|
"source": {"title": "Data Source", "type": "markdown", "order": 5},
|
|
398
627
|
"source_id": {"title": "Data Source", "type": "markdown", "order": 6},
|
|
628
|
+
"dataset": {"title": "Dataset", "type": "markdown", "order": 7},
|
|
629
|
+
"license": {"title": "License", "type": "markdown", "order": 8},
|
|
630
|
+
"thumbnail": {"title": "Thumbnail", "type": "markdown", "order": 9}
|
|
399
631
|
},
|
|
400
|
-
"rows":
|
|
632
|
+
"rows": [
|
|
633
|
+
{
|
|
634
|
+
key: row[key]
|
|
635
|
+
for key in [
|
|
636
|
+
"id",
|
|
637
|
+
"label",
|
|
638
|
+
"tags",
|
|
639
|
+
"parent",
|
|
640
|
+
"source",
|
|
641
|
+
"source_id",
|
|
642
|
+
"template",
|
|
643
|
+
"dataset",
|
|
644
|
+
"license",
|
|
645
|
+
"thumbnail"
|
|
646
|
+
]
|
|
647
|
+
}
|
|
648
|
+
for row in df.to_dict("records")
|
|
649
|
+
],
|
|
650
|
+
"count": total_count
|
|
401
651
|
}
|
|
402
652
|
|
|
403
653
|
return formatted_results
|
|
404
|
-
|
|
405
|
-
def
|
|
654
|
+
|
|
655
|
+
def get_templates(limit: int = -1, return_dataframe: bool = False):
|
|
656
|
+
"""Get list of templates
|
|
657
|
+
|
|
658
|
+
:param limit: maximum number of results to return (default -1, returns all results)
|
|
659
|
+
:param return_dataframe: Returns pandas dataframe if true, otherwise returns list of dicts.
|
|
660
|
+
:return: list of templates (id, label, tags, source (db) id, accession_in_source) + similarity score.
|
|
661
|
+
:rtype: pandas.DataFrame or list of dicts
|
|
662
|
+
|
|
406
663
|
"""
|
|
407
|
-
|
|
664
|
+
count_query = """MATCH (t:Template)<-[:depicts]-(tc:Template)-[r:in_register_with]->(tc:Template)
|
|
665
|
+
RETURN COUNT(DISTINCT t) AS total_count"""
|
|
408
666
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
667
|
+
count_results = vc.nc.commit_list([count_query])
|
|
668
|
+
count_df = pd.DataFrame.from_records(dict_cursor(count_results))
|
|
669
|
+
total_count = count_df['total_count'][0] if not count_df.empty else 0
|
|
670
|
+
|
|
671
|
+
# Define the main Cypher query
|
|
672
|
+
query = f"""
|
|
673
|
+
MATCH (t:Template)-[:INSTANCEOF]->(p:Class),
|
|
674
|
+
(t)<-[:depicts]-(tc:Template)-[r:in_register_with]->(tc:Template),
|
|
675
|
+
(t)-[:has_source]->(ds:DataSet)-[:has_license]->(lic:License)
|
|
676
|
+
RETURN t.short_form as id,
|
|
677
|
+
apoc.text.format("[%s](%s)",[COALESCE(t.symbol[0],t.label),t.short_form]) AS name,
|
|
678
|
+
apoc.text.join(t.uniqueFacets, '|') AS tags,
|
|
679
|
+
apoc.text.format("[%s](%s)",[COALESCE(ds.symbol[0],ds.label),ds.short_form]) AS dataset,
|
|
680
|
+
REPLACE(apoc.text.format("[%s](%s)",[COALESCE(lic.symbol[0],lic.label),lic.short_form]), '[null](null)', '') AS license,
|
|
681
|
+
REPLACE(apoc.text.format("[](%s)",[COALESCE(t.symbol[0],t.label), REPLACE(COALESCE(r.thumbnail[0],""),"thumbnailT.png","thumbnail.png"), COALESCE(t.symbol[0],t.label), t.short_form]), "[](null)", "") as thumbnail,
|
|
682
|
+
99 as order
|
|
683
|
+
ORDER BY id Desc
|
|
412
684
|
"""
|
|
413
685
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
686
|
+
if limit != -1:
|
|
687
|
+
query += f" LIMIT {limit}"
|
|
688
|
+
|
|
689
|
+
# Run the query using VFB_connect
|
|
690
|
+
results = vc.nc.commit_list([query])
|
|
691
|
+
|
|
692
|
+
# Convert the results to a DataFrame
|
|
693
|
+
df = pd.DataFrame.from_records(dict_cursor(results))
|
|
694
|
+
|
|
695
|
+
template_order = ["VFB_00101567","VFB_00200000","VFB_00017894","VFB_00101384","VFB_00050000","VFB_00049000","VFB_00100000","VFB_00030786","VFB_00110000","VFB_00120000"]
|
|
696
|
+
|
|
697
|
+
order = 1
|
|
698
|
+
|
|
699
|
+
for template in template_order:
|
|
700
|
+
df.loc[df['id'] == template, 'order'] = order
|
|
701
|
+
order += 1
|
|
702
|
+
|
|
703
|
+
# Sort the DataFrame by 'order'
|
|
704
|
+
df = df.sort_values('order')
|
|
705
|
+
|
|
706
|
+
if return_dataframe:
|
|
707
|
+
return df
|
|
708
|
+
|
|
709
|
+
# Format the results
|
|
710
|
+
formatted_results = {
|
|
711
|
+
"headers": {
|
|
712
|
+
"id": {"title": "Add", "type": "selection_id", "order": -1},
|
|
713
|
+
"order": {"title": "Order", "type": "numeric", "order": 1, "sort": {0: "Asc"}},
|
|
714
|
+
"name": {"title": "Name", "type": "markdown", "order": 1, "sort": {1: "Asc"}},
|
|
715
|
+
"tags": {"title": "Tags", "type": "tags", "order": 2},
|
|
716
|
+
"thumbnail": {"title": "Thumbnail", "type": "markdown", "order": 9},
|
|
717
|
+
"dataset": {"title": "Dataset", "type": "metadata", "order": 3},
|
|
718
|
+
"license": {"title": "License", "type": "metadata", "order": 4}
|
|
719
|
+
},
|
|
720
|
+
"rows": [
|
|
721
|
+
{
|
|
722
|
+
key: row[key]
|
|
723
|
+
for key in [
|
|
724
|
+
"id",
|
|
725
|
+
"order",
|
|
726
|
+
"name",
|
|
727
|
+
"tags",
|
|
728
|
+
"thumbnail",
|
|
729
|
+
"dataset",
|
|
730
|
+
"license"
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
for row in df.to_dict("records")
|
|
734
|
+
],
|
|
735
|
+
"count": total_count
|
|
736
|
+
}
|
|
737
|
+
return formatted_results
|
|
738
|
+
|
|
739
|
+
def get_related_anatomy(template_short_form: str, limit: int = -1, return_dataframe: bool = False):
|
|
740
|
+
"""
|
|
741
|
+
Retrieve related anatomical structures for a given template.
|
|
742
|
+
|
|
743
|
+
:param template_short_form: The short form of the template to query.
|
|
744
|
+
:param limit: Maximum number of results to return. Default is -1, which returns all results.
|
|
745
|
+
:param return_dataframe: If True, returns results as a pandas DataFrame. Otherwise, returns a list of dicts.
|
|
746
|
+
:return: Related anatomical structures and paths.
|
|
747
|
+
"""
|
|
748
|
+
|
|
749
|
+
# Define the Cypher query
|
|
750
|
+
query = f"""
|
|
751
|
+
MATCH (root:Class)<-[:INSTANCEOF]-(t:Template {{short_form:'{template_short_form}'}})<-[:depicts]-(tc:Template)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:Individual)-[r:INSTANCEOF]->(anat:Class:Anatomy)
|
|
752
|
+
WHERE exists(ie.index)
|
|
753
|
+
WITH root, anat,r,image
|
|
754
|
+
MATCH p=allshortestpaths((root)<-[:SUBCLASSOF|part_of*..50]-(anat))
|
|
755
|
+
UNWIND nodes(p) as n
|
|
756
|
+
UNWIND nodes(p) as m
|
|
757
|
+
WITH * WHERE id(n) < id(m)
|
|
758
|
+
MATCH path = allShortestPaths( (n)-[:SUBCLASSOF|part_of*..1]-(m) )
|
|
759
|
+
RETURN collect(distinct {{ node_id: id(anat), short_form: anat.short_form, image: image.short_form }}) AS image_nodes, id(root) AS root, collect(path)
|
|
760
|
+
"""
|
|
761
|
+
|
|
762
|
+
if limit != -1:
|
|
763
|
+
query += f" LIMIT {limit}"
|
|
764
|
+
|
|
765
|
+
# Execute the query using your database connection (e.g., VFB_connect)
|
|
766
|
+
results = vc.nc.commit_list([query])
|
|
767
|
+
|
|
768
|
+
# Convert the results to a DataFrame (if needed)
|
|
769
|
+
if return_dataframe:
|
|
770
|
+
df = pd.DataFrame.from_records(results)
|
|
771
|
+
return df
|
|
772
|
+
|
|
773
|
+
# Otherwise, return the raw results
|
|
427
774
|
return results
|
|
428
775
|
|
|
429
|
-
def
|
|
776
|
+
def get_similar_neurons(neuron, similarity_score='NBLAST_score', return_dataframe=True, limit: int = -1):
|
|
777
|
+
"""Get JSON report of individual neurons similar to input neuron
|
|
778
|
+
|
|
779
|
+
:param neuron:
|
|
780
|
+
:param similarity_score: Optionally specify similarity score to chose
|
|
781
|
+
:param return_dataframe: Returns pandas dataframe if true, otherwise returns list of dicts.
|
|
782
|
+
:param limit: maximum number of results to return (default -1, returns all results)
|
|
783
|
+
:return: list of similar neurons (id, label, tags, source (db) id, accession_in_source) + similarity score.
|
|
784
|
+
:rtype: pandas.DataFrame or list of dicts
|
|
785
|
+
|
|
430
786
|
"""
|
|
431
|
-
|
|
787
|
+
count_query = f"""MATCH (c1:Class)<-[:INSTANCEOF]-(n1)-[r:has_similar_morphology_to]-(n2)-[:INSTANCEOF]->(c2:Class)
|
|
788
|
+
WHERE n1.short_form = '{neuron}' and exists(r.{similarity_score})
|
|
789
|
+
RETURN COUNT(DISTINCT n2) AS total_count"""
|
|
790
|
+
|
|
791
|
+
count_results = vc.nc.commit_list([count_query])
|
|
792
|
+
count_df = pd.DataFrame.from_records(dict_cursor(count_results))
|
|
793
|
+
total_count = count_df['total_count'][0] if not count_df.empty else 0
|
|
794
|
+
|
|
795
|
+
main_query = f"""MATCH (c1:Class)<-[:INSTANCEOF]-(n1)-[r:has_similar_morphology_to]-(n2)-[:INSTANCEOF]->(c2:Class)
|
|
796
|
+
WHERE n1.short_form = '{neuron}' and exists(r.{similarity_score})
|
|
797
|
+
WITH c1, n1, r, n2, c2
|
|
798
|
+
OPTIONAL MATCH (n2)-[rx:database_cross_reference]->(site:Site)
|
|
799
|
+
WHERE site.is_data_source
|
|
800
|
+
WITH n2, r, c2, rx, site
|
|
801
|
+
OPTIONAL MATCH (n2)<-[:depicts]-(:Individual)-[ri:in_register_with]->(:Template)-[:depicts]->(templ:Template)
|
|
802
|
+
RETURN DISTINCT n2.short_form as id,
|
|
803
|
+
apoc.text.format("[%s](%s)", [n2.label, n2.short_form]) AS name,
|
|
804
|
+
r.{similarity_score}[0] AS score,
|
|
805
|
+
apoc.text.join(n2.uniqueFacets, '|') AS tags,
|
|
806
|
+
REPLACE(apoc.text.format("[%s](%s)",[COALESCE(site.symbol[0],site.label),site.short_form]), '[null](null)', '') AS source,
|
|
807
|
+
REPLACE(apoc.text.format("[%s](%s)",[rx.accession[0], (site.link_base[0] + rx.accession[0])]), '[null](null)', '') AS source_id,
|
|
808
|
+
REPLACE(apoc.text.format("[](%s)",[COALESCE(n2.symbol[0],n2.label) + " aligned to " + COALESCE(templ.symbol[0],templ.label), REPLACE(COALESCE(ri.thumbnail[0],""),"thumbnailT.png","thumbnail.png"), COALESCE(n2.symbol[0],n2.label) + " aligned to " + COALESCE(templ.symbol[0],templ.label), templ.short_form + "," + n2.short_form]), "[](null)", "") as thumbnail
|
|
809
|
+
ORDER BY score DESC"""
|
|
810
|
+
|
|
811
|
+
if limit != -1:
|
|
812
|
+
main_query += f" LIMIT {limit}"
|
|
813
|
+
|
|
814
|
+
# Run the query using VFB_connect
|
|
815
|
+
results = vc.nc.commit_list([main_query])
|
|
432
816
|
|
|
433
|
-
|
|
434
|
-
|
|
817
|
+
# Convert the results to a DataFrame
|
|
818
|
+
df = pd.DataFrame.from_records(dict_cursor(results))
|
|
819
|
+
|
|
820
|
+
if return_dataframe:
|
|
821
|
+
return df
|
|
822
|
+
else:
|
|
823
|
+
formatted_results = {
|
|
824
|
+
"headers": {
|
|
825
|
+
"id": {"title": "Add", "type": "selection_id", "order": -1},
|
|
826
|
+
"score": {"title": "Score", "type": "numeric", "order": 1, "sort": {0: "Desc"}},
|
|
827
|
+
"name": {"title": "Name", "type": "markdown", "order": 1, "sort": {1: "Asc"}},
|
|
828
|
+
"tags": {"title": "Tags", "type": "tags", "order": 2},
|
|
829
|
+
"source": {"title": "Source", "type": "metadata", "order": 3},
|
|
830
|
+
"source_id": {"title": "Source ID", "type": "metadata", "order": 4},
|
|
831
|
+
"thumbnail": {"title": "Thumbnail", "type": "markdown", "order": 9}
|
|
832
|
+
},
|
|
833
|
+
"rows": [
|
|
834
|
+
{
|
|
835
|
+
key: row[key]
|
|
836
|
+
for key in [
|
|
837
|
+
"id",
|
|
838
|
+
"name",
|
|
839
|
+
"score",
|
|
840
|
+
"tags",
|
|
841
|
+
"source",
|
|
842
|
+
"source_id",
|
|
843
|
+
"thumbnail"
|
|
844
|
+
]
|
|
845
|
+
}
|
|
846
|
+
for row in df.to_dict("records")
|
|
847
|
+
],
|
|
848
|
+
"count": total_count
|
|
849
|
+
}
|
|
850
|
+
return formatted_results
|
|
851
|
+
|
|
852
|
+
def get_individual_neuron_inputs(neuron_short_form: str, return_dataframe=True, limit: int = -1, summary_mode: bool = False):
|
|
435
853
|
"""
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
854
|
+
Retrieve neurons that have synapses into the specified neuron, along with the neurotransmitter
|
|
855
|
+
types, and additional information about the neurons.
|
|
856
|
+
|
|
857
|
+
:param neuron_short_form: The short form identifier of the neuron to query.
|
|
858
|
+
:param return_dataframe: If True, returns results as a pandas DataFrame. Otherwise, returns a dictionary.
|
|
859
|
+
:param limit: Maximum number of results to return. Default is -1, which returns all results.
|
|
860
|
+
:param summary_mode: If True, returns a preview of the results with summed weights for each neurotransmitter type.
|
|
861
|
+
:return: Neurons, neurotransmitter types, and additional neuron information.
|
|
862
|
+
"""
|
|
863
|
+
|
|
864
|
+
# Define the common part of the Cypher query
|
|
865
|
+
query_common = f"""
|
|
866
|
+
MATCH (a:has_neuron_connectivity {{short_form:'{neuron_short_form}'}})<-[r:synapsed_to]-(b:has_neuron_connectivity)
|
|
867
|
+
UNWIND(labels(b)) as l
|
|
868
|
+
WITH * WHERE l contains "ergic"
|
|
869
|
+
OPTIONAL MATCH (c:Class:Neuron) WHERE c.short_form starts with "FBbt_" AND toLower(c.label)=toLower(l+" neuron")
|
|
870
|
+
"""
|
|
871
|
+
if not summary_mode:
|
|
872
|
+
count_query = f"""{query_common}
|
|
873
|
+
RETURN COUNT(DISTINCT b) AS total_count"""
|
|
874
|
+
else:
|
|
875
|
+
count_query = f"""{query_common}
|
|
876
|
+
RETURN COUNT(DISTINCT c) AS total_count"""
|
|
877
|
+
|
|
878
|
+
count_results = vc.nc.commit_list([count_query])
|
|
879
|
+
count_df = pd.DataFrame.from_records(dict_cursor(count_results))
|
|
880
|
+
total_count = count_df['total_count'][0] if not count_df.empty else 0
|
|
881
|
+
|
|
882
|
+
# Define the part of the query for normal mode
|
|
883
|
+
query_normal = f"""
|
|
884
|
+
OPTIONAL MATCH (b)-[:INSTANCEOF]->(neuronType:Class),
|
|
885
|
+
(b)<-[:depicts]-(imageChannel:Individual)-[image:in_register_with]->(templateChannel:Template)-[:depicts]->(templ:Template),
|
|
886
|
+
(imageChannel)-[:is_specified_output_of]->(imagingTechnique:Class)
|
|
887
|
+
RETURN
|
|
888
|
+
b.short_form as id,
|
|
889
|
+
apoc.text.format("[%s](%s)", [l, c.short_form]) as Neurotransmitter,
|
|
890
|
+
sum(r.weight[0]) as Weight,
|
|
891
|
+
apoc.text.format("[%s](%s)", [b.label, b.short_form]) as Name,
|
|
892
|
+
apoc.text.format("[%s](%s)", [neuronType.label, neuronType.short_form]) as Type,
|
|
893
|
+
apoc.text.join(b.uniqueFacets, '|') as Gross_Type,
|
|
894
|
+
apoc.text.join(collect(apoc.text.format("[%s](%s)", [templ.label, templ.short_form])), ', ') as Template_Space,
|
|
895
|
+
apoc.text.format("[%s](%s)", [imagingTechnique.label, imagingTechnique.short_form]) as Imaging_Technique,
|
|
896
|
+
apoc.text.join(collect(REPLACE(apoc.text.format("[](%s)",[COALESCE(b.symbol[0],b.label), REPLACE(COALESCE(image.thumbnail[0],""),"thumbnailT.png","thumbnail.png"), COALESCE(b.symbol[0],b.label), b.short_form]), "[](null)", "")), ' | ') as Images
|
|
897
|
+
ORDER BY Weight Desc
|
|
898
|
+
"""
|
|
899
|
+
|
|
900
|
+
# Define the part of the query for preview mode
|
|
901
|
+
query_preview = f"""
|
|
902
|
+
RETURN DISTINCT c.short_form as id,
|
|
903
|
+
apoc.text.format("[%s](%s)", [l, c.short_form]) as Neurotransmitter,
|
|
904
|
+
sum(r.weight[0]) as Weight
|
|
905
|
+
ORDER BY Weight Desc
|
|
906
|
+
"""
|
|
907
|
+
|
|
908
|
+
# Choose the appropriate part of the query based on the summary_mode parameter
|
|
909
|
+
query = query_common + (query_preview if summary_mode else query_normal)
|
|
910
|
+
|
|
911
|
+
if limit != -1 and not summary_mode:
|
|
912
|
+
query += f" LIMIT {limit}"
|
|
913
|
+
|
|
914
|
+
# Execute the query using your database connection (e.g., vc.nc)
|
|
915
|
+
results = vc.nc.commit_list([query])
|
|
916
|
+
|
|
917
|
+
# Convert the results to a DataFrame
|
|
918
|
+
df = pd.DataFrame.from_records(dict_cursor(results))
|
|
919
|
+
|
|
920
|
+
# If return_dataframe is True, return the results as a DataFrame
|
|
921
|
+
if return_dataframe:
|
|
922
|
+
return df
|
|
923
|
+
|
|
924
|
+
# Format the results for the preview
|
|
925
|
+
if not summary_mode:
|
|
926
|
+
results = {
|
|
927
|
+
"headers": {
|
|
928
|
+
"id": {"title": "ID", "type": "text", "order": -1},
|
|
929
|
+
"Neurotransmitter": {"title": "Neurotransmitter", "type": "markdown", "order": 0},
|
|
930
|
+
"Weight": {"title": "Weight", "type": "numeric", "order": 1},
|
|
931
|
+
"Name": {"title": "Name", "type": "markdown", "order": 2},
|
|
932
|
+
"Type": {"title": "Type", "type": "markdown", "order": 3},
|
|
933
|
+
"Gross_Type": {"title": "Gross Type", "type": "text", "order": 4},
|
|
934
|
+
"Template_Space": {"title": "Template Space", "type": "markdown", "order": 5},
|
|
935
|
+
"Imaging_Technique": {"title": "Imaging Technique", "type": "markdown", "order": 6},
|
|
936
|
+
"Images": {"title": "Images", "type": "markdown", "order": 7}
|
|
937
|
+
},
|
|
938
|
+
"rows": [
|
|
939
|
+
{
|
|
940
|
+
key: row[key]
|
|
941
|
+
for key in [
|
|
942
|
+
"id",
|
|
943
|
+
"Neurotransmitter",
|
|
944
|
+
"Weight",
|
|
945
|
+
"Name",
|
|
946
|
+
"Type",
|
|
947
|
+
"Gross_Type",
|
|
948
|
+
"Template_Space",
|
|
949
|
+
"Imaging_Technique",
|
|
950
|
+
"Images"
|
|
951
|
+
]
|
|
952
|
+
}
|
|
953
|
+
for row in df.to_dict("records")
|
|
954
|
+
],
|
|
955
|
+
"count": total_count
|
|
956
|
+
}
|
|
957
|
+
else:
|
|
958
|
+
results = {
|
|
959
|
+
"headers": {
|
|
960
|
+
"id": {"title": "ID", "type": "text", "order": -1},
|
|
961
|
+
"Neurotransmitter": {"title": "Neurotransmitter", "type": "markdown", "order": 0},
|
|
962
|
+
"Weight": {"title": "Weight", "type": "numeric", "order": 1},
|
|
963
|
+
},
|
|
964
|
+
"rows": [
|
|
965
|
+
{
|
|
966
|
+
key: row[key]
|
|
967
|
+
for key in [
|
|
968
|
+
"id",
|
|
969
|
+
"Neurotransmitter",
|
|
970
|
+
"Weight",
|
|
971
|
+
]
|
|
972
|
+
}
|
|
973
|
+
for row in df.to_dict("records")
|
|
974
|
+
],
|
|
975
|
+
"count": total_count
|
|
976
|
+
}
|
|
460
977
|
|
|
461
|
-
|
|
462
|
-
|
|
978
|
+
return results
|
|
979
|
+
|
|
463
980
|
|
|
464
981
|
def contains_all_tags(lst: List[str], tags: List[str]) -> bool:
|
|
465
982
|
"""
|
|
@@ -471,15 +988,63 @@ def contains_all_tags(lst: List[str], tags: List[str]) -> bool:
|
|
|
471
988
|
"""
|
|
472
989
|
return all(tag in lst for tag in tags)
|
|
473
990
|
|
|
474
|
-
def
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
991
|
+
def fill_query_results(term_info):
|
|
992
|
+
for query in term_info['Queries']:
|
|
993
|
+
# print(f"Query Keys:{query.keys()}")
|
|
994
|
+
|
|
995
|
+
if "preview" in query.keys() and (query['preview'] > 0 or query['count'] < 0) and query['count'] != 0:
|
|
996
|
+
function = globals().get(query['function'])
|
|
997
|
+
summary_mode = query.get('output_format', 'table') == 'ribbon'
|
|
998
|
+
|
|
999
|
+
if function:
|
|
1000
|
+
# print(f"Function {query['function']} found")
|
|
1001
|
+
|
|
1002
|
+
# Unpack the default dictionary and pass its contents as arguments
|
|
1003
|
+
function_args = query['takes'].get("default", {})
|
|
1004
|
+
# print(f"Function args: {function_args}")
|
|
1005
|
+
|
|
1006
|
+
# Modify this line to use the correct arguments and pass the default arguments
|
|
1007
|
+
if summary_mode:
|
|
1008
|
+
result = function(return_dataframe=False, limit=query['preview'], summary_mode=summary_mode, **function_args)
|
|
1009
|
+
else:
|
|
1010
|
+
result = function(return_dataframe=False, limit=query['preview'], **function_args)
|
|
1011
|
+
# print(f"Function result: {result}")
|
|
1012
|
+
|
|
1013
|
+
# Filter columns based on preview_columns
|
|
1014
|
+
filtered_result = []
|
|
1015
|
+
filtered_headers = {}
|
|
1016
|
+
|
|
1017
|
+
if isinstance(result, dict) and 'rows' in result:
|
|
1018
|
+
for item in result['rows']:
|
|
1019
|
+
if 'preview_columns' in query.keys() and len(query['preview_columns']) > 0:
|
|
1020
|
+
filtered_item = {col: item[col] for col in query['preview_columns']}
|
|
1021
|
+
else:
|
|
1022
|
+
filtered_item = item
|
|
1023
|
+
filtered_result.append(filtered_item)
|
|
1024
|
+
|
|
1025
|
+
if 'headers' in result:
|
|
1026
|
+
if 'preview_columns' in query.keys() and len(query['preview_columns']) > 0:
|
|
1027
|
+
filtered_headers = {col: result['headers'][col] for col in query['preview_columns']}
|
|
1028
|
+
else:
|
|
1029
|
+
filtered_headers = result['headers']
|
|
1030
|
+
elif isinstance(result, list) and all(isinstance(item, dict) for item in result):
|
|
1031
|
+
for item in result:
|
|
1032
|
+
if 'preview_columns' in query.keys() and len(query['preview_columns']) > 0:
|
|
1033
|
+
filtered_item = {col: item[col] for col in query['preview_columns']}
|
|
1034
|
+
else:
|
|
1035
|
+
filtered_item = item
|
|
1036
|
+
filtered_result.append(filtered_item)
|
|
1037
|
+
elif isinstance(result, pd.DataFrame):
|
|
1038
|
+
filtered_result = result[query['preview_columns']].to_dict('records')
|
|
1039
|
+
else:
|
|
1040
|
+
print(f"Unsupported result format for filtering columns in {query['function']}")
|
|
1041
|
+
|
|
1042
|
+
query['preview_results'] = {'headers': filtered_headers, 'rows': filtered_result}
|
|
1043
|
+
query['count'] = result['count']
|
|
1044
|
+
# print(f"Filtered result: {filtered_result}")
|
|
1045
|
+
else:
|
|
1046
|
+
print(f"Function {query['function']} not found")
|
|
1047
|
+
else:
|
|
1048
|
+
print("Preview key not found or preview is 0")
|
|
1049
|
+
return term_info
|
|
1050
|
+
|