# SPDX-FileCopyrightText: 2025 Poirazi Lab <dendrotweaks@dendrites.gr>
# SPDX-License-Identifier: MPL-2.0
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
[docs]
def get_node_data(nodes):
data = {
'idx': [node.idx for node in nodes],
'depth': [node.depth for node in nodes],
'length': [node.length for node in nodes],
'diam': [node.diam for node in nodes],
'area': [node.area for node in nodes],
'n_children': [len(node.children) for node in nodes]
}
return pd.DataFrame(data)
[docs]
def calculate_section_statistics(sections, param_name=None):
df = get_node_data(sections)
depth_counts = df['depth'].value_counts().sort_index()
stats = {
'N_sections': len(df),
'N_bifurcations': (df['n_children'] == 2).sum(),
'N_terminations': (df['n_children'] == 0).sum(),
'depth': {
'min': df['depth'].min(),
'max': df['depth'].max(),
'counts': depth_counts.to_dict(),
},
'diam': {
'min': np.round(df['diam'].min(), 2),
'max': np.round(df['diam'].max(), 2),
'mean': np.round(df['diam'].mean(), 2),
'std': np.round(df['diam'].std(), 2)
},
'length': {
'min': np.round(df['length'].min(), 2),
'max': np.round(df['length'].max(), 2),
'mean': np.round(df['length'].mean(), 2),
'std': np.round(df['length'].std(), 2)
},
'area': {
'min': np.round(df['area'].min(), 2),
'max': np.round(df['area'].max(), 2),
'mean': np.round(df['area'].mean(), 2),
'std': np.round(df['area'].std(), 2)
},
'total_length': np.round(df['length'].sum(), 2),
'total_area': np.round(df['area'].sum(), 2)
}
return stats
[docs]
def calculate_cell_statistics(model):
all_sections = []
for domain in model.domains.values():
all_sections.extend(domain.sections)
return calculate_section_statistics(all_sections)
[docs]
def calculate_domain_statistics(model, domain_names=None, param_name=None):
if domain_names is None:
return calculate_cell_statistics(model)
if not isinstance(domain_names, list):
raise ValueError("domain_names must be a list of strings")
domains = [domain for domain in model.domains.values() if domain.name in domain_names]
stats = {}
for domain in domains:
stats[domain.name] = calculate_section_statistics(domain.sections)
return stats
[docs]
def calculate_segment_statistics(model, segments):
df = model.get_node_data(segments)
stats = {
'N_segments': len(df),
'N_bifurcations': (df['n_children'] == 2).sum(),
'N_terminations': (df['n_children'] == 0).sum(),
'diam': (np.round(df['diam'].mean(), 2), np.round(df['diam'].std(), 2), np.round(df['diam'].min(), 2), np.round(df['diam'].max(), 2)),
'length': (np.round(df['length'].mean(), 2), np.round(df['length'].std(), 2), np.round(df['length'].min(), 2), np.round(df['length'].max(), 2)),
'area': (np.round(df['area'].mean(), 2), np.round(df['area'].std(), 2), np.round(df['area'].min(), 2), np.round(df['area'].max(), 2)),
'total_lenght': np.round(df['length'].sum(), 2),
'total_area': np.round(df['area'].sum(), 2)
}
[docs]
def update_histogram(model, param_name, segments, **kwargs):
if param not in ['diam', 'length', 'area']:
raise ValueError(f"Invalid parameter: {param}")
values = [seg.get_param_value(param_name) for seg in segments]
hist, edges = np.histogram(values, **kwargs)
return hist, edges
[docs]
def calculate_pairwise_synaptic_distances(synapses):
"""
Calculates the pairwise distances between synapses based on their locations on the morphology.
Parameters
----------
synapses : list[Synapse]
A list of Synapse objects.
Returns
-------
np.ndarray
A 2D array of shape (N, N) where N is the number of synapses, containing the pairwise distances.
"""
pairwise_distances_matrix = np.zeros((len(synapses), len(synapses)))
for i, syn1 in enumerate(synapses):
for j, syn2 in enumerate(synapses):
if syn1 != syn2:
dist = syn1.sec.path_distance(
other=syn2.sec,
relative_position=syn1.loc,
relative_position_other=syn2.loc
)
pairwise_distances_matrix[i, j] = dist
return pairwise_distances_matrix