vfbquery 0.4.0__py3-none-any.whl → 0.5.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.
- test/readme_parser.py +35 -1
- test/term_info_queries_test.py +11 -11
- test/test_dataset_template_queries.py +138 -0
- test/test_default_caching.py +15 -11
- test/test_expression_overlaps.py +183 -0
- test/test_expression_pattern_fragments.py +123 -0
- test/test_images_neurons.py +152 -0
- test/test_images_that_develop_from.py +112 -0
- test/test_lineage_clones_in.py +190 -0
- test/test_nblast_queries.py +124 -0
- test/test_neuron_classes_fasciculating.py +187 -0
- test/test_neuron_inputs.py +193 -0
- test/test_neuron_neuron_connectivity.py +89 -0
- test/test_neuron_region_connectivity.py +117 -0
- test/test_neurons_part_here.py +204 -0
- test/test_new_owlery_queries.py +282 -0
- test/test_publication_transgene_queries.py +101 -0
- test/test_query_performance.py +743 -0
- test/test_similar_morphology.py +177 -0
- test/test_tracts_nerves_innervating.py +188 -0
- test/test_transcriptomics.py +223 -0
- vfbquery/__init__.py +22 -1
- vfbquery/neo4j_client.py +120 -0
- vfbquery/owlery_client.py +463 -0
- vfbquery/solr_fetcher.py +1 -1
- vfbquery/solr_result_cache.py +238 -53
- vfbquery/vfb_queries.py +2969 -638
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/METADATA +1023 -65
- vfbquery-0.5.0.dist-info/RECORD +39 -0
- vfbquery-0.4.0.dist-info/RECORD +0 -19
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/LICENSE +0 -0
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/WHEEL +0 -0
- {vfbquery-0.4.0.dist-info → vfbquery-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Comprehensive performance test for all VFB queries.
|
|
4
|
+
|
|
5
|
+
Tests the execution time of all implemented queries to ensure they meet performance thresholds.
|
|
6
|
+
Results are formatted for GitHub Actions reporting.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import unittest
|
|
10
|
+
import time
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
# Add the src directory to the path
|
|
15
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
16
|
+
|
|
17
|
+
from vfbquery.vfb_queries import (
|
|
18
|
+
get_term_info,
|
|
19
|
+
get_neurons_with_part_in,
|
|
20
|
+
get_neurons_with_synapses_in,
|
|
21
|
+
get_neurons_with_presynaptic_terminals_in,
|
|
22
|
+
get_neurons_with_postsynaptic_terminals_in,
|
|
23
|
+
get_components_of,
|
|
24
|
+
get_parts_of,
|
|
25
|
+
get_subclasses_of,
|
|
26
|
+
get_neuron_classes_fasciculating_here,
|
|
27
|
+
get_tracts_nerves_innervating_here,
|
|
28
|
+
get_lineage_clones_in,
|
|
29
|
+
get_images_neurons,
|
|
30
|
+
get_images_that_develop_from,
|
|
31
|
+
get_expression_pattern_fragments,
|
|
32
|
+
get_instances,
|
|
33
|
+
get_similar_neurons,
|
|
34
|
+
get_neuron_neuron_connectivity,
|
|
35
|
+
get_neuron_region_connectivity,
|
|
36
|
+
get_individual_neuron_inputs,
|
|
37
|
+
get_expression_overlaps_here,
|
|
38
|
+
get_anatomy_scrnaseq,
|
|
39
|
+
get_cluster_expression,
|
|
40
|
+
get_expression_cluster,
|
|
41
|
+
get_scrnaseq_dataset_data,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class QueryPerformanceTest(unittest.TestCase):
|
|
46
|
+
"""Comprehensive performance tests for all VFB queries"""
|
|
47
|
+
|
|
48
|
+
# Performance thresholds (in seconds)
|
|
49
|
+
THRESHOLD_FAST = 1.0 # Fast queries (simple SOLR lookups)
|
|
50
|
+
THRESHOLD_MEDIUM = 3.0 # Medium queries (Owlery + SOLR)
|
|
51
|
+
THRESHOLD_SLOW = 15.0 # Slow queries (Neo4j + complex processing)
|
|
52
|
+
THRESHOLD_VERY_SLOW = 31.0 # Very slow queries (complex OWL reasoning - over 30 seconds)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def setUpClass(cls):
|
|
56
|
+
"""Enable caching for performance tests"""
|
|
57
|
+
# Import caching module
|
|
58
|
+
from vfbquery import cache_enhancements
|
|
59
|
+
|
|
60
|
+
# Enable caching to speed up repeated queries
|
|
61
|
+
cache_enhancements.enable_vfbquery_caching()
|
|
62
|
+
print("\n🔥 Caching enabled for performance tests")
|
|
63
|
+
|
|
64
|
+
def setUp(self):
|
|
65
|
+
"""Set up test data"""
|
|
66
|
+
self.test_terms = {
|
|
67
|
+
'mushroom_body': 'FBbt_00003748', # Class - mushroom body
|
|
68
|
+
'antennal_lobe': 'FBbt_00007401', # Synaptic neuropil
|
|
69
|
+
'medulla': 'FBbt_00003982', # Visual system
|
|
70
|
+
'broad_root': 'FBbt_00003987', # Neuron projection bundle (tract)
|
|
71
|
+
'individual_neuron': 'VFB_00101567', # Individual anatomy
|
|
72
|
+
'neuron_with_nblast': 'VFB_00017894', # Neuron with NBLAST data (alternative)
|
|
73
|
+
'clone': 'FBbt_00050024', # Clone
|
|
74
|
+
'connected_neuron': 'VFB_jrchk00s', # LPC1 neuron with connectivity AND NBLAST data
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
self.results = []
|
|
78
|
+
|
|
79
|
+
def _time_query(self, query_name, query_func, *args, **kwargs):
|
|
80
|
+
"""Helper to time a query execution"""
|
|
81
|
+
start_time = time.time()
|
|
82
|
+
try:
|
|
83
|
+
result = query_func(*args, **kwargs)
|
|
84
|
+
duration = time.time() - start_time
|
|
85
|
+
success = result is not None
|
|
86
|
+
error = None
|
|
87
|
+
except Exception as e:
|
|
88
|
+
duration = time.time() - start_time
|
|
89
|
+
success = False
|
|
90
|
+
result = None
|
|
91
|
+
error = str(e)
|
|
92
|
+
|
|
93
|
+
self.results.append({
|
|
94
|
+
'name': query_name,
|
|
95
|
+
'duration': duration,
|
|
96
|
+
'success': success,
|
|
97
|
+
'error': error
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return result, duration, success
|
|
101
|
+
|
|
102
|
+
def test_01_term_info_queries(self):
|
|
103
|
+
"""Test term info query performance"""
|
|
104
|
+
print("\n" + "="*80)
|
|
105
|
+
print("TERM INFO QUERIES")
|
|
106
|
+
print("="*80)
|
|
107
|
+
|
|
108
|
+
# Test basic term info retrieval
|
|
109
|
+
result, duration, success = self._time_query(
|
|
110
|
+
"get_term_info (mushroom body)",
|
|
111
|
+
get_term_info,
|
|
112
|
+
self.test_terms['mushroom_body'],
|
|
113
|
+
preview=True
|
|
114
|
+
)
|
|
115
|
+
print(f"get_term_info (mushroom body): {duration:.4f}s {'✅' if success else '❌'}")
|
|
116
|
+
# term_info with preview can be very slow due to extensive sub-queries
|
|
117
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "term_info query exceeded threshold")
|
|
118
|
+
|
|
119
|
+
result, duration, success = self._time_query(
|
|
120
|
+
"get_term_info (individual)",
|
|
121
|
+
get_term_info,
|
|
122
|
+
self.test_terms['individual_neuron'],
|
|
123
|
+
preview=True
|
|
124
|
+
)
|
|
125
|
+
print(f"get_term_info (individual): {duration:.4f}s {'✅' if success else '❌'}")
|
|
126
|
+
# term_info with preview can be very slow due to extensive sub-queries
|
|
127
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "term_info query exceeded threshold")
|
|
128
|
+
|
|
129
|
+
def test_02_neuron_part_queries(self):
|
|
130
|
+
"""Test neuron part overlap queries"""
|
|
131
|
+
print("\n" + "="*80)
|
|
132
|
+
print("NEURON PART OVERLAP QUERIES")
|
|
133
|
+
print("="*80)
|
|
134
|
+
|
|
135
|
+
result, duration, success = self._time_query(
|
|
136
|
+
"NeuronsPartHere (antennal lobe)",
|
|
137
|
+
get_neurons_with_part_in,
|
|
138
|
+
self.test_terms['antennal_lobe'],
|
|
139
|
+
return_dataframe=False,
|
|
140
|
+
limit=-1 # Changed to -1 to enable caching
|
|
141
|
+
)
|
|
142
|
+
print(f"NeuronsPartHere: {duration:.4f}s {'✅' if success else '❌'}")
|
|
143
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsPartHere exceeded threshold")
|
|
144
|
+
|
|
145
|
+
def test_03_synaptic_queries(self):
|
|
146
|
+
"""Test synaptic terminal queries"""
|
|
147
|
+
print("\n" + "="*80)
|
|
148
|
+
print("SYNAPTIC TERMINAL QUERIES")
|
|
149
|
+
print("="*80)
|
|
150
|
+
|
|
151
|
+
test_term = self.test_terms['antennal_lobe']
|
|
152
|
+
|
|
153
|
+
result, duration, success = self._time_query(
|
|
154
|
+
"NeuronsSynaptic",
|
|
155
|
+
get_neurons_with_synapses_in,
|
|
156
|
+
test_term,
|
|
157
|
+
return_dataframe=False,
|
|
158
|
+
limit=-1 # Enable caching for performance tests
|
|
159
|
+
)
|
|
160
|
+
print(f"NeuronsSynaptic: {duration:.4f}s {'✅' if success else '❌'}")
|
|
161
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsSynaptic exceeded threshold")
|
|
162
|
+
|
|
163
|
+
result, duration, success = self._time_query(
|
|
164
|
+
"NeuronsPresynapticHere",
|
|
165
|
+
get_neurons_with_presynaptic_terminals_in,
|
|
166
|
+
test_term,
|
|
167
|
+
return_dataframe=False,
|
|
168
|
+
limit=-1 # Enable caching for performance tests
|
|
169
|
+
)
|
|
170
|
+
print(f"NeuronsPresynapticHere: {duration:.4f}s {'✅' if success else '❌'}")
|
|
171
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsPresynapticHere exceeded threshold")
|
|
172
|
+
|
|
173
|
+
result, duration, success = self._time_query(
|
|
174
|
+
"NeuronsPostsynapticHere",
|
|
175
|
+
get_neurons_with_postsynaptic_terminals_in,
|
|
176
|
+
test_term,
|
|
177
|
+
return_dataframe=False,
|
|
178
|
+
limit=-1 # Enable caching for performance tests
|
|
179
|
+
)
|
|
180
|
+
print(f"NeuronsPostsynapticHere: {duration:.4f}s {'✅' if success else '❌'}")
|
|
181
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "NeuronsPostsynapticHere exceeded threshold")
|
|
182
|
+
|
|
183
|
+
# Test neuron-neuron connectivity query
|
|
184
|
+
result, duration, success = self._time_query(
|
|
185
|
+
"NeuronNeuronConnectivity",
|
|
186
|
+
get_neuron_neuron_connectivity,
|
|
187
|
+
self.test_terms['connected_neuron'],
|
|
188
|
+
return_dataframe=False,
|
|
189
|
+
limit=-1 # Enable caching for performance tests
|
|
190
|
+
)
|
|
191
|
+
print(f"NeuronNeuronConnectivity: {duration:.4f}s {'✅' if success else '❌'}")
|
|
192
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronNeuronConnectivity exceeded threshold")
|
|
193
|
+
|
|
194
|
+
def test_04_anatomy_hierarchy_queries(self):
|
|
195
|
+
"""Test anatomical hierarchy queries"""
|
|
196
|
+
print("\n" + "="*80)
|
|
197
|
+
print("ANATOMICAL HIERARCHY QUERIES")
|
|
198
|
+
print("="*80)
|
|
199
|
+
|
|
200
|
+
test_term = self.test_terms['mushroom_body']
|
|
201
|
+
|
|
202
|
+
result, duration, success = self._time_query(
|
|
203
|
+
"ComponentsOf",
|
|
204
|
+
get_components_of,
|
|
205
|
+
test_term,
|
|
206
|
+
return_dataframe=False,
|
|
207
|
+
limit=-1 # Enable caching for performance tests
|
|
208
|
+
)
|
|
209
|
+
print(f"ComponentsOf: {duration:.4f}s {'✅' if success else '❌'}")
|
|
210
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "ComponentsOf exceeded threshold")
|
|
211
|
+
|
|
212
|
+
result, duration, success = self._time_query(
|
|
213
|
+
"PartsOf",
|
|
214
|
+
get_parts_of,
|
|
215
|
+
test_term,
|
|
216
|
+
return_dataframe=False,
|
|
217
|
+
limit=-1 # Enable caching for performance tests
|
|
218
|
+
)
|
|
219
|
+
print(f"PartsOf: {duration:.4f}s {'✅' if success else '❌'}")
|
|
220
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "PartsOf exceeded threshold")
|
|
221
|
+
|
|
222
|
+
result, duration, success = self._time_query(
|
|
223
|
+
"SubclassesOf",
|
|
224
|
+
get_subclasses_of,
|
|
225
|
+
test_term,
|
|
226
|
+
return_dataframe=False,
|
|
227
|
+
limit=-1 # Enable caching for performance tests
|
|
228
|
+
)
|
|
229
|
+
print(f"SubclassesOf: {duration:.4f}s {'✅' if success else '❌'}")
|
|
230
|
+
# SubclassesOf can be very slow for complex terms like mushroom body
|
|
231
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "SubclassesOf exceeded threshold")
|
|
232
|
+
|
|
233
|
+
def test_05_tract_lineage_queries(self):
|
|
234
|
+
"""Test tract/nerve and lineage clone queries"""
|
|
235
|
+
print("\n" + "="*80)
|
|
236
|
+
print("TRACT/NERVE AND LINEAGE QUERIES")
|
|
237
|
+
print("="*80)
|
|
238
|
+
|
|
239
|
+
# NeuronClassesFasciculatingHere
|
|
240
|
+
result, duration, success = self._time_query(
|
|
241
|
+
"NeuronClassesFasciculatingHere",
|
|
242
|
+
get_neuron_classes_fasciculating_here,
|
|
243
|
+
self.test_terms['broad_root'],
|
|
244
|
+
return_dataframe=False,
|
|
245
|
+
limit=-1 # Enable caching for performance tests
|
|
246
|
+
)
|
|
247
|
+
print(f"NeuronClassesFasciculatingHere: {duration:.4f}s {'✅' if success else '❌'}")
|
|
248
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronClassesFasciculatingHere exceeded threshold")
|
|
249
|
+
|
|
250
|
+
# TractsNervesInnervatingHere
|
|
251
|
+
result, duration, success = self._time_query(
|
|
252
|
+
"TractsNervesInnervatingHere",
|
|
253
|
+
get_tracts_nerves_innervating_here,
|
|
254
|
+
self.test_terms['antennal_lobe'],
|
|
255
|
+
return_dataframe=False,
|
|
256
|
+
limit=-1 # Enable caching for performance tests
|
|
257
|
+
)
|
|
258
|
+
print(f"TractsNervesInnervatingHere: {duration:.4f}s {'✅' if success else '❌'}")
|
|
259
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "TractsNervesInnervatingHere exceeded threshold")
|
|
260
|
+
|
|
261
|
+
# LineageClonesIn
|
|
262
|
+
result, duration, success = self._time_query(
|
|
263
|
+
"LineageClonesIn",
|
|
264
|
+
get_lineage_clones_in,
|
|
265
|
+
self.test_terms['antennal_lobe'],
|
|
266
|
+
return_dataframe=False,
|
|
267
|
+
limit=-1 # Enable caching for performance tests
|
|
268
|
+
)
|
|
269
|
+
print(f"LineageClonesIn: {duration:.4f}s {'✅' if success else '❌'}")
|
|
270
|
+
# LineageClonesIn can be very slow due to complex OWL reasoning
|
|
271
|
+
self.assertLess(duration, self.THRESHOLD_VERY_SLOW, "LineageClonesIn exceeded threshold")
|
|
272
|
+
|
|
273
|
+
def test_05b_image_queries(self):
|
|
274
|
+
"""Test image and developmental lineage queries"""
|
|
275
|
+
print("\n" + "="*80)
|
|
276
|
+
print("IMAGE AND DEVELOPMENTAL QUERIES")
|
|
277
|
+
print("="*80)
|
|
278
|
+
|
|
279
|
+
# ImagesNeurons
|
|
280
|
+
result, duration, success = self._time_query(
|
|
281
|
+
"ImagesNeurons",
|
|
282
|
+
get_images_neurons,
|
|
283
|
+
self.test_terms['antennal_lobe'],
|
|
284
|
+
return_dataframe=False,
|
|
285
|
+
limit=-1 # Enable caching for performance tests
|
|
286
|
+
)
|
|
287
|
+
print(f"ImagesNeurons: {duration:.4f}s {'✅' if success else '❌'}")
|
|
288
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "ImagesNeurons exceeded threshold")
|
|
289
|
+
|
|
290
|
+
# ImagesThatDevelopFrom test (neuroblast developmental lineages)
|
|
291
|
+
result, duration, success = self._time_query(
|
|
292
|
+
"ImagesThatDevelopFrom",
|
|
293
|
+
get_images_that_develop_from,
|
|
294
|
+
"FBbt_00001419", # neuroblast MNB - has 336 neuron images
|
|
295
|
+
return_dataframe=False,
|
|
296
|
+
limit=-1 # Enable caching for performance tests
|
|
297
|
+
)
|
|
298
|
+
print(f"ImagesThatDevelopFrom: {duration:.4f}s {'✅' if success else '❌'}")
|
|
299
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "ImagesThatDevelopFrom exceeded threshold")
|
|
300
|
+
|
|
301
|
+
# epFrag test (expression pattern fragments)
|
|
302
|
+
result, duration, success = self._time_query(
|
|
303
|
+
"epFrag",
|
|
304
|
+
get_expression_pattern_fragments,
|
|
305
|
+
"FBtp0000001", # expression pattern example
|
|
306
|
+
return_dataframe=False,
|
|
307
|
+
limit=-1 # Enable caching for performance tests
|
|
308
|
+
)
|
|
309
|
+
print(f"epFrag: {duration:.4f}s {'✅' if success else '❌'}")
|
|
310
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "epFrag exceeded threshold")
|
|
311
|
+
|
|
312
|
+
def test_06_instance_queries(self):
|
|
313
|
+
"""Test instance retrieval queries"""
|
|
314
|
+
print("\n" + "="*80)
|
|
315
|
+
print("INSTANCE QUERIES")
|
|
316
|
+
print("="*80)
|
|
317
|
+
|
|
318
|
+
result, duration, success = self._time_query(
|
|
319
|
+
"ListAllAvailableImages",
|
|
320
|
+
get_instances,
|
|
321
|
+
self.test_terms['medulla'],
|
|
322
|
+
return_dataframe=False,
|
|
323
|
+
limit=-1 # Enable caching for performance tests
|
|
324
|
+
)
|
|
325
|
+
print(f"ListAllAvailableImages: {duration:.4f}s {'✅' if success else '❌'}")
|
|
326
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "ListAllAvailableImages exceeded threshold")
|
|
327
|
+
|
|
328
|
+
def test_07_connectivity_queries(self):
|
|
329
|
+
"""Test neuron connectivity queries"""
|
|
330
|
+
print("\n" + "="*80)
|
|
331
|
+
print("CONNECTIVITY QUERIES")
|
|
332
|
+
print("="*80)
|
|
333
|
+
|
|
334
|
+
# NeuronNeuronConnectivity
|
|
335
|
+
result, duration, success = self._time_query(
|
|
336
|
+
"NeuronNeuronConnectivityQuery",
|
|
337
|
+
get_neuron_neuron_connectivity,
|
|
338
|
+
self.test_terms['connected_neuron'],
|
|
339
|
+
return_dataframe=False,
|
|
340
|
+
limit=-1 # Enable caching for performance tests
|
|
341
|
+
)
|
|
342
|
+
print(f"NeuronNeuronConnectivityQuery: {duration:.4f}s {'✅' if success else '❌'}")
|
|
343
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronNeuronConnectivityQuery exceeded threshold")
|
|
344
|
+
|
|
345
|
+
# NeuronRegionConnectivity
|
|
346
|
+
result, duration, success = self._time_query(
|
|
347
|
+
"NeuronRegionConnectivityQuery",
|
|
348
|
+
get_neuron_region_connectivity,
|
|
349
|
+
self.test_terms['connected_neuron'],
|
|
350
|
+
return_dataframe=False,
|
|
351
|
+
limit=-1 # Enable caching for performance tests
|
|
352
|
+
)
|
|
353
|
+
print(f"NeuronRegionConnectivityQuery: {duration:.4f}s {'✅' if success else '❌'}")
|
|
354
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronRegionConnectivityQuery exceeded threshold")
|
|
355
|
+
|
|
356
|
+
def test_08_similarity_queries(self):
|
|
357
|
+
"""Test NBLAST similarity queries"""
|
|
358
|
+
print("\n" + "="*80)
|
|
359
|
+
print("SIMILARITY QUERIES (Neo4j NBLAST)")
|
|
360
|
+
print("="*80)
|
|
361
|
+
|
|
362
|
+
# SimilarMorphologyTo (NBLAST)
|
|
363
|
+
result, duration, success = self._time_query(
|
|
364
|
+
"SimilarMorphologyTo",
|
|
365
|
+
get_similar_neurons,
|
|
366
|
+
self.test_terms['connected_neuron'], # VFB_jrchk00s has NBLAST data
|
|
367
|
+
similarity_score='NBLAST_score',
|
|
368
|
+
return_dataframe=False,
|
|
369
|
+
limit=5
|
|
370
|
+
)
|
|
371
|
+
print(f"SimilarMorphologyTo: {duration:.4f}s {'✅' if success else '❌'}")
|
|
372
|
+
# Legacy NBLAST similarity can be slower
|
|
373
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "SimilarMorphologyTo exceeded threshold")
|
|
374
|
+
|
|
375
|
+
def test_09_neuron_input_queries(self):
|
|
376
|
+
"""Test neuron input/synapse queries"""
|
|
377
|
+
print("\n" + "="*80)
|
|
378
|
+
print("NEURON INPUT QUERIES (Neo4j)")
|
|
379
|
+
print("="*80)
|
|
380
|
+
|
|
381
|
+
# NeuronInputsTo
|
|
382
|
+
result, duration, success = self._time_query(
|
|
383
|
+
"NeuronInputsTo",
|
|
384
|
+
get_individual_neuron_inputs,
|
|
385
|
+
self.test_terms['connected_neuron'],
|
|
386
|
+
return_dataframe=False,
|
|
387
|
+
limit=5
|
|
388
|
+
)
|
|
389
|
+
print(f"NeuronInputsTo: {duration:.4f}s {'✅' if success else '❌'}")
|
|
390
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "NeuronInputsTo exceeded threshold")
|
|
391
|
+
|
|
392
|
+
def test_10_expression_queries(self):
|
|
393
|
+
"""Test expression pattern queries"""
|
|
394
|
+
print("\n" + "="*80)
|
|
395
|
+
print("EXPRESSION PATTERN QUERIES (Neo4j)")
|
|
396
|
+
print("="*80)
|
|
397
|
+
|
|
398
|
+
# ExpressionOverlapsHere - test with adult brain which has many expression patterns
|
|
399
|
+
result, duration, success = self._time_query(
|
|
400
|
+
"ExpressionOverlapsHere (adult brain)",
|
|
401
|
+
get_expression_overlaps_here,
|
|
402
|
+
self.test_terms['medulla'], # FBbt_00003982 (adult brain/medulla)
|
|
403
|
+
return_dataframe=False,
|
|
404
|
+
limit=10 # Limit to 10 for performance test
|
|
405
|
+
)
|
|
406
|
+
print(f"ExpressionOverlapsHere: {duration:.4f}s {'✅' if success else '❌'}")
|
|
407
|
+
if success and result:
|
|
408
|
+
print(f" └─ Found {result.get('count', 0)} total expression patterns, returned 10")
|
|
409
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "ExpressionOverlapsHere exceeded threshold")
|
|
410
|
+
|
|
411
|
+
def test_11_transcriptomics_queries(self):
|
|
412
|
+
"""Test scRNAseq transcriptomics queries"""
|
|
413
|
+
print("\n" + "="*80)
|
|
414
|
+
print("TRANSCRIPTOMICS QUERIES (Neo4j scRNAseq)")
|
|
415
|
+
print("="*80)
|
|
416
|
+
|
|
417
|
+
# Note: These tests use example IDs that may need to be updated based on actual database content
|
|
418
|
+
# The queries are designed to work even if no data is found (will return empty results)
|
|
419
|
+
|
|
420
|
+
# anatScRNAseqQuery - test with adult brain
|
|
421
|
+
result, duration, success = self._time_query(
|
|
422
|
+
"anatScRNAseqQuery (adult brain)",
|
|
423
|
+
get_anatomy_scrnaseq,
|
|
424
|
+
self.test_terms['medulla'], # FBbt_00003982 (adult brain/medulla)
|
|
425
|
+
return_dataframe=False,
|
|
426
|
+
limit=10
|
|
427
|
+
)
|
|
428
|
+
print(f"anatScRNAseqQuery: {duration:.4f}s {'✅' if success else '❌'}")
|
|
429
|
+
if success and result:
|
|
430
|
+
count = result.get('count', 0)
|
|
431
|
+
print(f" └─ Found {count} total clusters" + (", returned 10" if count > 10 else ""))
|
|
432
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "anatScRNAseqQuery exceeded threshold")
|
|
433
|
+
|
|
434
|
+
# clusterExpression - test with a cluster ID (may return empty if cluster doesn't exist)
|
|
435
|
+
# Using a dummy ID - test will pass even with empty results
|
|
436
|
+
try:
|
|
437
|
+
result, duration, success = self._time_query(
|
|
438
|
+
"clusterExpression (example cluster)",
|
|
439
|
+
get_cluster_expression,
|
|
440
|
+
"VFBc_00101567", # Example cluster ID
|
|
441
|
+
return_dataframe=False,
|
|
442
|
+
limit=10
|
|
443
|
+
)
|
|
444
|
+
print(f"clusterExpression: {duration:.4f}s {'✅' if success else '❌'}")
|
|
445
|
+
if success and result:
|
|
446
|
+
count = result.get('count', 0)
|
|
447
|
+
print(f" └─ Found {count} genes expressed" + (", returned 10" if count > 10 else ""))
|
|
448
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "clusterExpression exceeded threshold")
|
|
449
|
+
except Exception as e:
|
|
450
|
+
print(f"clusterExpression: Skipped (test data may not exist): {e}")
|
|
451
|
+
|
|
452
|
+
# expressionCluster - test with a gene ID (may return empty if no scRNAseq data)
|
|
453
|
+
try:
|
|
454
|
+
result, duration, success = self._time_query(
|
|
455
|
+
"expressionCluster (example gene)",
|
|
456
|
+
get_expression_cluster,
|
|
457
|
+
"FBgn_00000024", # Example gene ID
|
|
458
|
+
return_dataframe=False,
|
|
459
|
+
limit=10
|
|
460
|
+
)
|
|
461
|
+
print(f"expressionCluster: {duration:.4f}s {'✅' if success else '❌'}")
|
|
462
|
+
if success and result:
|
|
463
|
+
count = result.get('count', 0)
|
|
464
|
+
print(f" └─ Found {count} clusters expressing gene" + (", returned 10" if count > 10 else ""))
|
|
465
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "expressionCluster exceeded threshold")
|
|
466
|
+
except Exception as e:
|
|
467
|
+
print(f"expressionCluster: Skipped (test data may not exist): {e}")
|
|
468
|
+
|
|
469
|
+
# scRNAdatasetData - test with a dataset ID (may return empty if dataset doesn't exist)
|
|
470
|
+
try:
|
|
471
|
+
result, duration, success = self._time_query(
|
|
472
|
+
"scRNAdatasetData (example dataset)",
|
|
473
|
+
get_scrnaseq_dataset_data,
|
|
474
|
+
"VFBds_00001234", # Example dataset ID
|
|
475
|
+
return_dataframe=False,
|
|
476
|
+
limit=10
|
|
477
|
+
)
|
|
478
|
+
print(f"scRNAdatasetData: {duration:.4f}s {'✅' if success else '❌'}")
|
|
479
|
+
if success and result:
|
|
480
|
+
count = result.get('count', 0)
|
|
481
|
+
print(f" └─ Found {count} clusters in dataset" + (", returned 10" if count > 10 else ""))
|
|
482
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "scRNAdatasetData exceeded threshold")
|
|
483
|
+
except Exception as e:
|
|
484
|
+
print(f"scRNAdatasetData: Skipped (test data may not exist): {e}")
|
|
485
|
+
|
|
486
|
+
def test_12_nblast_queries(self):
|
|
487
|
+
"""Test NBLAST similarity queries"""
|
|
488
|
+
print("\n" + "="*80)
|
|
489
|
+
print("NBLAST SIMILARITY QUERIES")
|
|
490
|
+
print("="*80)
|
|
491
|
+
|
|
492
|
+
# Import the new query functions
|
|
493
|
+
from vfbquery.vfb_queries import (
|
|
494
|
+
get_similar_morphology,
|
|
495
|
+
get_similar_morphology_part_of,
|
|
496
|
+
get_similar_morphology_part_of_exp,
|
|
497
|
+
get_similar_morphology_nb,
|
|
498
|
+
get_similar_morphology_nb_exp,
|
|
499
|
+
get_similar_morphology_userdata
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# SimilarMorphologyTo - NBLAST matches
|
|
503
|
+
result, duration, success = self._time_query(
|
|
504
|
+
"SimilarMorphologyTo",
|
|
505
|
+
get_similar_morphology,
|
|
506
|
+
self.test_terms['connected_neuron'], # LPC1 neuron with NBLAST data
|
|
507
|
+
return_dataframe=False,
|
|
508
|
+
limit=10
|
|
509
|
+
)
|
|
510
|
+
print(f"SimilarMorphologyTo: {duration:.4f}s {'✅' if success else '❌'}")
|
|
511
|
+
if success and result:
|
|
512
|
+
count = result.get('count', 0)
|
|
513
|
+
print(f" └─ Found {count} NBLAST matches" + (", returned 10" if count > 10 else ""))
|
|
514
|
+
# NBLAST queries can be slightly slower due to database complexity
|
|
515
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "SimilarMorphologyTo exceeded threshold")
|
|
516
|
+
|
|
517
|
+
# SimilarMorphologyToPartOf - NBLASTexp to expression patterns
|
|
518
|
+
result, duration, success = self._time_query(
|
|
519
|
+
"SimilarMorphologyToPartOf",
|
|
520
|
+
get_similar_morphology_part_of,
|
|
521
|
+
self.test_terms['connected_neuron'],
|
|
522
|
+
return_dataframe=False,
|
|
523
|
+
limit=10
|
|
524
|
+
)
|
|
525
|
+
print(f"SimilarMorphologyToPartOf: {duration:.4f}s {'✅' if success else '❌'}")
|
|
526
|
+
if success and result:
|
|
527
|
+
count = result.get('count', 0)
|
|
528
|
+
print(f" └─ Found {count} NBLASTexp matches" + (", returned 10" if count > 10 else ""))
|
|
529
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToPartOf exceeded threshold")
|
|
530
|
+
|
|
531
|
+
# SimilarMorphologyToPartOfexp - Reverse NBLASTexp
|
|
532
|
+
result, duration, success = self._time_query(
|
|
533
|
+
"SimilarMorphologyToPartOfexp",
|
|
534
|
+
get_similar_morphology_part_of_exp,
|
|
535
|
+
self.test_terms['connected_neuron'],
|
|
536
|
+
return_dataframe=False,
|
|
537
|
+
limit=10
|
|
538
|
+
)
|
|
539
|
+
print(f"SimilarMorphologyToPartOfexp: {duration:.4f}s {'✅' if success else '❌'}")
|
|
540
|
+
if success and result:
|
|
541
|
+
count = result.get('count', 0)
|
|
542
|
+
print(f" └─ Found {count} reverse NBLASTexp matches" + (", returned 10" if count > 10 else ""))
|
|
543
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToPartOfexp exceeded threshold")
|
|
544
|
+
|
|
545
|
+
# SimilarMorphologyToNB - NeuronBridge matches
|
|
546
|
+
result, duration, success = self._time_query(
|
|
547
|
+
"SimilarMorphologyToNB",
|
|
548
|
+
get_similar_morphology_nb,
|
|
549
|
+
self.test_terms['connected_neuron'],
|
|
550
|
+
return_dataframe=False,
|
|
551
|
+
limit=10
|
|
552
|
+
)
|
|
553
|
+
print(f"SimilarMorphologyToNB: {duration:.4f}s {'✅' if success else '❌'}")
|
|
554
|
+
if success and result:
|
|
555
|
+
count = result.get('count', 0)
|
|
556
|
+
print(f" └─ Found {count} NeuronBridge matches" + (", returned 10" if count > 10 else ""))
|
|
557
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToNB exceeded threshold")
|
|
558
|
+
|
|
559
|
+
# SimilarMorphologyToNBexp - NeuronBridge for expression patterns
|
|
560
|
+
result, duration, success = self._time_query(
|
|
561
|
+
"SimilarMorphologyToNBexp",
|
|
562
|
+
get_similar_morphology_nb_exp,
|
|
563
|
+
self.test_terms['connected_neuron'],
|
|
564
|
+
return_dataframe=False,
|
|
565
|
+
limit=10
|
|
566
|
+
)
|
|
567
|
+
print(f"SimilarMorphologyToNBexp: {duration:.4f}s {'✅' if success else '❌'}")
|
|
568
|
+
if success and result:
|
|
569
|
+
count = result.get('count', 0)
|
|
570
|
+
print(f" └─ Found {count} NeuronBridge expression matches" + (", returned 10" if count > 10 else ""))
|
|
571
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "SimilarMorphologyToNBexp exceeded threshold")
|
|
572
|
+
|
|
573
|
+
print(f"✅ All NBLAST similarity queries completed")
|
|
574
|
+
|
|
575
|
+
def test_13_dataset_template_queries(self):
|
|
576
|
+
"""Test dataset and template queries"""
|
|
577
|
+
print("\n" + "="*80)
|
|
578
|
+
print("DATASET/TEMPLATE QUERIES")
|
|
579
|
+
print("="*80)
|
|
580
|
+
|
|
581
|
+
# Import the new query functions
|
|
582
|
+
from vfbquery.vfb_queries import (
|
|
583
|
+
get_painted_domains,
|
|
584
|
+
get_dataset_images,
|
|
585
|
+
get_all_aligned_images,
|
|
586
|
+
get_aligned_datasets,
|
|
587
|
+
get_all_datasets
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# Test terms for templates and datasets
|
|
591
|
+
template_term = 'VFBc_00050000' # Adult Brain template
|
|
592
|
+
dataset_term = 'VFBc_00101384' # Example dataset
|
|
593
|
+
|
|
594
|
+
# PaintedDomains - Template painted anatomy domains
|
|
595
|
+
result, duration, success = self._time_query(
|
|
596
|
+
"PaintedDomains",
|
|
597
|
+
get_painted_domains,
|
|
598
|
+
template_term,
|
|
599
|
+
return_dataframe=False,
|
|
600
|
+
limit=10
|
|
601
|
+
)
|
|
602
|
+
print(f"PaintedDomains: {duration:.4f}s {'✅' if success else '❌'}")
|
|
603
|
+
if success and result:
|
|
604
|
+
count = result.get('count', 0)
|
|
605
|
+
print(f" └─ Found {count} painted domains" + (", returned 10" if count > 10 else ""))
|
|
606
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "PaintedDomains exceeded threshold")
|
|
607
|
+
|
|
608
|
+
# DatasetImages - Images in a dataset
|
|
609
|
+
result, duration, success = self._time_query(
|
|
610
|
+
"DatasetImages",
|
|
611
|
+
get_dataset_images,
|
|
612
|
+
dataset_term,
|
|
613
|
+
return_dataframe=False,
|
|
614
|
+
limit=10
|
|
615
|
+
)
|
|
616
|
+
print(f"DatasetImages: {duration:.4f}s {'✅' if success else '❌'}")
|
|
617
|
+
if success and result:
|
|
618
|
+
count = result.get('count', 0)
|
|
619
|
+
print(f" └─ Found {count} images in dataset" + (", returned 10" if count > 10 else ""))
|
|
620
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "DatasetImages exceeded threshold")
|
|
621
|
+
|
|
622
|
+
# AllAlignedImages - All images aligned to template
|
|
623
|
+
result, duration, success = self._time_query(
|
|
624
|
+
"AllAlignedImages",
|
|
625
|
+
get_all_aligned_images,
|
|
626
|
+
template_term,
|
|
627
|
+
return_dataframe=False,
|
|
628
|
+
limit=10
|
|
629
|
+
)
|
|
630
|
+
print(f"AllAlignedImages: {duration:.4f}s {'✅' if success else '❌'}")
|
|
631
|
+
if success and result:
|
|
632
|
+
count = result.get('count', 0)
|
|
633
|
+
print(f" └─ Found {count} aligned images" + (", returned 10" if count > 10 else ""))
|
|
634
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "AllAlignedImages exceeded threshold")
|
|
635
|
+
|
|
636
|
+
# AlignedDatasets - All datasets aligned to template
|
|
637
|
+
result, duration, success = self._time_query(
|
|
638
|
+
"AlignedDatasets",
|
|
639
|
+
get_aligned_datasets,
|
|
640
|
+
template_term,
|
|
641
|
+
return_dataframe=False,
|
|
642
|
+
limit=10
|
|
643
|
+
)
|
|
644
|
+
print(f"AlignedDatasets: {duration:.4f}s {'✅' if success else '❌'}")
|
|
645
|
+
if success and result:
|
|
646
|
+
count = result.get('count', 0)
|
|
647
|
+
print(f" └─ Found {count} aligned datasets" + (", returned 10" if count > 10 else ""))
|
|
648
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "AlignedDatasets exceeded threshold")
|
|
649
|
+
|
|
650
|
+
# AllDatasets - All available datasets
|
|
651
|
+
result, duration, success = self._time_query(
|
|
652
|
+
"AllDatasets",
|
|
653
|
+
get_all_datasets,
|
|
654
|
+
return_dataframe=False,
|
|
655
|
+
limit=20
|
|
656
|
+
)
|
|
657
|
+
print(f"AllDatasets: {duration:.4f}s {'✅' if success else '❌'}")
|
|
658
|
+
if success and result:
|
|
659
|
+
count = result.get('count', 0)
|
|
660
|
+
print(f" └─ Found {count} total datasets" + (", returned 20" if count > 20 else ""))
|
|
661
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "AllDatasets exceeded threshold")
|
|
662
|
+
|
|
663
|
+
print(f"✅ All dataset/template queries completed")
|
|
664
|
+
|
|
665
|
+
def test_14_publication_transgene_queries(self):
|
|
666
|
+
"""Test publication and transgene queries"""
|
|
667
|
+
print("\n" + "="*80)
|
|
668
|
+
print("PUBLICATION/TRANSGENE QUERIES")
|
|
669
|
+
print("="*80)
|
|
670
|
+
|
|
671
|
+
# Import the new query functions
|
|
672
|
+
from vfbquery.vfb_queries import (
|
|
673
|
+
get_terms_for_pub,
|
|
674
|
+
get_transgene_expression_here
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
# Test terms
|
|
678
|
+
pub_term = 'DOI_10_7554_eLife_04577' # Example publication
|
|
679
|
+
anatomy_term = self.test_terms['mushroom_body'] # mushroom body
|
|
680
|
+
|
|
681
|
+
# TermsForPub - Terms referencing a publication
|
|
682
|
+
result, duration, success = self._time_query(
|
|
683
|
+
"TermsForPub",
|
|
684
|
+
get_terms_for_pub,
|
|
685
|
+
pub_term,
|
|
686
|
+
return_dataframe=False,
|
|
687
|
+
limit=10
|
|
688
|
+
)
|
|
689
|
+
print(f"TermsForPub: {duration:.4f}s {'✅' if success else '❌'}")
|
|
690
|
+
if success and result:
|
|
691
|
+
count = result.get('count', 0)
|
|
692
|
+
print(f" └─ Found {count} terms for publication" + (", returned 10" if count > 10 else ""))
|
|
693
|
+
self.assertLess(duration, self.THRESHOLD_MEDIUM, "TermsForPub exceeded threshold")
|
|
694
|
+
|
|
695
|
+
# TransgeneExpressionHere - Complex transgene expression query
|
|
696
|
+
result, duration, success = self._time_query(
|
|
697
|
+
"TransgeneExpressionHere",
|
|
698
|
+
get_transgene_expression_here,
|
|
699
|
+
anatomy_term,
|
|
700
|
+
return_dataframe=False,
|
|
701
|
+
limit=10
|
|
702
|
+
)
|
|
703
|
+
print(f"TransgeneExpressionHere: {duration:.4f}s {'✅' if success else '❌'}")
|
|
704
|
+
if success and result:
|
|
705
|
+
count = result.get('count', 0)
|
|
706
|
+
print(f" └─ Found {count} transgene expressions" + (", returned 10" if count > 10 else ""))
|
|
707
|
+
self.assertLess(duration, self.THRESHOLD_SLOW, "TransgeneExpressionHere exceeded threshold")
|
|
708
|
+
|
|
709
|
+
print(f"✅ All publication/transgene queries completed")
|
|
710
|
+
|
|
711
|
+
def tearDown(self):
|
|
712
|
+
"""Generate performance summary"""
|
|
713
|
+
pass
|
|
714
|
+
|
|
715
|
+
@classmethod
|
|
716
|
+
def tearDownClass(cls):
|
|
717
|
+
"""Generate final performance report"""
|
|
718
|
+
print("\n" + "="*80)
|
|
719
|
+
print("PERFORMANCE TEST SUMMARY")
|
|
720
|
+
print("="*80)
|
|
721
|
+
|
|
722
|
+
# This will be populated by the test instance
|
|
723
|
+
# For now, just print a summary message
|
|
724
|
+
print("All performance tests completed!")
|
|
725
|
+
print("="*80)
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def run_tests():
|
|
729
|
+
"""Run the performance test suite"""
|
|
730
|
+
# Create test suite
|
|
731
|
+
loader = unittest.TestLoader()
|
|
732
|
+
suite = loader.loadTestsFromTestCase(QueryPerformanceTest)
|
|
733
|
+
|
|
734
|
+
# Run tests with detailed output
|
|
735
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
736
|
+
result = runner.run(suite)
|
|
737
|
+
|
|
738
|
+
return result.wasSuccessful()
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
if __name__ == '__main__':
|
|
742
|
+
success = run_tests()
|
|
743
|
+
sys.exit(0 if success else 1)
|