Queries¶
The strength of a spatial index is that one can query it to get all elements within a certain query shape, quickly. Here “quickly” means without looping over all elements in the index.
This section describes the available queries and their API. Note that this applies to both indexes for a single population and multi-population indexes. If you’re writing code where you don’t know which type of index you’re dealing with please consult Writing Generic Code.
Query Shapes¶
A query returns all indexed elements that intersect with the query shape in the requested geometry mode. The query shape can be:
An axis-aligned box in which case the query is called a box query. The axis-aligned box can be specified using any two opposing corners of the box; often the min/max corners are used, but any two corners that define the box are accepted.
A sphere for which we call the query a sphere query.
A cylinder (mostly for internal purposes such as placing segments).
Indexed Elements¶
SI supports indexes containing points, boxes, spheres and cylinders. From these we can build indexes for
morphologies which refers a discretization of the morphology in terms of a sphere for the soma and cylinders for the segments of the axons or dendrites,
synapses which are treated as points.
A string identifier of the indexed element can be obtained by
index.element_type
which is
"morphology"for morphology indexes,"synapse"for synapse indexes,"sphere"for indexes of spheres.
Regular Queries¶
Regular queries are queries which return attributes of the indexed elements that intersect with a given query shape. Please see Counting Queries only the number of index elements is needed.
Keyword argument: fields¶
The preferred output format of queries are numpy arrays containing the values
of interest. Which attributes are returned is controlled by a keyword argument
fields. If fields is a string then a list or numpy array is returned.
To allow retrieving multiple attributes in a single query, a (non-string)
iterable can be passed to fields. In this case a dictionary of the
retrieved attributes is returned.
With very few and clearly documented exceptions, all fields can be combined together as desired. The fields that don’t play nicely will be called partially supported. Please note that partially supported fields are intended for internal debugging purposes only, if you find yourself relying on them a lot please report it as an issue.
Morphology Indexes¶
For morphology indexes the supported fields are:
"gid"which is the GID of the neuron,"section_id"which is the section ID of the neuron,"segment_id"which is the segment ID of the neuron,"ids"the three ids as a numpy structured array,"centroid"the center of the sphere or cylinder,endpointswhich, for segments, is a tuple of the two centers of the caps of the cylinder. For somas only the first array of points is valid and represents the center of the soma.radiuswhich is the radius of either the sphere or cylinder.section_typewhich is the type of the section, see Section Type.is_somaan array of booleans which areTrueif that element is a soma.
The partially supported field is:
"raw_elements"in rare cases one may be interested in a list of Python objects, i.e., acore.MorphoEntrywhich is a C++ variant that represents either a soma or a segment.
Section Type¶
The field section_type contains information on the nature of the section.
In particular it categorizes the various sections of the circuit in 4 types:
soma
axon
basal dendrite
apical dendrite
The information is fetched directly from the SONATA files. You can find more information here.
Examples¶
>>> index = brain_indexer.open(morph_index_path)
>>> index.box_query(*window, fields="gid")
np.array([12, 3, 32, ...], np.int64)
>>> index.box_query(*window, fields=["gid"])
{
"gid": np.array([12, 3, 32, ...], np.int64)
}
>>> index.box_query(*window, fields=["gid", "radius"])
{
"gid": np.array([...], ...),
"radius": np.array([...], ...)
}
>>> index.box_query(*window)
{
"gid": ...,
"section_id": ...,
...
"is_soma": ...
}
Synapse Indexes¶
For synapse indexes the supported fields are:
"id"which is the ID of the synapse,"post_gid"which is the GID of the post-synaptic neuron,"pre_gid"which is the GID of the pre-synaptic neuron,"position"the center of the sphere or cylinder.
The partially supported field is:
"raw_elements"in rare cases one may be interested in a list of Python objects, i.e.,core.Synapse.
Sphere Indexes¶
Indexes of Spheres support the following fields:
"id"which is the ID of the synapse,"centroid"which is the center of the sphere,"radius"which is the radius of the sphere,
The partially supported field is:
"raw_elements"in rare cases one may be interested in a list of Python objects, i.e.core.IndexedSphere.
SONATA Fields¶
Synapse indexes created from SONATA input files, can be queried for attributes
stored in the input file. This is accomplishes passing the SONATA name of the
attribute to fields. SONATA fields can be combined with any other fully
supported field.
As an example the section and segment id on the pre- and post-synapse can be obtained as follows:
>>> index.box_query(
*window,
fields=[
"id",
"pre_gid", "post_gid",
"afferent_section_id", "afferent_segment_id",
"efferent_section_id", "efferent_segment_id",
]
)
{
"id": ...,
...
"efferent_segment_id": ...
}
Keyword argument: accuracy¶
The query always reports all elements that intersect (as opposed
to contained in) with the query shape. However, it is not always possible to
decide efficiently if the element intersects exactly with the query shape. In
particular, when the indexed element is a cylinder/segment, closed formulas
rarely exist. Therefore, SI exposes a keyword argument accuracy which
controls how accurately the indexed element is treated during queries. There
are two values:
best_effortAs the name indicates exact closed formulas are used if available. If not the cylinder is approximated by a capsule, i.e., a cylinder with two half spheres on either end. For capsules efficient closed formulas to detect intersection always exist. The final twist is that in all cases there is a pre-check to see if the exact bounding boxes of the query shape and of the indexed element intersect. This is the default.bounding_boxThe indexed elements are treated as if they were equal to their exact minimal bounding box. This is similar to how the FLAT index treated indexed elements.
Examples¶
>>> index = brain_indexer.open_index(morph_index_path)
>>> index.box_query(*window, accuracy="best_effort")
{
"gid": ...,
...
"is_soma": ...,
}
Counting Queries¶
Counting queries are queries for which only the number of index elements is returned. If information about the individual indexed elements themselves is needed, please consult Regular Queries.
The API for counting queries is simple and the accuracy can be controlled in the same way as for regular indexes.
>>> index.box_counts(*window)
9238
>>> index.sphere_counts(*sphere)
2789
Keyword argument: group_by¶
For synapse indexes a special mode of counting is supported. For indexes of synapses from N source populations into a single target population, one can group the synapses by the GID of the target neuron; and then count the number of synapses per target GID.
This is enabled through the keyword argument group_by="post_gid".
# The keys of the dictionary are the target GIDs, and
# the values are the number of synapses are contained in
# `box` with the specified target GID.
>>> index.box_counts(*box, group_by="post_gid")
{
2379: 23,
293: 1,
...
}
Existence Queries¶
A variant of counting queries is to know if no element intersects the query shape. This could be implemented as:
>>> index.box_counts(*box) == 0
However, this can’t take advantage of the short circuiting trick, i.e., to
return True as soon as the first element has been found. Therefore,
brain-indexer provides a method for this.
>>> index.box_empty(*box)
>>> index.sphere_empty(*sphere)
Both methods support the keyword argument accuracy, see regular indexes.