class Features(ABC):
def __init__(self, neuron: "core.TreeNeuron", label=None, verbose=False):
self.neuron = neuron
self.verbose = verbose
if label is None:
self.label = ""
elif not label.endswith("_"):
self.label = f"{label}_"
else:
self.label = label
# Make sure the neuron is rooted to the soma (if present)
self.soma = self.neuron.soma
if self.soma is not None:
self.soma_pos = self.neuron.soma_pos[0]
self.soma_radius = self.neuron.nodes.set_index("node_id").loc[
self.soma, "radius"
]
if self.neuron.soma not in self.neuron.root:
self.neuron = self.neuron.reroot(self.neuron.soma)
# For each leaf, the geodesic distance to the farthest node it can reach
# travelling towards the root (i.e. its distance to the root). Note we
# only ever use the maximum, so there is no point in building the full
# leafs x nodes matrix - that's gigabytes on a large neuron.
leafs = np.unique(self.neuron.leafs.node_id.values)
node_ids = self.neuron.nodes.node_id.values
parent_ids = self.neuron.nodes.parent_id.values
if utils.fastcore:
dists, _ = utils.fastcore.geodesic_farthest(
node_ids,
parent_ids,
sources=leafs,
directed=True,
weights=utils.fastcore.dag.parent_dist(
node_ids,
parent_ids,
self.neuron.nodes[["x", "y", "z"]].values,
root_dist=0,
),
)
else:
dmat = graph.geodesic_matrix(self.neuron, leafs, directed=True)
# Replace infinities with -1
dmat[dmat == float("inf")] = -1
dists = dmat.values.max(axis=1)
self.max_leaf_dist = pd.Series(dists, index=leafs)
self.features = {}
def record_feature(self, name, value):
"""Record a feature."""
self.features[f"{self.label}{name}"] = value
@abstractmethod
def extract_features(self):
"""Extract features."""
pass