Selectors

This section reviews what are NeuroSTR selector functions and how to use them. Selectors are a key component in the NeuroSTR architecture. All classes, functions, etc. related to selectors are defined in the neurostr::selector namespace. You can include their headers individually or take them all by adding the header file selector.h

Basics

The selector concept is quite simple: A selector is a function that takes one or more references to elements of a neuron (or the neuron itself) as input, and returns one or more elements as output. Easy, isn't it?

Selector basics

Single elements references (e.g. a single Node) are simply C++ references, whereas sets are std::vector of std::reference_wrapper. So, looking at the input and output arity we distinguish four selector function types:

  1. Single input - single output: [](const ElementA& n) => const ElementB& {}
  2. Single input - set output: [](const ElementA& n) => std::vector<std::reference_wrapper<ElementB>> {}
  3. Set input - single output: [](const reference_iter& beg, const reference_iter& end) => const ElementB& {}
  4. Set input - set output: [](const reference_iter<A>& beg, const reference_iter<A>& end) => std::vector<std::reference_wrapper<ElementB>> {}

Here we use reference_iter<Element> as shorthand for std::vector<std::reference_wrapper<Element>>::iterator Any function with signature equal to one of these, can be used as selector in NeuroSTR and it is compatible with all selector functionality in the library. Along the documentation, we might mention filter functions which are just Set input - set output (type 4) selectors that return a subset of a given set of elements.


Prebuilt selectors

NeuroSTR includes a relatively large set of prebuilt selector functions. They are organized in four categories, according to their input element type. For example, all selector functions that take either a single node or a node set as input fall into the Node category.

Here is the list of available selectors, you can find more details about each selector by clicking on their name:

Node Selectors

Branch Selectors


Neurite Selectors


Neuron Selectors


Generic Selectors


Selector operations

So far, we have a (large) set of selector functions that might cover our needs. But, imagine that we need a essential selector that, unfortunately, we forgot to implement. Wouldn't be nice to be able to reuse other selectors?. That's where selector operations become really useful. Now we proceed to describe the operations available, but in the next section we show you how to use the selector operations to create a new useful selector. At the moment all operations are templated operations, in other words they are done in compile time, in the close future neuroSTR will support execution time operations.

There are two classes of selector operations: selector functions inner operations and selection set operations. The latter, are operations that combine the output of two selectors, whereas the first are regular function operation over selectors.

Inner operations


Set operations


Create a selector

So far you have read all about selectors but you still feel like a fish out of water. To ease your mind we will show you how to create a new selector in 5 lines. Let's start by picking a prebuilt selector that seems "hard" to implement: Pre-termianl branches selector.

  1. The Pre-termianl branches selector selects all branches in a neurite which have at least one daughter branch that is terminal. Instead of writing it from scratch we will use the selector operations. Selecting all terminal branches in the neurite seems like a good start, so we use the Terminal branches selector.

    using namespace ns = neurostr::selector;
    ns::neurite_terminal_branches();
  2. Since we want the pre-terminal seems pretty obvious that we should use the Parent branch selector somewhere. Actually we want to select the parent of each terminal branch...that is exactly what the Combined Foreach operation does:

    ns::selector_foreach(ns::neurite_terminal_branches,
                       ns::branch_parent_selector);
  3. But...what if the neurite has only one branch? We should select and empty set (and the parent selector don't work that way). We need to remove the first branch from the terminal branches selection first. We can select the first branch with the First branch selector, but the Asymmetric Difference operator requires both arguments to output a set (and to have the same input type), so first we convert the First branch selector to an output set selector:

    ns::selector_out_single_to_set(ns::neurite_first_branch_selector);
  4. Then we can remove it from the terminal branches selection

    ns::diff_selector_factory(ns::neurite_terminal_branches,
                            ns::selector_out_single_to_set(ns::neurite_first_branch_selector));
  5. Finally, we can create our selector in one (really long) line. Of course, for the sake of code readability, you ought to store intermediate results in auxiliar variables:

    auto preterminal_selector = ns::selector_foreach(
    
                                ns::diff_selector_factory(ns::neurite_terminal_branches,
                                                          ns::selector_out_single_to_set(ns::neurite_first_branch_selector)),
                                ns::branch_parent_selector);